/** * Unit coverage for the gateway's sample-restore + dirty-hook plumbing * that backs the Postgres persistence layer (Task #130). The actual * upsert is exercised by the route integration tests; here we just * verify the in-memory contract the persistence module relies on. * * Run with: pnpm --filter @workspace/api-server test:sample-persistence */ import { test } from "node:test"; import assert from "node:assert/strict"; process.env.DATABASE_URL ??= "postgres://test:test@127.0.0.1:5432/test"; const gateway = await import("../gateway"); const ADAPTER = "anthropic"; const SOURCE = "replit-integration"; // Default for anthropic@replit-integration in ADAPTER_DEFAULTS. const TIMEOUT_MS = 90_000; function snapshotFor(adapter: string, source: string) { return gateway .circuitSnapshot() .find((e) => e.adapter === adapter && e.source === source); } test("restoreRecentSamples seeds the in-memory window so the sparkline is populated immediately after a restart", () => { gateway._resetAllCircuitsForTesting(); const now = Date.now(); const samples = Array.from({ length: 12 }, (_, i) => ({ ms: 200 + i * 10, at: now - (12 - i) * 1000, })); gateway.restoreRecentSamples(ADAPTER, SOURCE, samples); const snap = snapshotFor(ADAPTER, SOURCE); assert.ok(snap, "provider state should exist after restore"); assert.equal(snap!.recentCallCount, 12); assert.equal(snap!.recentSamples.length, 12); // Restored samples must keep chronological order. for (let i = 1; i < snap!.recentSamples.length; i++) { assert.ok(snap!.recentSamples[i]!.at >= snap!.recentSamples[i - 1]!.at); } // Persistence cap mirrors the in-memory RECENT_DURATION_LIMIT (50). const big = Array.from({ length: 200 }, (_, i) => ({ ms: i, at: now + i })); gateway.restoreRecentSamples(ADAPTER, SOURCE, big); const snap2 = snapshotFor(ADAPTER, SOURCE); assert.equal(snap2!.recentSamples.length, 50); }); test("restoreRecentSamples re-arms the near-ceiling latch so latency-alert evaluation survives a restart", () => { gateway._resetAllCircuitsForTesting(); const now = Date.now(); const samples = Array.from( { length: gateway.LATENCY_ALERT_MIN_SAMPLES + 2 }, (_, i) => ({ ms: TIMEOUT_MS, at: now - (10 - i) * 1000 }), ); gateway.restoreRecentSamples(ADAPTER, SOURCE, samples); const snap = snapshotFor(ADAPTER, SOURCE); assert.ok( (snap?.p95NearCeilingSinceMs ?? 0) > 0, "p95 should be flagged near-ceiling once enough hot samples are restored", ); }); test("dirty hook fires on every new sample so the persistence flush picks it up", () => { gateway._resetAllCircuitsForTesting(); const dirty: string[] = []; gateway._setSampleDirtyHook((adapter, source) => { dirty.push(`${adapter}@${source}`); }); try { gateway._recordDurationForTesting(ADAPTER, SOURCE, 123); gateway._recordDurationForTesting(ADAPTER, SOURCE, 456); gateway._recordDurationForTesting("openai", "byok-direct", 789); assert.deepEqual(dirty, [ `${ADAPTER}@${SOURCE}`, `${ADAPTER}@${SOURCE}`, "openai@byok-direct", ]); } finally { gateway._setSampleDirtyHook(null); } }); test("getRecentSamplesFor returns the live window the persistence layer flushes", () => { gateway._resetAllCircuitsForTesting(); gateway._recordDurationForTesting(ADAPTER, SOURCE, 111); gateway._recordDurationForTesting(ADAPTER, SOURCE, 222); const samples = gateway.getRecentSamplesFor(ADAPTER, SOURCE); assert.equal(samples.length, 2); assert.equal(samples[0]!.ms, 111); assert.equal(samples[1]!.ms, 222); assert.deepEqual(gateway.getRecentSamplesFor("nope", "nope"), []); });