import { test } from "node:test"; import assert from "node:assert/strict"; import { createFocusCoordinator, createRevisionGateway } from "../../src/lib/preview/focus.js"; import { createFixtureResolver } from "../../src/lib/preview/resolver.js"; function harness({ audible = true, ackGeneration, ackApplied = true, ackDelayMs = 0, ackHangs = false } = {}) { const gateway = createRevisionGateway(); const resumed = []; const status = { status: audible ? "playing" : "idle", trackKey: "track-A", positionMs: 102_000, audible, playbackGeneration: 7, }; const coord = createFocusCoordinator({ mainStatus: () => ({ ...status }), requestSilence: (gen) => ackHangs ? new Promise(() => {}) : new Promise((res) => setTimeout(() => res({ generation: ackGeneration ?? gen, applied: ackApplied }), ackDelayMs) ), resumeMain: (snapshot, opts) => resumed.push({ snapshot, opts }), gateway, ackTimeoutMs: 50, }); return { coord, gateway, resumed, status }; } // ------------------------------------------------------------ acquisition -- test("main idle: focus granted without any silence negotiation, nothing to restore", async () => { const { coord, resumed } = harness({ audible: false }); const { granted, token } = await coord.acquirePreview(); assert.ok(granted); assert.equal(token.pausedByPreview, false); const rel = coord.release(token); assert.equal(rel.restored, false); assert.equal(rel.reason, "main_was_not_audible"); assert.equal(resumed.length, 0); }); test("main audible: granted only through a generation-bound acknowledgement", async () => { const { coord } = harness({ audible: true }); const { granted, token } = await coord.acquirePreview(); assert.ok(granted); assert.equal(token.pausedByPreview, true); assert.equal(token.snapshot.trackKey, "track-A"); assert.equal(token.snapshot.positionMs, 102_000); }); test("stale/wrong-generation ack leaves preview parked", async () => { const { coord } = harness({ audible: true, ackGeneration: 3 }); // main is at 7 const { granted, token } = await coord.acquirePreview(); assert.equal(granted, false); assert.equal(token, null); }); test("ack that never arrives (timeout) leaves preview parked — renderer status is not proof", async () => { const { coord } = harness({ audible: true, ackHangs: true }); const { granted } = await coord.acquirePreview(); assert.equal(granted, false); }); test("ack applied=false is not silence", async () => { const { coord } = harness({ audible: true, ackApplied: false }); const { granted } = await coord.acquirePreview(); assert.equal(granted, false); }); test("user command DURING negotiation invalidates the grant", async () => { const { coord, gateway } = harness({ audible: true, ackDelayMs: 20 }); const p = coord.acquirePreview(); gateway.userSeekOrScratch(); // user scrubbed while we negotiated const { granted } = await p; assert.equal(granted, false); }); // -------------------------------------------------------------- release ---- test("clean release restores main at the snapshot, tagged focus-internal", async () => { const { coord, gateway, resumed } = harness({ audible: true }); const { token } = await coord.acquirePreview(); const rel = coord.release(token); assert.ok(rel.restored); assert.equal(resumed.length, 1); assert.equal(resumed[0].snapshot.trackKey, "track-A"); assert.equal(resumed[0].snapshot.positionMs, 102_000); assert.equal(resumed[0].opts.focusInternal, true); // and the focus-internal resume did not invalidate anything assert.equal(gateway.changedSince(token.revisions), false); }); for (const [name, bump] of [ ["user pause/resume", (g) => g.userPauseResume()], ["user starts another track", (g) => g.userPlay()], ["queue jump / next / previous", (g) => g.queueJumpOrNextPrev()], ["queue mutation", (g) => g.queueMutation()], ["seek/scratch", (g) => g.userSeekOrScratch()], ["stop", (g) => g.userStop()], ["auto-advance", (g) => g.autoAdvance()], ["native source change", (g) => g.nativeSourceChange()], ]) { test(`no restoration after ${name}`, async () => { const { coord, gateway, resumed } = harness({ audible: true }); const { token } = await coord.acquirePreview(); bump(gateway); const rel = coord.release(token); assert.equal(rel.restored, false); assert.equal(rel.reason, "revisions_changed"); assert.equal(resumed.length, 0); }); } test("focus-internal commands do not invalidate their own token", async () => { const { coord, gateway } = harness({ audible: true }); const { token } = await coord.acquirePreview(); gateway.userPauseResume({ focusInternal: true }); // the coordinator's own pause gateway.userPlay({ focusInternal: true }); assert.equal(gateway.changedSince(token.revisions), false); assert.ok(coord.release(token).restored); }); test("play-full handoff never restores the old track", async () => { const { coord, resumed } = harness({ audible: true }); await coord.acquirePreview(); const h = coord.handoffToMain(); assert.equal(h.restored, false); assert.equal(h.reason, "handoff"); assert.equal(resumed.length, 0); assert.equal(coord.owner(), "main"); }); test("a stale token cannot release anything", async () => { const { coord } = harness({ audible: true }); const { token } = await coord.acquirePreview(); coord.release(token); const again = coord.release(token); assert.equal(again.restored, false); assert.equal(again.reason, "stale_token"); }); // ------------------------------------------------------------- resolver ---- test("fixture resolver: scripted fail-once-then-succeed with bypassCache", async () => { const src = { provider: "deezer", providerTrackId: "42", matchConfidence: "native" }; const resolver = createFixtureResolver({ 42: (n) => (n === 1 ? { failure: "stale_lease", retryable: true } : { opaqueLeaseId: "op-fresh" }), }); const first = await resolver.resolve(src, { generation: 5 }); assert.equal(first.ok, false); assert.equal(first.failure, "stale_lease"); assert.ok(first.retryable); const second = await resolver.resolve(src, { generation: 5, bypassCache: true }); assert.ok(second.ok); assert.equal(second.lease.transport, "native"); assert.equal(second.lease.opaqueLeaseId, "op-fresh"); assert.equal(resolver.callCount("42"), 2); }); test("fixture resolver: unknown id classifies as not_found, not an exception", async () => { const resolver = createFixtureResolver({}); const r = await resolver.resolve({ provider: "deezer", providerTrackId: "999", matchConfidence: "native" }, { generation: 1 }); assert.equal(r.ok, false); assert.equal(r.failure, "not_found"); assert.equal(r.retryable, false); });