| 1 | // Run: tsx src/__tests__/remote-session-cache.test.ts |
| 2 | |
| 3 | import { JSDOM } from "jsdom"; |
| 4 | import { |
| 5 | loadRemoteSessionCache, |
| 6 | REMOTE_SESSION_CACHE_TTL_MS, |
| 7 | removeRemoteSessionCache, |
| 8 | saveRemoteSessionCache, |
| 9 | } from "../lib/remoteSessionCache"; |
| 10 | import type { RemoteSessionView } from "../lib/types"; |
| 11 | |
| 12 | let passed = 0; |
| 13 | function ok(value: boolean, label: string): void { |
| 14 | if (!value) throw new Error(label); |
| 15 | passed += 1; |
| 16 | process.stdout.write(` PASS ${label}\n`); |
| 17 | } |
| 18 | |
| 19 | const dom = new JSDOM("<!doctype html>", { url: "http://localhost/" }); |
| 20 | globalThis.localStorage = dom.window.localStorage; |
| 21 | |
| 22 | const key = "box\u0000~/app"; |
| 23 | const rows: RemoteSessionView[] = [{ name: "one", path: "/sessions/one.jsonl", title: "One", turns: 2 }]; |
| 24 | const now = 10_000; |
| 25 | |
| 26 | saveRemoteSessionCache(key, rows, now); |
| 27 | ok(loadRemoteSessionCache(key, now + 1)[0]?.name === "one", "restores a current versioned snapshot"); |
| 28 | |
| 29 | saveRemoteSessionCache(key, [ |
| 30 | { name: "", path: "/sessions/transient-blank.jsonl", title: "", turns: 0, current: true }, |
| 31 | ...rows, |
| 32 | ], now); |
| 33 | const withoutBlank = loadRemoteSessionCache(key, now + 1); |
| 34 | ok(withoutBlank.length === 1 && withoutBlank[0]?.name === "one", |
| 35 | "does not persist a transient blank session with a generated path"); |
| 36 | |
| 37 | ok(loadRemoteSessionCache(key, now + REMOTE_SESSION_CACHE_TTL_MS + 1).length === 0, "expires stale snapshots"); |
| 38 | ok(localStorage.length === 0, "removes an expired snapshot from storage"); |
| 39 | |
| 40 | localStorage.setItem("projectTree:remoteSessions:" + key, JSON.stringify(rows)); |
| 41 | ok(loadRemoteSessionCache(key, now).length === 0, "invalidates the legacy unversioned array schema"); |
| 42 | ok(localStorage.length === 0, "removes an invalid legacy snapshot"); |
| 43 | |
| 44 | localStorage.setItem("projectTree:remoteSessions:" + key, JSON.stringify({ |
| 45 | version: 1, |
| 46 | savedAt: now, |
| 47 | rows: [{ name: "bad", title: "Bad", turns: 1, running: "yes" }], |
| 48 | })); |
| 49 | ok(loadRemoteSessionCache(key, now).length === 0, "rejects malformed optional row fields"); |
| 50 | ok(localStorage.length === 0, "removes a malformed versioned snapshot"); |
| 51 | |
| 52 | localStorage.setItem("projectTree:remoteSessions:" + key, JSON.stringify({ |
| 53 | version: 1, |
| 54 | savedAt: now, |
| 55 | rows: [{ name: "empty" }], |
| 56 | })); |
| 57 | const empty = loadRemoteSessionCache(key, now); |
| 58 | ok(empty.length === 1 && empty[0]?.title === "" && empty[0]?.turns === 0, |
| 59 | "normalizes Go-omitted zero-value title and turns"); |
| 60 | |
| 61 | saveRemoteSessionCache(key, rows, now); |
| 62 | removeRemoteSessionCache(key); |
| 63 | ok(loadRemoteSessionCache(key, now).length === 0, "explicit removal clears an unpinned project cache"); |
| 64 | |
| 65 | console.log(`\n${passed} remote session cache assertions passed.`); |
| 66 |