// SPEC: PJ2026-0106050514 Workbench实时运行面 draft-2026-06-30-p0-1297-spec-first. // Responsibility: Regression tests for pure Workbench realtime runtime helpers before Vue/store integration. import assert from "node:assert/strict"; import test from "node:test"; import { AsyncQueue, work } from "../src/utils/scheduler/async-queue.ts"; import { createCoalescedEventQueue } from "../src/utils/scheduler/coalesced-event-queue.ts"; import { createKeyedSingleflight } from "../src/utils/scheduler/keyed-singleflight.ts"; import { createScopedCache } from "../src/utils/scoped-cache.ts"; import { composeWorkbenchScopedKey, splitWorkbenchScopedKey, workbenchPathKey, workbenchRealtimeScopeKey } from "../src/utils/workbench-key.ts"; import { createSafeStorageRuntime, isStorageQuotaError, migrateLegacyStorage, normalizePersistedValue, readJsonStorage, removePersistedTarget, removeStorageKey, writeJsonStorage, type StorageLike } from "../src/utils/safe-storage.ts"; import { checkWorkbenchHealth, createWorkbenchHealthProbeCache } from "../src/utils/workbench-health.ts"; import { buildWorkbenchTimelineRows, normalizeWorkbenchTimelineMessages, workbenchTimelineSignature } from "../src/stores/workbench-timeline-model.ts"; import { trimWorkbenchSessionCache } from "../src/stores/workbench-session-cache.ts"; test("Workbench scoped keys encode delimiter characters", () => { assert.deepEqual(splitWorkbenchScopedKey(workbenchRealtimeScopeKey("ses|one", "trc/two")), ["workbench.realtime", "ses|one", "trc/two"]); assert.deepEqual(splitWorkbenchScopedKey(composeWorkbenchScopedKey("queue", null, "", "a b")), ["queue", "~", "~", "a b"]); assert.throws(() => composeWorkbenchScopedKey("queue", "bad\0part"), /null bytes/u); assert.equal(workbenchPathKey("C:\\Users\\foo\\"), "C:/Users/foo"); }); test("scoped cache keeps OpenCode LRU and TTL semantics", () => { const disposed: string[] = []; let clock = 0; let count = 0; const cache = createScopedCache((key) => ({ key, count: ++count }), { maxEntries: 2, ttlMs: 10, now: () => clock, dispose: (value) => disposed.push(`${value.key}:${value.count}`) }); assert.equal(cache.get("a").count, 1); assert.equal(cache.get("b").count, 2); cache.get("a"); assert.equal(cache.get("c").count, 3); assert.equal(cache.peek("b"), undefined); assert.deepEqual(disposed, ["b:2"]); clock = 11; assert.equal(cache.peek("a"), undefined); assert.deepEqual(disposed, ["b:2", "a:1", "c:3"]); }); test("async queue and bounded work preserve OpenCode utility behavior", async () => { const queue = new AsyncQueue(); const first = queue.next(); queue.push(1); assert.equal(await first, 1); queue.push(2); assert.equal(await queue.next(), 2); const visited: number[] = []; await work(2, [1, 2, 3], async (item) => { visited.push(item); }); assert.deepEqual(visited.sort(), [1, 2, 3]); }); test("coalesced event queue replaces keyed snapshots and preserves unkeyed events", () => { const flushed: string[][] = []; const queue = createCoalescedEventQueue<{ key?: string; value: string }>({ keyOf: (item) => item.key, schedule: () => undefined, onFlush: (items) => flushed.push(items.map((item) => item.value)) }); queue.push({ key: "snapshot:trc", value: "old" }); queue.push({ value: "event-1" }); queue.push({ key: "snapshot:trc", value: "new" }); queue.push({ value: "event-2" }); assert.equal(queue.size, 3); queue.flush(); assert.deepEqual(flushed, [["new", "event-1", "event-2"]]); }); test("keyed single-flight coalesces duplicate work and supports replacement", async () => { const flight = createKeyedSingleflight(); let calls = 0; const first = flight.run("sessions", () => { calls += 1; return 7; }); const second = flight.run("sessions", () => { calls += 1; return 9; }); assert.equal(await first, 7); assert.equal(await second, 7); assert.equal(calls, 1); assert.equal(await flight.run("sessions", () => 11, { replace: true, reason: "manual-refresh" }), 11); assert.equal(calls, 1); }); test("safe storage reports failures instead of throwing", () => { const storage = new MemoryStorage(); assert.equal(writeJsonStorage(storage, "k", { a: 1 }).ok, true); assert.deepEqual(readJsonStorage(storage, "k", { a: 0 }).value, { a: 1 }); assert.equal(removeStorageKey(storage, "k").ok, true); const broken: StorageLike = { getItem: () => "{not-json", setItem: () => { throw new Error("quota"); }, removeItem: () => { throw new Error("security"); } }; assert.equal(readJsonStorage(broken, "bad", { safe: true }).status, "parse_error"); assert.equal(writeJsonStorage(broken, "bad", { safe: true }).status, "write_error"); assert.equal(removeStorageKey(broken, "bad").status, "remove_error"); }); test("safe storage preserves OpenCode quota fallback, prefix isolation and legacy migration", () => { const storage = new MemoryStorage(); const runtime = createSafeStorageRuntime({ cacheMaxEntries: 3, cacheMaxBytes: 200, evictPrefix: "hwlab." }); const bad = runtime.localStorageWithPrefix("hwlab.throw.scope", storage); bad.setItem("value", '{"value":1}'); const before = storage.calls.set; bad.setItem("value", '{"value":2}'); assert.equal(storage.calls.set, before); const safe = runtime.localStorageWithPrefix("hwlab.safe.scope", storage); safe.setItem("value", '{"value":3}'); assert.equal(storage.getItem("hwlab.safe.scope:value"), '{"value":3}'); assert.equal(isStorageQuotaError(new DOMException("quota", "QuotaExceededError")), true); assert.equal(normalizePersistedValue({ value: 1, nested: { a: true } }, '{"nested":{"b":2}}'), '{"value":1,"nested":{"a":true,"b":2}}'); assert.equal(normalizePersistedValue({ value: "ok" }, '{"value":"\\x"}'), undefined); storage.setItem("legacy.workspace", '{"value":2}'); const current = runtime.localStorageWithPrefix("hwlab.current", storage); const migrated = migrateLegacyStorage({ current, legacyStore: runtime.localStorageDirect(storage), stores: [], keys: ["legacy.workspace"], key: "workspace:demo", defaults: { value: 1 } }); assert.equal(migrated, '{"value":2}'); assert.equal(storage.getItem("hwlab.current:workspace:demo"), '{"value":2}'); assert.equal(storage.getItem("legacy.workspace"), null); storage.setItem("hwlab.current:workspace:demo", '{"value":2}'); storage.setItem("hwlab.legacy:workspace:demo", '{"value":3}'); removePersistedTarget({ storage: "hwlab.current", legacyStorageNames: ["hwlab.legacy"], key: "workspace:demo" }, runtime, storage); assert.equal(storage.getItem("hwlab.current:workspace:demo"), null); assert.equal(storage.getItem("hwlab.legacy:workspace:demo"), null); }); test("timeline model deduplicates authoritative messages with stable row keys", () => { const rows = buildWorkbenchTimelineRows([ { id: "msg_user", messageId: "msg_user", role: "user", title: "用户", text: "hi", status: "sent", createdAt: "2026-06-30T00:00:00.000Z", sessionId: "ses_a", traceId: "trc_a" }, { id: "msg_agent_old", messageId: "msg_agent", role: "agent", title: "Code Agent", text: "", status: "running", createdAt: "2026-06-30T00:00:01.000Z", sessionId: "ses_a", traceId: "trc_a" }, { id: "msg_agent_new", messageId: "msg_agent", role: "agent", title: "Code Agent", text: "done", status: "completed", createdAt: "2026-06-30T00:00:01.000Z", sessionId: "ses_a", traceId: "trc_a" } ]); assert.equal(rows.length, 2); assert.deepEqual(rows.map((row) => `${row.role}:${row.message.text}`), ["user:hi", "agent:done"]); assert.match(workbenchTimelineSignature(rows), /completed:4/u); assert.deepEqual(normalizeWorkbenchTimelineMessages(rows.map((row) => row.message)).map((message) => message.messageId), ["msg_user", "msg_agent"]); }); test("session cache trim keeps retained sessions and trims messages by explicit policy", () => { const result = trimWorkbenchSessionCache({ sessionOrder: ["ses_1", "ses_2", "ses_3"], sessionsById: { ses_1: { sessionId: "ses_1" }, ses_2: { sessionId: "ses_2" }, ses_3: { sessionId: "ses_3" } }, messagesBySessionId: { ses_1: [{ id: "m1", role: "user", title: "用户", text: "1", status: "sent", createdAt: "2026-06-30T00:00:00.000Z" }], ses_2: [ { id: "m2", role: "user", title: "用户", text: "2", status: "sent", createdAt: "2026-06-30T00:00:00.000Z" }, { id: "m3", role: "agent", title: "Code Agent", text: "3", status: "completed", createdAt: "2026-06-30T00:00:01.000Z" } ], ses_3: [{ id: "m4", role: "user", title: "用户", text: "4", status: "sent", createdAt: "2026-06-30T00:00:00.000Z" }] } }, { retainSessionIds: ["ses_3"], maxSessions: 1, maxMessagesPerSession: 1 }); assert.deepEqual(result.state.sessionOrder, ["ses_1", "ses_3"]); assert.deepEqual(result.evictedSessionIds, ["ses_2"]); assert.deepEqual(Object.keys(result.state.sessionsById).sort(), ["ses_1", "ses_3"]); }); test("health probe cache records ok and unavailable states", async () => { const cache = createWorkbenchHealthProbeCache({ cacheMs: 100 }); const ok = await cache.probe({ key: "workbench", fetcher: async () => ({ ready: true }), classify: (value) => value.ready ? "ok" : "degraded" }); assert.equal(ok.state, "ok"); assert.deepEqual(cache.get("workbench")?.value, { ready: true }); cache.clear("workbench"); const failed = await cache.probe({ key: "workbench", fetcher: async () => { throw new Error("offline"); } }); assert.equal(failed.state, "unavailable"); assert.ok(failed.error instanceof Error); }); test("health probe retries transient failures with caller supplied policy", async () => { let calls = 0; const result = await checkWorkbenchHealth({ key: "workbench", retryCount: 2, retryDelayMs: 1, fetcher: async () => { calls += 1; if (calls < 3) throw new TypeError("network"); return { ready: true, version: "v" }; }, classify: (value) => value.ready ? "ok" : "degraded" }); assert.equal(calls, 3); assert.equal(result.state, "ok"); assert.deepEqual(result.value, { ready: true, version: "v" }); }); class MemoryStorage implements StorageLike { private readonly values = new Map(); readonly calls = { get: 0, set: 0, remove: 0 }; get length(): number { return this.values.size; } key(index: number): string | null { return Array.from(this.values.keys())[index] ?? null; } getItem(key: string): string | null { this.calls.get += 1; if (key.startsWith("hwlab.throw")) throw new Error("storage get failed"); return this.values.get(key) ?? null; } setItem(key: string, value: string): void { this.calls.set += 1; if (key.startsWith("hwlab.quota")) throw new DOMException("quota", "QuotaExceededError"); if (key.startsWith("hwlab.throw")) throw new Error("storage set failed"); this.values.set(key, value); } removeItem(key: string): void { this.calls.remove += 1; if (key.startsWith("hwlab.throw")) throw new Error("storage remove failed"); this.values.delete(key); } }