import { test } from 'node:test'; import assert from 'node:assert/strict'; import { createScopedLoader, loadQueryKey } from '../../src/lib/canvas-runtime/loader.ts'; const scope = { accountId: 'account', workspaceId: 'workspace', projectId: 'project', canvasId: 'canvas', viewKey: 'canvas', format: 'v1', queryFingerprint: 'all', source: 'fixture', authEpoch: 1, navigationGeneration: 1 }; const deferred = () => { let resolve, reject; const promise = new Promise((yes, no) => { resolve = yes; reject = no; }); return { promise, resolve, reject }; }; const tick = () => new Promise(resolve => setImmediate(resolve)); const chunk = (query, sequence = 0, complete = true, extra = {}) => ({ queryKey: loadQueryKey(query), projectId: 'project', snapshotToken: 'snapshot', serverVersions: { canvas: 3 }, permissionEpoch: query.authEpoch, sequence, coverage: [`part-${sequence}`], complete, byteLength: 2, value: Object.freeze({ part: sequence }), ...extra }); test('subscribers share a progressive read while retaining navigation scopes; cancelling one preserves the other', async () => { const gate = deferred(), updates = []; let calls = 0, signal; const loader = createScopedLoader({ current: () => true, async *load(query, abort) { calls++; signal = abort; yield chunk(query, 0, false); await gate.promise; yield chunk(query, 1); } }); const a = loader.acquire(scope, { onUpdate: value => updates.push(value.status) }); await tick(); assert.equal(a.getSnapshot().status, 'initial-ready'); assert.equal(a.getSnapshot().chunks.length, 1); assert.strictEqual(a.getSnapshot(),a.getSnapshot()); const b = loader.acquire({ ...scope, navigationGeneration: 2 }); assert.equal(b.getSnapshot().scope.navigationGeneration, 2); const cancelled = assert.rejects(a.ready, { code: 'load_aborted' }); a.release(); await cancelled;const stopped=a.getSnapshot(); assert.equal(signal.aborted, false); gate.resolve(); const result = await b.ready; assert.equal(calls, 1); assert.equal(result.status, 'complete'); assert.deepEqual(result.coverage, ['part-0', 'part-1']); assert.equal(a.getSnapshot().status,'cancelled');assert.equal(a.getSnapshot().chunks.length,1);assert.deepEqual(a.getSnapshot().coverage,['part-0']); assert.strictEqual(a.getSnapshot(),stopped);assert.strictEqual(b.getSnapshot(),result); assert.deepEqual(updates, ['queued', 'loading', 'initial-ready']); b.release(); loader.close(); }); test('tail failure keeps the accepted prefix and a fresh retry replaces the failed inflight entry', async () => { let calls = 0; const loader = createScopedLoader({ current: () => true, async *load(query) { calls++; yield chunk(query, 0, false); if (calls === 1) throw Object.assign(Error('tail_failed'), { code: 'tail_failed' }); yield chunk(query, 1); } }); const a = loader.acquire(scope); await assert.rejects(a.ready, { code: 'tail_failed' }); assert.equal(a.getSnapshot().status, 'failed-with-partial'); assert.equal(a.getSnapshot().chunks[0].value.part, 0); const b = loader.acquire(scope); assert.equal((await b.ready).chunks.length, 2); assert.equal(calls, 2); a.release(); b.release(); loader.close(); }); test('a complete marker is not authoritative before iterator EOF; a checksum/trailing failure retains partial status', async () => { const statuses = [], loader = createScopedLoader({ current: () => true, async *load(query) { yield chunk(query); throw Error('trailing_checksum'); } }); const handle = loader.acquire(scope, { onUpdate: value => statuses.push(value.status) }); await assert.rejects(handle.ready, /trailing_checksum/); assert.equal(handle.getSnapshot().status, 'failed-with-partial'); assert.equal(statuses.includes('complete'), false); handle.release(); loader.close(); }); test('snapshot token, version vector and project changes reject the tail without mixing it into accepted coverage', async () => { for (const extra of [{ snapshotToken: 'other' }, { serverVersions: { canvas: 4 } }, { projectId: 'other' }]) { const loader = createScopedLoader({ current: () => true, async *load(query) { yield chunk(query, 0, false); yield chunk(query, 1, true, extra); } }); const handle = loader.acquire(scope); await assert.rejects(handle.ready); assert.equal(handle.getSnapshot().status, 'failed-with-partial'); assert.equal(handle.getSnapshot().chunks.length, 1); assert.deepEqual(handle.getSnapshot().coverage, ['part-0']); handle.release(); loader.close(); } }); test('query, permission, sequence and malformed coverage headers reject before exposing a chunk', async () => { for (const extra of [{ queryKey: 'foreign' }, { permissionEpoch: 2 }, { sequence: 3 }, { coverage: ['x', 'x'] }, { byteLength: -1 }, { serverVersions: { canvas: 0 } }]) { const loader = createScopedLoader({ current: () => true, async *load(query) { yield chunk(query, 0, true, extra); } }); const handle = loader.acquire(scope); await assert.rejects(handle.ready, { code: 'invalid_load_chunk' }); assert.equal(handle.getSnapshot().chunks.length, 0); assert.equal(handle.getSnapshot().status, 'failed'); handle.release(); loader.close(); } }); test('an incomplete EOF or extra chunk after a complete marker cannot become a complete result', async () => { for (const extra of [false, true]) { const loader = createScopedLoader({ current: () => true, async *load(query) { yield chunk(query, 0, extra); if (extra) yield chunk(query, 1); } }); const handle = loader.acquire(scope); await assert.rejects(handle.ready); assert.equal(handle.getSnapshot().status, 'failed-with-partial'); handle.release(); loader.close(); } }); test('response byte and chunk budgets bound retained read copies while preserving earlier chunks', async () => { for (const limits of [{ maxResponseBytes: 3 }, { maxChunks: 1 }]) { const loader = createScopedLoader({ ...limits, current: () => true, async *load(query) { yield chunk(query, 0, false); yield chunk(query, 1); } }); const handle = loader.acquire(scope); await assert.rejects(handle.ready, { code: 'load_response_budget' }); assert.equal(handle.getSnapshot().byteLength, 2); assert.equal(handle.getSnapshot().chunks.length, 1); handle.release(); loader.close(); } }); test('cancelled active work retains its concurrency slot; its late cleanup cannot delete the newer generation', async () => { const first = deferred(), second = deferred(), entered = []; let calls = 0; const loader = createScopedLoader({ concurrency: 1, maxQueued: 1, current: () => true, async *load(query) { const index = ++calls; entered.push(index); await (index === 1 ? first.promise : second.promise); yield chunk(query); } }); const a = loader.acquire(scope); await tick(); const cancelled = assert.rejects(a.ready, { code: 'load_aborted' }); a.release(); await cancelled; const b = loader.acquire(scope); await tick(); assert.equal(calls, 1); assert.equal(b.getSnapshot().status, 'queued'); assert.throws(() => loader.acquire({ ...scope, canvasId: 'third' }), { code: 'load_capacity' }); first.resolve(); await tick(); assert.equal(calls, 2); const peer = loader.acquire(scope); assert.equal(peer.getSnapshot().generation, b.getSnapshot().generation); second.resolve(); await Promise.all([b.ready, peer.ready]); assert.deepEqual(entered, [1, 2]); b.release(); peer.release(); loader.close(); }); test('queued cancellation frees admission capacity and never executes the abandoned source', async () => { const gate = deferred(), called = [], loader = createScopedLoader({ concurrency: 1, maxQueued: 1, current: () => true, async *load(query) { called.push(query.canvasId); await gate.promise; yield chunk(query); } }); const a = loader.acquire(scope), b = loader.acquire({ ...scope, canvasId: 'cancelled' }); const cancelled = assert.rejects(b.ready, { code: 'load_aborted' }); b.release(); await cancelled; const c = loader.acquire({ ...scope, canvasId: 'next' }); gate.resolve(); await Promise.all([a.ready, c.ready]); assert.deepEqual(called, ['canvas', 'next']); a.release(); c.release(); loader.close(); }); test('read cache requires explicit source validation and is bounded by byte LRU and permission age', async () => { let time = 100, accepted = true; const calls = new Map(); const loader = createScopedLoader({ maxCacheEntries: 10, maxCacheBytes: 4, cacheMaxAgeMs: 100, now: () => time, acceptCached: () => accepted, current: () => true, async *load(query) { calls.set(query.canvasId, (calls.get(query.canvasId) ?? 0) + 1); yield chunk(query); } }); const read = async (canvasId, cached = false) => { const handle = loader.acquire({ ...scope, canvasId }, { cache: cached ? 'allow-validated' : 'fresh' }); await handle.ready; handle.release(); }; await read('a'); await read('b'); await read('a', true); assert.equal(calls.get('a'), 1); await read('c'); await read('b', true); assert.equal(calls.get('b'), 2); accepted = false; await read('b', true); assert.equal(calls.get('b'), 3); accepted = true; time = 201; await read('b', true); assert.equal(calls.get('b'), 4); time = 0; await read('b', true); assert.equal(calls.get('b'), 5); loader.close(); }); test('different query fingerprints and permission epochs do not share requests', async () => { let calls = 0; const loader = createScopedLoader({ current: () => true, async *load(query) { calls++; yield chunk(query); } }); const handles = [scope, { ...scope, queryFingerprint: 'filtered' }, { ...scope, authEpoch: 2 }].map(query => loader.acquire(query)); await Promise.all(handles.map(handle => handle.ready)); assert.equal(calls, 3); handles.forEach(handle => handle.release()); loader.close(); }); test('scope capture owns only declared metadata without freezing unrelated caller state', async () => { const extra={filter:'mutable'},input={...scope,extra};const loader=createScopedLoader({current:()=>true,async *load(query){yield chunk(query);}}); const handle=loader.acquire(input);assert.equal(Object.isFrozen(extra),false);input.canvasId='later';extra.filter='changed'; const result=await handle.ready;assert.equal(result.scope.canvasId,'canvas');assert.equal(Object.hasOwn(result.scope,'extra'),false);assert.equal(Object.isFrozen(result.scope),true);handle.release();loader.close(); }); test('identity changes discard late chunks, and close promptly rejects waiting readers even if the source ignores abort', async () => { const gate = deferred(); let current = true; const loader = createScopedLoader({ current: () => current, async *load(query) { await gate.promise; yield chunk(query); } }); const a = loader.acquire(scope); const rejected = assert.rejects(a.ready, { code: 'identity_changed' }); current = false; gate.resolve(); await rejected; assert.equal(a.getSnapshot().chunks.length, 0); a.release(); loader.close(); const pending = deferred(), closed = createScopedLoader({ current: () => true, async *load(query) { await pending.promise; yield chunk(query); } }); const b = closed.acquire(scope); const stopped = assert.rejects(b.ready, { code: 'loader_closed' }); closed.close(); await stopped; pending.resolve(); await tick(); assert.equal(b.getSnapshot().status, 'cancelled'); assert.throws(() => closed.acquire(scope), { code: 'loader_closed' }); }); test('observer reentrancy does not re-notify old subscribers or allow observer exceptions to fail a read', async () => { const errors = []; let peer, updates = 0; const loader = createScopedLoader({ current: () => true, onObserverError: error => errors.push(error), async *load(query) { yield chunk(query); } }); const a = loader.acquire(scope, { onUpdate: state => { updates++; if (state.status === 'initial-ready' && !peer) peer = loader.acquire(scope, { onUpdate: () => { throw Error('observer'); } }); } }); await a.ready; await peer.ready; assert.equal(updates, 4); assert.ok(errors.length > 0); a.release(); peer.release(); loader.close(); });