From 75bb6a0fcd290a509ba25e9c5939dfa79bbc07c6 Mon Sep 17 00:00:00 2001 From: Vegard Stikbakke Date: Fri, 14 Aug 2026 10:50:23 +0200 Subject: [PATCH] feat(agent): add SQLite storage conformance coverage --- .../test/storage-conformance.test.ts | 59 +++++++++++++++++++ .../sqlite-node/vitest.config.ts | 4 ++ 2 files changed, 63 insertions(+) create mode 100644 packages/session-backends/sqlite-node/test/storage-conformance.test.ts diff --git a/packages/session-backends/sqlite-node/test/storage-conformance.test.ts b/packages/session-backends/sqlite-node/test/storage-conformance.test.ts new file mode 100644 index 000000000..6104a4c2b --- /dev/null +++ b/packages/session-backends/sqlite-node/test/storage-conformance.test.ts @@ -0,0 +1,59 @@ +import { + type ConformanceCase, + createStorageConformance, + type StorageFixture, +} from "@earendil-works/pi-agent-core/session/testing"; +import { describe, it } from "vitest"; +import { createNodeSqliteFactory, SQLITE_STORAGE_VERSION, SqliteStorage, sql } from "../src/index.ts"; +import { applyInitialSchema } from "../src/sqlite/migrations.ts"; + +const NOW = 1_700_000_000_000; +const EMPTY_USAGE = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, +}; + +function registerConformance(name: string, cases: readonly ConformanceCase[]): void { + describe(name, () => { + for (const group of new Set(cases.map((testCase) => testCase.group))) { + describe(group, () => { + for (const testCase of cases.filter((candidate) => candidate.group === group)) { + it(testCase.name, () => testCase.run()); + } + }); + } + }); +} + +registerConformance( + "SqliteStorage conformance", + createStorageConformance(async () => { + const db = await createNodeSqliteFactory().open(":memory:"); + try { + await applyInitialSchema(db); + sql`INSERT INTO session + (created_at, parent_session_id, storage_version, metadata, message_count, usage_payload, next_seq) + VALUES (${NOW}, ${null}, ${SQLITE_STORAGE_VERSION}, ${null}, ${0}, ${JSON.stringify(EMPTY_USAGE)}, ${1})`.run( + db, + ); + const storage = new SqliteStorage(db, { now: () => NOW }); + return { + storage, + async [Symbol.asyncDispose]() { + try { + await storage.close(); + } finally { + db.close(); + } + }, + } satisfies StorageFixture; + } catch (error) { + db.close(); + throw error; + } + }), +); diff --git a/packages/session-backends/sqlite-node/vitest.config.ts b/packages/session-backends/sqlite-node/vitest.config.ts index 8e83131e8..d8b141aae 100644 --- a/packages/session-backends/sqlite-node/vitest.config.ts +++ b/packages/session-backends/sqlite-node/vitest.config.ts @@ -5,6 +5,9 @@ const telemetryIndex = fileURLToPath(new URL("../../telemetry/src/index.ts", imp const aiIndex = fileURLToPath(new URL("../../ai/src/index.ts", import.meta.url)); const agentIndex = fileURLToPath(new URL("../../agent/src/index.ts", import.meta.url)); const agentNode = fileURLToPath(new URL("../../agent/src/node.ts", import.meta.url)); +const agentSessionTesting = fileURLToPath( + new URL("../../agent/src/harness/session/testing/index.ts", import.meta.url), +); export default defineConfig({ test: { @@ -24,6 +27,7 @@ export default defineConfig({ alias: [ { find: /^@earendil-works\/pi-telemetry$/, replacement: telemetryIndex }, { find: /^@earendil-works\/pi-agent-core\/node$/, replacement: agentNode }, + { find: /^@earendil-works\/pi-agent-core\/session\/testing$/, replacement: agentSessionTesting }, { find: /^@earendil-works\/pi-agent-core$/, replacement: agentIndex }, { find: /^@earendil-works\/pi-ai$/, replacement: aiIndex }, ],