返回 CodeWhale
stock-adapters.test.mjs
根目录 / crates / tui / extension-host / test / stock-adapters.test.mjs
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
104 lines Plain Text