| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; |
| 4 | import { tmpdir } from "node:os"; |
| 5 | import { join } from "node:path"; |
| 6 | import { BrowserRecoveryStore, type RecoveredTab } from "./recoveryStore.js"; |
| 7 | import { LazyGuestView } from "./lazyGuestView.js"; |
| 8 | import { FakeViewFactory } from "./fakeGuestViews.js"; |
| 9 | |
| 10 | const tab: RecoveredTab = { id: "tab-2", taskId: "task", sessionId: "session", url: "https://example.test/", title: "Page", viewport: null }; |
| 11 | test("recovery preserves future fields but never capabilities and rejects expired preview URLs", () => { |
| 12 | const directory = mkdtempSync(join(tmpdir(), "browser-recovery-")), path = join(directory, "tabs.json"); |
| 13 | try { |
| 14 | writeFileSync(path, JSON.stringify({ version: 1, future: 42, tabs: [{ ...tab, custom: true, grantId: "old", refs: [1], password: "secret" }, { ...tab, id: "tab-3", url: "http://localhost/__reasonix_workspace_media/expired" }] })); |
| 15 | const store = new BrowserRecoveryStore(path, assert.fail); |
| 16 | assert.equal(store.tabs.length, 1); |
| 17 | store.save(store.tabs); |
| 18 | const saved = JSON.parse(readFileSync(path, "utf8")); |
| 19 | assert.equal(saved.future, 42); assert.equal(saved.tabs[0].custom, true); |
| 20 | for (const key of ["grantId", "refs", "password"]) assert.equal(saved.tabs[0][key], undefined); |
| 21 | writeFileSync(path, '{"version":999,"future":"untouched"}'); |
| 22 | const future = new BrowserRecoveryStore(path, assert.fail); future.save([tab]); |
| 23 | assert.equal(readFileSync(path, "utf8"), '{"version":999,"future":"untouched"}'); |
| 24 | } finally { rmSync(directory, { recursive: true, force: true }); } |
| 25 | }); |
| 26 | test("recovered tabs allocate no page until access and share one initial load", async () => { |
| 27 | const factory = new FakeViewFactory(); |
| 28 | let creates = 0; |
| 29 | const lazy = new LazyGuestView({ create: partition => { creates++; return factory.create(partition); } }, "persist:browser", tab); |
| 30 | assert.equal(lazy.page.id, -1); assert.equal(lazy.page.getURL(), tab.url); |
| 31 | lazy.setBounds({ x: 0, y: 0, width: 800, height: 600 }); lazy.setVisible(false); |
| 32 | assert.equal(creates, 0); |
| 33 | await Promise.all([lazy.ensureLoaded(), lazy.ensureLoaded()]); |
| 34 | assert.equal(creates, 1); assert.equal(lazy.isPlaceholder(), false); |
| 35 | lazy.destroy(); |
| 36 | const unopened = new LazyGuestView(factory, "persist:browser", tab); unopened.destroy(); |
| 37 | assert.throws(() => unopened.ensureLoaded(), /closed/); |
| 38 | }); |
| 39 | |
| 40 | test("file preview recovery persists source references without expiring bearer URLs", async () => { |
| 41 | const directory = mkdtempSync(join(tmpdir(), "preview-recovery-")), path = join(directory, "tabs.json"); |
| 42 | try { |
| 43 | const reference = { source: "workspace" as const, path: "index.html", toolCallId: "" }; |
| 44 | const store = new BrowserRecoveryStore(path, assert.fail); |
| 45 | store.save([{ ...tab, url: "http://localhost/__reasonix_workspace_media/SECRET", fileReference: reference }]); |
| 46 | assert.equal(readFileSync(path, "utf8").includes("SECRET"), false); |
| 47 | const loaded = new BrowserRecoveryStore(path, assert.fail); |
| 48 | assert.deepEqual(loaded.tabs[0].fileReference, reference); |
| 49 | const lazy = new LazyGuestView(new FakeViewFactory(), "persist:browser", loaded.tabs[0]); |
| 50 | await assert.rejects(lazy.ensureLoaded(), /fresh file authorization/); |
| 51 | assert.equal(lazy.isPlaceholder(), true); |
| 52 | } finally { rmSync(directory, { recursive: true, force: true }); } |
| 53 | }); |
| 54 |