{"version":3,"file":"rpc-memory-snapshot.test.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-memory-snapshot.test.ts"],"names":[],"mappings":"","sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport type { MemoryHistorySnapshot } from \"../../core/memory.js\";\nimport { SESSION_MEMORY_CUSTOM_TYPE } from \"../../core/memory.js\";\nimport { SessionManager } from \"../../core/session-manager.js\";\nimport { resolveSnapshotSelector } from \"../../core/snapshot-selector-resolver.js\";\nimport { serializeJsonLine } from \"./jsonl.js\";\nimport { buildRpcMemoryCompareData, buildRpcMemoryHistoryData } from \"./rpc-memory.js\";\n\nfunction makeSnapshot(\n\tentryId: string,\n\titems: Array<{ key: string; value: string }>,\n\trecordedAt = \"2026-04-01T12:00:00.000Z\",\n\tisCurrent = false,\n\tparentId: string | null = null,\n): MemoryHistorySnapshot {\n\treturn {\n\t\tentryId,\n\t\tparentId,\n\t\trecordedAt,\n\t\titems: items.map((item) => ({ ...item, timestamp: recordedAt })),\n\t\tisCurrent,\n\t};\n}\n\nfunction makeContext(snapshots: readonly MemoryHistorySnapshot[]) {\n\treturn {\n\t\tgetMemoryHistory: () => snapshots,\n\t\tresolveMemorySnapshotSelector: (input: string) => resolveSnapshotSelector(input, snapshots),\n\t};\n}\n\ndescribe(\"buildRpcMemoryHistoryData\", () => {\n\tit(\"returns current-branch snapshot history with explicit snapshot framing\", () => {\n\t\tconst snapshots = [\n\t\t\tmakeSnapshot(\"baseline01-1111\", [{ key: \"alpha\", value: \"first\" }], \"2026-04-01T10:00:00.000Z\"),\n\t\t\tmakeSnapshot(\n\t\t\t\t\"target001-2222\",\n\t\t\t\t[{ key: \"beta\", value: \"second\" }],\n\t\t\t\t\"2026-04-01T12:00:00.000Z\",\n\t\t\t\ttrue,\n\t\t\t\t\"baseline01-1111\",\n\t\t\t),\n\t\t];\n\n\t\tconst data = buildRpcMemoryHistoryData(makeContext(snapshots));\n\n\t\texpect(data.branchScope).toBe(\"current\");\n\t\texpect(data.historyModel).toBe(\"snapshot\");\n\t\texpect(data.snapshots).toHaveLength(2);\n\t\texpect(data.snapshots[0]).toMatchObject({\n\t\t\tentryId: \"baseline01-1111\",\n\t\t\tshortId: \"baseline\",\n\t\t\titemCount: 1,\n\t\t\tisCurrent: false,\n\t\t});\n\t\texpect(data.snapshots[1]).toMatchObject({\n\t\t\tentryId: \"target001-2222\",\n\t\t\tshortId: \"target00\",\n\t\t\tparentId: \"baseline01-1111\",\n\t\t\titemCount: 1,\n\t\t\tisCurrent: true,\n\t\t});\n\t});\n});\n\ndescribe(\"buildRpcMemoryCompareData\", () => {\n\tit(\"returns empty_history when no snapshots exist\", () => {\n\t\tconst data = buildRpcMemoryCompareData(makeContext([]));\n\t\texpect(data).toEqual({\n\t\t\tbranchScope: \"current\",\n\t\t\thistoryModel: \"snapshot\",\n\t\t\tstatus: \"empty_history\",\n\t\t\tsnapshotCount: 0,\n\t\t});\n\t});\n\n\tit(\"returns initial_snapshot honestly for adjacent comparison with one snapshot\", () => {\n\t\tconst data = buildRpcMemoryCompareData(\n\t\t\tmakeContext([\n\t\t\t\tmakeSnapshot(\"baseline01-1111\", [{ key: \"alpha\", value: \"first\" }], \"2026-04-01T10:00:00.000Z\", true),\n\t\t\t]),\n\t\t);\n\n\t\texpect(data.status).toBe(\"initial_snapshot\");\n\t\tif (data.status === \"initial_snapshot\") {\n\t\t\texpect(data.compareMode).toBe(\"adjacent\");\n\t\t\texpect(data.target.shortId).toBe(\"baseline\");\n\t\t\texpect(data.diff.isInitialSnapshot).toBe(true);\n\t\t\texpect(data.diff.added).toEqual([{ type: \"added\", key: \"alpha\", value: \"first\" }]);\n\t\t}\n\t});\n\n\tit(\"returns adjacent comparison against latest and previous snapshots\", () => {\n\t\tconst snapshots = [\n\t\t\tmakeSnapshot(\"baseline01-1111\", [{ key: \"alpha\", value: \"first\" }], \"2026-04-01T10:00:00.000Z\"),\n\t\t\tmakeSnapshot(\n\t\t\t\t\"target001-2222\",\n\t\t\t\t[\n\t\t\t\t\t{ key: \"alpha\", value: \"updated\" },\n\t\t\t\t\t{ key: \"beta\", value: \"second\" },\n\t\t\t\t],\n\t\t\t\t\"2026-04-01T12:00:00.000Z\",\n\t\t\t\ttrue,\n\t\t\t\t\"baseline01-1111\",\n\t\t\t),\n\t\t];\n\n\t\tconst data = buildRpcMemoryCompareData(makeContext(snapshots));\n\t\texpect(data.status).toBe(\"ok\");\n\t\tif (data.status === \"ok\") {\n\t\t\texpect(data.compareMode).toBe(\"adjacent\");\n\t\t\texpect(data.sameSnapshot).toBe(false);\n\t\t\texpect(data.baseline.shortId).toBe(\"baseline\");\n\t\t\texpect(data.target.shortId).toBe(\"target00\");\n\t\t\texpect(data.diff.added).toEqual([{ type: \"added\", key: \"beta\", value: \"second\" }]);\n\t\t\texpect(data.diff.changed).toEqual([\n\t\t\t\t{ type: \"changed\", key: \"alpha\", previousValue: \"first\", currentValue: \"updated\" },\n\t\t\t]);\n\t\t}\n\t});\n\n\tit(\"reuses selector resolution and returns resolved selectors on explicit success\", () => {\n\t\tconst snapshots = [\n\t\t\tmakeSnapshot(\"baseline01-1111\", [{ key: \"alpha\", value: \"first\" }], \"2026-04-01T10:00:00.000Z\"),\n\t\t\tmakeSnapshot(\"target001-2222\", [{ key: \"alpha\", value: \"second\" }], \"2026-04-01T12:00:00.000Z\", true),\n\t\t];\n\n\t\tconst data = buildRpcMemoryCompareData(makeContext(snapshots), {\n\t\t\tbaseline: \"[baseline]\",\n\t\t\ttarget: \"target00\",\n\t\t});\n\n\t\texpect(data.status).toBe(\"ok\");\n\t\tif (data.status === \"ok\") {\n\t\t\texpect(data.compareMode).toBe(\"explicit\");\n\t\t\texpect(data.selectors).toEqual({\n\t\t\t\tbaseline: {\n\t\t\t\t\tinput: \"[baseline]\",\n\t\t\t\t\tmatchedInput: \"baseline\",\n\t\t\t\t\tresolvedId: \"baseline01-1111\",\n\t\t\t\t},\n\t\t\t\ttarget: {\n\t\t\t\t\tinput: \"target00\",\n\t\t\t\t\tmatchedInput: \"target00\",\n\t\t\t\t\tresolvedId: \"target001-2222\",\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t});\n\n\tit(\"returns selector_resolution_failed with explicit issue structure\", () => {\n\t\tconst snapshots = [\n\t\t\tmakeSnapshot(\"abc00001-1111\", [{ key: \"a\", value: \"1\" }]),\n\t\t\tmakeSnapshot(\"abc00002-2222\", [{ key: \"b\", value: \"2\" }], \"2026-04-01T12:00:00.000Z\", true),\n\t\t];\n\n\t\tconst data = buildRpcMemoryCompareData(makeContext(snapshots), {\n\t\t\tbaseline: \"[]\",\n\t\t\ttarget: \"abc\",\n\t\t});\n\n\t\texpect(data.status).toBe(\"selector_resolution_failed\");\n\t\tif (data.status === \"selector_resolution_failed\") {\n\t\t\texpect(data.compareMode).toBe(\"explicit\");\n\t\t\texpect(data.issues).toEqual([\n\t\t\t\t{\n\t\t\t\t\tlabel: \"baseline\",\n\t\t\t\t\tinput: \"[]\",\n\t\t\t\t\tmatchedInput: \"\",\n\t\t\t\t\terror: \"empty\",\n\t\t\t\t\tcandidates: [],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: \"target\",\n\t\t\t\t\tinput: \"abc\",\n\t\t\t\t\tmatchedInput: \"abc\",\n\t\t\t\t\terror: \"ambiguous\",\n\t\t\t\t\tcandidates: [\n\t\t\t\t\t\t{ entryId: \"abc00001-1111\", shortId: \"abc00001\" },\n\t\t\t\t\t\t{ entryId: \"abc00002-2222\", shortId: \"abc00002\" },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t]);\n\t\t}\n\t});\n\n\tit(\"handles same-snapshot explicit comparison honestly\", () => {\n\t\tconst snapshots = [\n\t\t\tmakeSnapshot(\"baseline01-1111\", [{ key: \"alpha\", value: \"first\" }], \"2026-04-01T10:00:00.000Z\", true),\n\t\t];\n\n\t\tconst data = buildRpcMemoryCompareData(makeContext(snapshots), {\n\t\t\tbaseline: \"baseline01-1111\",\n\t\t\ttarget: \"[baseline]\",\n\t\t});\n\n\t\texpect(data.status).toBe(\"ok\");\n\t\tif (data.status === \"ok\") {\n\t\t\texpect(data.sameSnapshot).toBe(true);\n\t\t\texpect(data.diff.added).toEqual([]);\n\t\t\texpect(data.diff.removed).toEqual([]);\n\t\t\texpect(data.diff.changed).toEqual([]);\n\t\t}\n\t});\n\n\tit(\"stays current-branch-only when backed by SessionManager\", () => {\n\t\tconst session = SessionManager.inMemory(\"/tmp/project\");\n\t\tconst shared = session.appendCustomEntry(SESSION_MEMORY_CUSTOM_TYPE, [\n\t\t\t{ key: \"shared\", value: \"root\", timestamp: new Date().toISOString() },\n\t\t]);\n\t\tconst siblingOnly = session.appendCustomEntry(SESSION_MEMORY_CUSTOM_TYPE, [\n\t\t\t{ key: \"sibling\", value: \"main\", timestamp: new Date().toISOString() },\n\t\t]);\n\n\t\tsession.branch(shared);\n\t\tsession.appendCustomEntry(SESSION_MEMORY_CUSTOM_TYPE, [\n\t\t\t{ key: \"branch\", value: \"active\", timestamp: new Date().toISOString() },\n\t\t]);\n\n\t\tconst history = buildRpcMemoryHistoryData({ getMemoryHistory: () => session.getMemoryHistory() });\n\t\texpect(history.snapshots).toHaveLength(2);\n\t\texpect(history.snapshots.map((snapshot) => snapshot.entryId)).not.toContain(siblingOnly);\n\n\t\tconst compare = buildRpcMemoryCompareData(\n\t\t\t{\n\t\t\t\tgetMemoryHistory: () => session.getMemoryHistory(),\n\t\t\t\tresolveMemorySnapshotSelector: (input: string) => session.resolveMemorySnapshotSelector(input),\n\t\t\t},\n\t\t\t{ baseline: siblingOnly, target: history.snapshots[1]!.entryId },\n\t\t);\n\t\texpect(compare.status).toBe(\"selector_resolution_failed\");\n\t});\n});\n\ndescribe(\"RPC memory payloads\", () => {\n\tit(\"remain JSON-serializable\", () => {\n\t\tconst payload = buildRpcMemoryCompareData(\n\t\t\tmakeContext([\n\t\t\t\tmakeSnapshot(\"baseline01-1111\", [{ key: \"alpha\", value: \"first\" }]),\n\t\t\t\tmakeSnapshot(\"target001-2222\", [{ key: \"alpha\", value: \"second\" }], \"2026-04-01T12:00:00.000Z\", true),\n\t\t\t]),\n\t\t\t{ baseline: \"baseline01-1111\", target: \"target001-2222\" },\n\t\t);\n\n\t\tconst serialized = serializeJsonLine(payload);\n\t\texpect(JSON.parse(serialized)).toEqual(payload);\n\t});\n});\n"]}