Files
Jyotisha/frontend/tests/birth-time-dynamic-scoring-persistence.test.ts
T
2026-07-19 07:50:23 +08:00

206 lines
7.1 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { createDynamicTurnPersistence } from "../src/lib/birth-time-journey-dynamic-persistence.ts";
import {
BirthTimeJourneyStoreError,
StaleJourneyTurnError,
} from "../src/lib/birth-time-journey-turn-persistence.ts";
import type { DynamicStoredRectificationCase } from "../src/lib/birth-time-journey-service.ts";
import {
caseId,
dynamicCase,
ownerId,
} from "./birth-time-dynamic-persistence-fixture.ts";
import { lowCandidate } from "./birth-time-journey-test-support.ts";
const jobId = "8c9d09e8-91b6-4335-b891-122f205a050c";
const fingerprint = "evidence-fingerprint";
const algorithmVersion = "birth-time-event-scoring-v1";
function scoringCase(
action: "complete" | "fail",
): DynamicStoredRectificationCase {
const stored = dynamicCase();
return {
...stored,
candidateResult: action === "complete" ? lowCandidate : null,
dynamicTurnState: {
...stored.dynamicTurnState,
nextAction: action === "complete"
? { kind: "present_low_result", resultId: lowCandidate.resultId }
: { kind: "retry_scoring", jobId },
progress: { ...stored.dynamicTurnState.progress, phase: action === "complete" ? "result" : "scoring" },
},
};
}
function successfulRpc(target: DynamicStoredRectificationCase) {
let stored = dynamicCase();
let committed = false;
const calls: { readonly name: string; readonly args: Readonly<Record<string, unknown>> }[] = [];
const persistence = createDynamicTurnPersistence({
async rpc(name, args) {
calls.push({ name, args });
if (!committed) {
stored = {
...target,
turnVersion: 8,
dynamicTurnState: { ...target.dynamicTurnState, turnVersion: 8 },
};
committed = true;
}
return { data: 8, error: null };
},
}, async () => stored, () => "2026-07-18");
return { calls, persistence };
}
function assertNoPublicLeak(args: Readonly<Record<string, unknown>>) {
const publicPayload = JSON.stringify({
turn: args.p_public_turn_state,
snapshot: args.p_snapshot,
candidate: args.p_candidate_result,
});
for (const forbidden of [
"partitionId",
"candidateScores",
"agentContext",
"active_birth_time",
"birth_time",
"activeBirthTime",
"birthTime",
]) assert.equal(publicPayload.includes(forbidden), false);
}
test("dynamic scoring completion calls the exact RPC and replays the stored turn", async () => {
const value = scoringCase("complete");
const fake = successfulRpc(value);
const command = { expectedVersion: 7, jobId, evidenceFingerprint: fingerprint, algorithmVersion };
const first = await fake.persistence.completeDynamicScoringJob(value, command);
const replay = await fake.persistence.completeDynamicScoringJob(value, command);
assert.equal(first.turnVersion, 8);
assert.equal(replay, first);
assert.deepEqual(fake.calls.map((call) => call.name), [
"complete_birth_time_dynamic_scoring_job",
"complete_birth_time_dynamic_scoring_job",
]);
assert.deepEqual(fake.calls[0]?.args, {
p_user_id: ownerId,
p_case_id: caseId,
p_job_id: jobId,
p_expected_version: 7,
p_evidence_fingerprint: fingerprint,
p_algorithm_version: algorithmVersion,
p_public_turn_state: { ...value.dynamicTurnState, turnVersion: 8 },
p_snapshot: value.snapshot,
p_candidate_result: lowCandidate,
p_private_state: {
candidateModel: value.candidateModel,
currentChoiceQuestion: value.currentChoiceQuestion,
choiceAnswers: value.choiceAnswers,
choiceEvidence: value.choiceEvidence,
dynamicControl: value.dynamicControl,
agentContext: value.agentContext,
},
});
assertNoPublicLeak(fake.calls[0]?.args ?? {});
});
test("dynamic scoring failure calls the exact RPC and replays the stored turn", async () => {
const value = scoringCase("fail");
const fake = successfulRpc(value);
const command = {
expectedVersion: 7,
jobId,
evidenceFingerprint: fingerprint,
algorithmVersion,
failureCode: "engine_unavailable",
};
const first = await fake.persistence.failDynamicScoringJob(value, command);
const replay = await fake.persistence.failDynamicScoringJob(value, command);
assert.equal(first.turnVersion, 8);
assert.equal(replay, first);
assert.deepEqual(fake.calls.map((call) => call.name), [
"fail_birth_time_dynamic_scoring_job",
"fail_birth_time_dynamic_scoring_job",
]);
assert.deepEqual(fake.calls[0], {
name: "fail_birth_time_dynamic_scoring_job",
args: {
p_user_id: ownerId,
p_case_id: caseId,
p_job_id: jobId,
p_expected_version: 7,
p_evidence_fingerprint: fingerprint,
p_algorithm_version: algorithmVersion,
p_failure_code: "engine_unavailable",
p_public_turn_state: { ...value.dynamicTurnState, turnVersion: 8 },
p_private_state: {
candidateModel: value.candidateModel,
currentChoiceQuestion: value.currentChoiceQuestion,
choiceAnswers: value.choiceAnswers,
choiceEvidence: value.choiceEvidence,
dynamicControl: value.dynamicControl,
agentContext: value.agentContext,
},
},
});
assertNoPublicLeak(fake.calls[0]?.args ?? {});
});
test("dynamic scoring persistence rejects malformed versions and maps RPC errors", async () => {
const value = scoringCase("fail");
let currentVersion = 9;
let completionCalls = 0;
const persistence = createDynamicTurnPersistence({
async rpc(name) {
if (name === "complete_birth_time_dynamic_scoring_job") {
completionCalls += 1;
return { data: completionCalls === 1 ? "8" : 9, error: null };
}
if (name === "fail_birth_time_dynamic_scoring_job" && currentVersion === 9) {
return { data: null, error: { message: "stale_birth_time_dynamic_scoring_job" } };
}
return { data: null, error: { message: "database unavailable" } };
},
}, async () => ({
...dynamicCase(),
turnVersion: currentVersion,
dynamicTurnState: { ...dynamicCase().dynamicTurnState, turnVersion: currentVersion },
}), () => "2026-07-18");
await assert.rejects(
persistence.completeDynamicScoringJob({ ...value, candidateResult: lowCandidate }, {
expectedVersion: 7, jobId, evidenceFingerprint: fingerprint, algorithmVersion,
}),
BirthTimeJourneyStoreError,
);
await assert.rejects(
persistence.completeDynamicScoringJob({ ...value, candidateResult: lowCandidate }, {
expectedVersion: 7, jobId, evidenceFingerprint: fingerprint, algorithmVersion,
}),
BirthTimeJourneyStoreError,
);
await assert.rejects(
persistence.failDynamicScoringJob(value, {
expectedVersion: 7, jobId, evidenceFingerprint: fingerprint,
algorithmVersion, failureCode: "engine_unavailable",
}),
(error) => error instanceof StaleJourneyTurnError
&& error.expectedVersion === 7
&& error.currentVersion === 9,
);
currentVersion = 10;
await assert.rejects(
persistence.failDynamicScoringJob(value, {
expectedVersion: 7, jobId, evidenceFingerprint: fingerprint,
algorithmVersion, failureCode: "engine_unavailable",
}),
BirthTimeJourneyStoreError,
);
});