import assert from "node:assert/strict"; import { after, before, test } from "node:test"; import { createServer } from "vite"; let server, createIsolatedRerunHost, keepRecordedBlueprintSession, createRecordedRerunFacade, createRecordedRerunOwner, relayIsolatedRerunInput; before(async () => { server = await createServer({ appType: "custom", logLevel: "silent", server: { middlewareMode: true } }); ({ createIsolatedRerunHost } = await server.ssrLoadModule( "/src/components/rerun/isolatedRerunHost.ts")); ({ keepRecordedBlueprintSession } = await server.ssrLoadModule( "/src/core/observation/recordedBlueprintLifecycle.ts")); ({ createRecordedRerunFacade } = await server.ssrLoadModule( "/src/components/rerun/recordedRerunFacade.ts")); ({ createRecordedRerunOwner } = await server.ssrLoadModule( "/src/components/rerun/recordedRerunOwner.ts")); ({ relayIsolatedRerunInput } = await server.ssrLoadModule( "/src/components/rerun/isolatedRerunInput.ts")); }); after(async () => { await server?.close(); }); function fixture(t, { stopFails = false } = {}) { const timers = new Map(); let timerId = 0, creates = 0, stops = 0, removals = 0; t.mock.method(globalThis, "setTimeout", (fn) => { timers.set(++timerId, fn); return timerId; }); t.mock.method(globalThis, "clearTimeout", (id) => timers.delete(id)); const listeners = new Map(); const viewer = { stop() { stops++; if (stopFails) throw new Error("partial start"); } }; const mount = {}; const frame = { contentWindow: { missionCoreRerun: createRecordedRerunOwner(() => { creates++; return viewer; }, mount), addEventListener: (name, fn) => listeners.set(name, fn), removeEventListener: (name) => listeners.delete(name), }, remove() { removals++; }, }; const host = { dataset: {}, ownerDocument: { createElement(name) { assert.equal(name, "iframe"); return frame; } }, append(child) { assert.equal(child, frame); } }; return { host, frame, viewer, mount, timers, listeners, counts: () => ({ creates, stops, removals }) }; } function bridge(native, mount = {}) { const child = createRecordedRerunOwner(() => native, mount); const parent = createRecordedRerunFacade((request, bytes) => { assert.equal(typeof request, "string"); const result = child.invoke(request, bytes); assert.equal(typeof result, "string"); return result; }); child.connect(message => { assert.equal(typeof message, "string"); parent.notify(message); }); return parent; } test("camera snapshot crosses the disposable realm as copied primitive data", () => { const eye = { position: [300, 7, 8], lookTarget: [298, 9, 0], eyeUp: [0, 0, 1] }; const { facade, dispose } = bridge({ stop() {}, get_camera_eye: () => eye }); const received = facade.get_camera_eye(); assert.deepEqual(received, eye); assert.notEqual(received, eye); assert.notEqual(received.position, eye.position); dispose(); assert.throws(() => facade.get_camera_eye(), /disposed/); }); test("recorded realm terminates on close even if upstream stop throws", async (t) => { const f = fixture(t, { stopFails: true }); const scope = createIsolatedRerunHost(f.host); assert.equal(f.frame.src, "/rerun-runtime.html"); f.frame.onload(); const ready = await scope.ready; assert.notEqual(ready.viewer, f.viewer); assert.equal(ready.mount, f.host); assert.equal(f.timers.size, 0); assert.equal(f.listeners.size, 1); scope.dispose(); scope.dispose(); assert.deepEqual(f.counts(), { creates: 1, stops: 1, removals: 1 }); assert.equal(f.listeners.size, 0); assert.equal(f.frame.onload, null); assert.equal(ready.viewer.ready, false); }); test("native frame input relays Escape and leaves pointer ownership inside Rerun", () => { const listeners = new Map(), events = []; class ParentEvent { constructor(type, init) { this.type = type; Object.assign(this, init); } } const host = { dataset: {}, ownerDocument: { defaultView: { KeyboardEvent: ParentEvent, PointerEvent: ParentEvent, } }, dispatchEvent: event => events.push(event) }; const child = { addEventListener: (name, fn) => listeners.set(name, fn), removeEventListener: name => listeners.delete(name), }; const release = relayIsolatedRerunInput(host, child, null); assert.equal(listeners.has("pointerdown"), false); listeners.get("keydown")({ key: "Escape", defaultPrevented: false }); assert.equal(events[0].key, "Escape"); release(); release(); assert.equal(listeners.size, 0); assert.equal(events.length, 1); }); test("disposed facade, channel and unsubscribe reject native use and copy event values", async () => { let eventCallback, unsubscribed = 0, sends = 0, starts = 0; const range = { min: 1, max: 2 }, mount = {}; const native = { ready: true, stop() {}, async start(source, parent) { assert.equal(parent, mount); starts++; }, get_time_range: () => range, on(event, callback) { eventCallback = callback; return () => { unsubscribed++; }; }, open_channel: () => ({ ready: true, send_rrd() { sends++; }, close() {} }), }; const { facade, dispose } = bridge(native, mount); await facade.start(null, {}, null); assert.equal(starts, 1); let event; const unsubscribe = facade.on("time_update", value => { event = value; }); eventCallback(range); assert.deepEqual(event, range); assert.notEqual(event, range); assert.notEqual(facade.get_time_range(), range); const channel = facade.open_channel(); channel.send_rrd(new Uint8Array()); dispose(); dispose(); unsubscribe(); assert.equal(unsubscribed, 1); assert.equal(channel.ready, false); channel.send_rrd(new Uint8Array()); eventCallback({ min: 3, max: 4 }); assert.deepEqual(event, range); assert.equal(sends, 1); await assert.rejects(facade.start(null, {}, null), /disposed/); }); test("close before frame load cancels startup and cannot reopen", async (t) => { const f = fixture(t); const scope = createIsolatedRerunHost(f.host); const lateLoad = f.frame.onload; const rejected = assert.rejects(scope.ready, /disposed/); scope.dispose(); lateLoad(); await rejected; assert.deepEqual(f.counts(), { creates: 0, stops: 0, removals: 1 }); assert.equal(f.timers.size, 0); }); test("closing during SDK startup also releases a late successful start", async () => { let finish, stops = 0; const owner = bridge({ start: () => new Promise(resolve => { finish = resolve; }), stop() { stops++; }, }, {}); const pending = owner.facade.start(null, {}, null); const rejected = assert.rejects(pending, /disposed/); owner.dispose(); assert.equal(stops, 1); finish(); await rejected; await Promise.resolve(); assert.equal(stops, 2); assert.equal(owner.facade.ready, false); }); test("late failed startup cannot revive a disposed parent and still finishes cleanup", async () => { let fail, stops = 0; const owner = bridge({ start: () => new Promise((_resolve, reject) => { fail = reject; }), stop() { stops++; if (stops > 1) throw new Error("already gone"); }, }, {}); const pending = owner.facade.start(null, {}, null); const rejected = assert.rejects(pending, /disposed/); owner.dispose(); fail(new Error("startup failed")); await rejected; await Promise.resolve(); assert.equal(stops, 2); assert.equal(owner.facade.ready, false); }); test("primitive bridge preserves all public clock/control arguments and errors", async () => { const calls = []; const methods = { open: [["one.rrd", "two.rrd"]], close: ["one.rrd"], override_panel_state: ["time", "hidden"], get_active_recording_id: [], get_active_timeline: ["recording"], get_current_time: ["recording", "session_time"], get_playing: ["recording"], get_time_range: ["recording", "session_time"], set_active_timeline: ["recording", "session_time"], set_current_time: ["recording", "session_time", 123.456], set_playing: ["recording", false], }; const native = { stop() {}, start: async () => { throw new Error("native startup failure"); } }; for (const name of Object.keys(methods)) native[name] = (...args) => { calls.push([name, args]); return 1; }; const { facade, dispose } = bridge(native); for (const [name, args] of Object.entries(methods)) facade[name](...args); assert.deepEqual(calls, Object.entries(methods)); await assert.rejects(facade.start(null, {}, null), /native startup failure/); dispose(); }); test("sync native failures become parent-owned errors and optional channel names stay undefined", () => { const nativeError = new Error("native failure"); let channelName = "unobserved"; const { facade, dispose } = bridge({ stop() {}, open() { throw nativeError; }, open_channel(name) { channelName = name; return { close() {} }; }, }); assert.throws(() => facade.open("rrd"), error => error !== nativeError && /native failure/.test(error.message)); facade.open_channel(); assert.equal(channelName, undefined); dispose(); }); test("dispose closes every auxiliary channel even if one channel fails", () => { const closed = []; let stops = 0; const owner = bridge({ stop() { stops++; }, open_channel: name => ({ close() { closed.push(name); if (name === "first") throw new Error("already gone"); } }), }, {}); const first = owner.facade.open_channel("first"); const second = owner.facade.open_channel("second"); owner.dispose(); owner.dispose(); first.close(); second.close(); assert.deepEqual(closed, ["first", "second"]); assert.equal(stops, 1); }); test("host load timeout removes the frame and clears its timer", async (t) => { const f = fixture(t); const scope = createIsolatedRerunHost(f.host); const rejected = assert.rejects(scope.ready, /timed out/); [...f.timers.values()][0](); await rejected; assert.equal(f.counts().removals, 1); assert.equal(f.timers.size, 0); }); test("blueprint termination cancels renewal and sends one bounded keepalive release", async () => { let tick, canceled = 0; const requests = []; const release = keepRecordedBlueprintSession({ endpointUrl: "/api/v1/observation-sessions/source/blueprint.rrd", origin: "http://core.test", applicationId: "nodedc_mission_core_recorded", recordingId: "recording", ownerId: "a".repeat(32), fetcher: async (url, init) => { requests.push({ url, ...init }); return {}; }, schedule(fn) { tick = fn; return 1; }, cancel() { canceled++; }, }); tick(); const renewal = requests[0]; release(); release(); tick(); assert.equal(canceled, 1); assert.equal(requests.length, 2); assert.equal(renewal.signal.aborted, true); assert.equal(requests[1].keepalive, true); assert.equal(requests[1].url, "http://core.test/api/v1/observation-sessions/source/blueprint-lifecycle"); assert.deepEqual(JSON.parse(requests[1].body), { action: "release", application_id: "nodedc_mission_core_recorded", recording_id: "recording", blueprint_session_id: "a".repeat(32), }); }); test("blueprint lease refuses a foreign endpoint", () => { assert.throws(() => keepRecordedBlueprintSession({ endpointUrl: "https://other.test/blueprint.rrd", origin: "http://core.test", applicationId: "app", recordingId: "recording", ownerId: "owner", }), /Unsafe/); });