| 1 | import test from 'node:test' |
| 2 | import assert from 'node:assert/strict' |
| 3 | import { transformStockSnapshot } from '../dist/stock-adapters.mjs' |
| 4 | import { createHarnessModule } from '../dist/builtin/harness.mjs' |
| 5 | |
| 6 | const project = (operation, input) => ({ kind: 'stock_adapter', operation, input }) |
| 7 | const request = { requested_ticker: 'BTC', resolved_symbol: 'BTC-USD' } |
| 8 | const quote = extra => project('finance_quote', { request, parsed: { quoteResponse: { result: [{ symbol: 'btc-usd', regularMarketPrice: 125, regularMarketPreviousClose: 100, regularMarketTime: '9223372036854775807', ...extra }] } } }) |
| 9 | const chart = extra => project('finance_chart', { request, parsed: { chart: { result: [{ meta: { symbol: 'BTC-USD', regularMarketPrice: 125, chartPreviousClose: 100, previousClose: 50, ...extra } }], error: null } } }) |
| 10 | const metadata = value => transformStockSnapshot(value).result.metadata |
| 11 | const owner = { plugin_id: 'host:harness', generation: 4, owner_token: 'opaque-fixture-owner' } |
| 12 | const params = extra => ({ owner, execution_id: 'captured-one', ticket: 'opaque-fixture-ticket', deadline_ms: 1000, ...extra }) |
| 13 | |
| 14 | test('quote normalization preserves priority, changes and exact i64 timestamps', () => { |
| 15 | assert.deepEqual(metadata(quote({ longName: 'Long', shortName: 'Short', exchange: 'low', fullExchangeName: 'Full' })), { |
| 16 | requested_ticker: 'BTC', ticker: 'btc-usd', price: '125', previous_close: '100', change: '25', change_percent: '25', |
| 17 | source: 'yahoo_quote', fallback_used: false, name: 'Long', exchange: 'Full', market_time: '9223372036854775807', |
| 18 | }) |
| 19 | const result = metadata(quote({ regularMarketChange: 0, regularMarketChangePercent: 0, longName: '', regularMarketTime: '-9223372036854775808' })) |
| 20 | assert.equal(result.change, '0'); assert.equal(result.change_percent, '0'); assert.equal(result.name, '') |
| 21 | assert.equal(result.market_time, '-9223372036854775808') |
| 22 | }) |
| 23 | test('numeric wire preserves negative zero, EPSILON policy and derived infinities', () => { |
| 24 | const zero = metadata(quote({ regularMarketPrice: -0, regularMarketPreviousClose: 0 })) |
| 25 | assert.equal(zero.price, '-0'); assert.equal(zero.change, '-0'); assert.equal('change_percent' in zero, false) |
| 26 | assert.equal(metadata(quote({ regularMarketPreviousClose: Number.EPSILON })).change_percent, String(((125 - Number.EPSILON) / Number.EPSILON) * 100)) |
| 27 | const overflow = metadata(quote({ regularMarketPrice: 1e308, regularMarketPreviousClose: -1e308 })) |
| 28 | assert.equal(overflow.change, 'inf'); assert.equal(overflow.change_percent, '-inf') |
| 29 | }) |
| 30 | test('chart fallback uses chart close and the existing field priorities', () => { |
| 31 | assert.deepEqual(metadata(chart({ longName: 'Long', instrumentType: 'CRYPTOCURRENCY', exchangeName: 'low', fullExchangeName: 'Full' })), { |
| 32 | requested_ticker: 'BTC', ticker: 'BTC-USD', price: '125', previous_close: '100', change: '25', change_percent: '25', |
| 33 | source: 'yahoo_chart', fallback_used: true, name: 'Long', quote_type: 'CRYPTOCURRENCY', exchange: 'Full', |
| 34 | }) |
| 35 | }) |
| 36 | test('upstream not-found and missing-price failures retain exact details', () => { |
| 37 | const noResult = project('finance_quote', { request, parsed: { quoteResponse: { result: [] } } }) |
| 38 | assert.deepEqual(metadata(noResult), { endpoint: 'yahoo_quote', kind: 'not_found', detail: "no result for symbol 'BTC-USD'" }) |
| 39 | assert.deepEqual(metadata(quote({ regularMarketPrice: null })), { endpoint: 'yahoo_quote', kind: 'upstream', detail: 'response missing regularMarketPrice' }) |
| 40 | for (const error of [{ code: 'Not Found', description: 'missing' }, { description: 'Symbol may be delisted' }]) { |
| 41 | assert.equal(metadata(project('finance_chart', { request, parsed: { chart: { error } } })).kind, 'not_found') |
| 42 | } |
| 43 | assert.equal(metadata(project('finance_chart', { request, parsed: { chart: { error: { code: 'rate limit', description: 'retry' } } } })).kind, 'upstream') |
| 44 | }) |
| 45 | test('symbol matching uses Rust ASCII case folding rather than Unicode expansion', () => { |
| 46 | const value = project('finance_quote', { request: { requested_ticker: 'SS', resolved_symbol: 'SS' }, parsed: { quoteResponse: { result: [{ symbol: 'ß', regularMarketPrice: 1 }] } } }) |
| 47 | assert.equal(metadata(value).kind, 'not_found') |
| 48 | }) |
| 49 | |
| 50 | const parser = value => ({ ok: true, value }) |
| 51 | const error = message => ({ ok: false, error: message }) |
| 52 | const data = extra => project('validate_data', { format: 'auto', source: 'inline', extension: null, json: error('JSON diagnostic'), toml: error('TOML diagnostic'), ...extra }) |
| 53 | test('data auto selects JSON then TOML, while a file extension pins its parser', () => { |
| 54 | assert.deepEqual(metadata(data({ json: parser({ kind: 'array', entries: 3 }), toml: parser({ kind: 'table', keys: ['a'] }) })), { valid: true, format: 'json', source: 'inline', summary: { top_level: 'array', entries: 3 } }) |
| 55 | assert.equal(metadata(data({ toml: parser({ kind: 'table', keys: ['a'] }) })).format, 'toml') |
| 56 | const pinned = transformStockSnapshot(data({ extension: 'json', toml: parser({ kind: 'table', keys: [] }) })).result |
| 57 | assert.equal(pinned.success, false); assert.equal(pinned.content, 'Invalid JSON: JSON diagnostic') |
| 58 | }) |
| 59 | test('data previews preserve Core captured ordering, ten-key cap and primitive types', () => { |
| 60 | const keys = ['😀', '\ue000', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '__proto__'] |
| 61 | for (const captured of [keys, keys.slice().sort((a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)))]) { |
| 62 | const summary = metadata(data({ json: parser({ kind: 'object', keys: captured }) })).summary |
| 63 | assert.deepEqual(summary.keys_preview, captured.slice(0, 10)) |
| 64 | assert.equal(summary.entries, 12) |
| 65 | } |
| 66 | for (const kind of ['string', 'number', 'boolean', 'null']) assert.equal(metadata(data({ json: parser({ kind }) })).summary.top_level, kind) |
| 67 | for (const kind of ['string', 'integer', 'float', 'boolean', 'datetime']) assert.equal(metadata(data({ format: 'toml', toml: parser({ kind }) })).summary.top_level, kind) |
| 68 | }) |
| 69 | test('parser diagnostics remain exact captured bytes and auto errors are unchanged', () => { |
| 70 | const result = transformStockSnapshot(data({ json: error('line 1:\n bad 😀'), toml: error('toml\nspan') })).result |
| 71 | assert.equal(result.content, 'Validation failed in auto mode: content is neither valid JSON nor TOML.') |
| 72 | assert.deepEqual(result.metadata, { valid: false, format: 'auto', source: 'inline', json_error: 'line 1:\n bad 😀', toml_error: 'toml\nspan' }) |
| 73 | }) |
| 74 | test('malformed, unknown and oversized snapshots refuse instead of producing results', () => { |
| 75 | for (const value of [null, {}, project('fetch', {}), quote({ regularMarketPrice: '125' }), quote({ regularMarketTime: '9223372036854775808' }), data({ json: parser({ kind: 'object', keys: ['a', 'a'] }) }), data({ json: parser({ kind: 'array', entries: -1 }) }), data({ source: 'x'.repeat(1024 * 1024) })]) assert.throws(() => transformStockSnapshot(value)) |
| 76 | }) |
| 77 | |
| 78 | test('actual pinned harness transforms one captured reply and sends only an opaque ticket', async () => { |
| 79 | const calls = [] |
| 80 | const runner = createHarnessModule({ async request(method, input) { calls.push([method, input]); return quote({}) } }, owner) |
| 81 | const result = await runner.run(params(), new AbortController().signal) |
| 82 | assert.equal(result.result.metadata.price, '125') |
| 83 | assert.deepEqual(calls, [['exec/redeem', { owner, execution_id: 'captured-one', ticket: 'opaque-fixture-ticket' }]]) |
| 84 | await runner.dispose() |
| 85 | }) |
| 86 | test('wrong owner cannot redeem a stock operation and malformed reply has no fallback', async () => { |
| 87 | let calls = 0 |
| 88 | const runner = createHarnessModule({ async request() { calls++; return project('unknown', {}) } }, owner) |
| 89 | await assert.rejects(runner.run(params({ owner: { ...owner, plugin_id: 'native' } }), new AbortController().signal), /stale/) |
| 90 | assert.equal(calls, 0) |
| 91 | await assert.rejects(runner.run(params(), new AbortController().signal), /unadmitted/) |
| 92 | assert.equal(calls, 1); await runner.dispose() |
| 93 | }) |
| 94 | test('stock cancellation and owner disposal settle before a late captured reply', async () => { |
| 95 | let finish |
| 96 | const controller = new AbortController() |
| 97 | const runner = createHarnessModule({ request() { return new Promise(resolve => { finish = resolve }) } }, owner) |
| 98 | const pending = runner.run(params(), controller.signal) |
| 99 | controller.abort() |
| 100 | await assert.rejects(pending, /cancelled/) |
| 101 | finish(quote({})); await runner.dispose() |
| 102 | await assert.rejects(runner.run(params(), new AbortController().signal), /stale/) |
| 103 | }) |
| 104 |