import assert from "node:assert/strict"; import test from "node:test"; import { createDynamicScoringJobStore } from "../src/lib/birth-time-dynamic-scoring-job-store.ts"; import { createDynamicTurnPersistence } from "../src/lib/birth-time-journey-dynamic-persistence.ts"; import { createDynamicScoringJobSpec } from "../src/lib/birth-time-scoring-job.ts"; import { BirthTimeJourneyStoreError, StaleJourneyTurnError, } from "../src/lib/birth-time-journey-turn-persistence.ts"; import { answerTransition } from "../src/lib/birth-time-dynamic-transitions.ts"; import type { DynamicStoredRectificationCase } from "../src/lib/birth-time-journey-service.ts"; import { actionId, dynamicCase, persistedQuestion } from "./birth-time-dynamic-persistence-fixture.ts"; function savedTurn( kind: "pause" | "finish" = "pause", ): DynamicStoredRectificationCase { const stored = dynamicCase(); return { ...stored, turnVersion: 8, processedActionIds: [actionId], dynamicTurnState: { ...stored.dynamicTurnState, turnVersion: 8, nextAction: kind === "pause" ? { kind: "paused" } : { kind: "present_low_result", resultId: null }, }, dynamicControl: { ...stored.dynamicControl, lastActionReceipt: { actionId, kind, turnVersion: 7 }, }, }; } test("normal and duplicate RPC success reload the exact receipt", async () => { const loaded = savedTurn(); const proposed = { ...loaded, turnVersion: 7 }; const persistence = createDynamicTurnPersistence({ async rpc() { return { data: 8, error: null }; }, }, async () => loaded, () => "2026-07-18"); const first = await persistence.saveDynamicTurn(proposed, 7, actionId); const duplicate = await persistence.saveDynamicTurn(proposed, 7, actionId); assert.equal(first, loaded); assert.equal(duplicate, loaded); }); test("a concurrent different action cannot use a successful RPC as replay", async () => { const proposed = { ...savedTurn(), turnVersion: 7 }; const concurrent = savedTurn("finish"); const persistence = createDynamicTurnPersistence({ async rpc() { return { data: 8, error: null }; }, }, async () => concurrent, () => "2026-07-18"); await assert.rejects( persistence.saveDynamicTurn(proposed, 7, actionId), StaleJourneyTurnError, ); }); test("a transport error returns an exact committed dynamic turn", async () => { const loaded = savedTurn(); const proposed = { ...loaded, turnVersion: 7 }; const persistence = createDynamicTurnPersistence({ async rpc() { return { data: null, error: { message: "connection reset" } }; }, }, async () => loaded, () => "2026-07-18"); const replay = await persistence.saveDynamicTurn(proposed, 7, actionId); assert.equal(replay, loaded); }); test("a transport error maps a conflicting committed receipt to stale", async () => { const proposed = { ...savedTurn(), turnVersion: 7 }; const concurrent = savedTurn("finish"); const persistence = createDynamicTurnPersistence({ async rpc() { return { data: null, error: { message: "connection reset" } }; }, }, async () => concurrent, () => "2026-07-18"); await assert.rejects( persistence.saveDynamicTurn(proposed, 7, actionId), StaleJourneyTurnError, ); }); test("a transport error remains a store error when nothing committed", async () => { const proposed = dynamicCase(); const persistence = createDynamicTurnPersistence({ async rpc() { return { data: null, error: { message: "connection reset" } }; }, }, async () => proposed, () => "2026-07-18"); await assert.rejects( persistence.saveDynamicTurn(proposed, 7, actionId), BirthTimeJourneyStoreError, ); }); function pendingTurn(): DynamicStoredRectificationCase { const stored = dynamicCase(); const option = persistedQuestion.options.find((item) => item.kind === "primary"); if (!option) throw new Error("missing primary option"); const transitioned = answerTransition({ stored, option, answeredAt: "2026-07-18T08:00:00.000Z", jobId: "85b22d7e-3adc-473d-81e1-6ad29e9b06f4", nextVersion: 8, }); return { ...transitioned, dynamicControl: { ...transitioned.dynamicControl, lastActionReceipt: { actionId, kind: "answer_choice", turnVersion: 7, questionId: persistedQuestion.questionId, optionId: option.optionId, }, }, }; } test("dynamic scoring creation accepts only exact duplicate-success receipts", async () => { const pending = pendingTurn(); const spec = createDynamicScoringJobSpec( "85b22d7e-3adc-473d-81e1-6ad29e9b06f4", pending.choiceEvidence, new Date("2026-07-18T08:00:00.000Z"), ); let loaded = { ...pending, turnVersion: 8, dynamicTurnState: { ...pending.dynamicTurnState, turnVersion: 8 }, processedActionIds: [actionId], }; const store = createDynamicScoringJobStore({ async rpc() { return { data: 8, error: null }; }, }, async () => loaded); const exact = await store.createDynamicScoringJob( pending, 7, actionId, persistedQuestion.questionId, spec, ); assert.equal(exact, loaded); loaded = { ...loaded, dynamicControl: { ...loaded.dynamicControl, lastActionReceipt: { actionId, kind: "answer_choice", turnVersion: 7, questionId: persistedQuestion.questionId, optionId: persistedQuestion.options[1].optionId, }, }, }; await assert.rejects( store.createDynamicScoringJob(pending, 7, actionId, persistedQuestion.questionId, spec), StaleJourneyTurnError, ); }); test("dynamic scoring transport errors return an exact committed receipt", async () => { const pending = pendingTurn(); const spec = createDynamicScoringJobSpec( "85b22d7e-3adc-473d-81e1-6ad29e9b06f4", pending.choiceEvidence, new Date("2026-07-18T08:00:00.000Z"), ); const loaded = { ...pending, turnVersion: 8, dynamicTurnState: { ...pending.dynamicTurnState, turnVersion: 8 }, processedActionIds: [actionId], }; const store = createDynamicScoringJobStore({ async rpc() { return { data: null, error: { message: "connection reset" } }; }, }, async () => loaded); const replay = await store.createDynamicScoringJob( pending, 7, actionId, persistedQuestion.questionId, spec, ); assert.equal(replay, loaded); });