Files
Jyotisha/frontend/tests/birth-time-dynamic-job-lease.test.ts
T
2026-07-19 09:12:45 +08:00

92 lines
3.8 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { createBirthTimeJourneyService } from "../src/lib/birth-time-journey-service.ts";
import { completeDynamicScoreTransition } from "../src/lib/birth-time-dynamic-transitions.ts";
import {
BirthTimeScoringJobError,
dynamicChoiceScoringAlgorithmVersion,
dynamicEvidenceFingerprint,
} from "../src/lib/birth-time-scoring-job.ts";
import { dynamicCase, ownerId, persistedQuestion } from "./birth-time-dynamic-persistence-fixture.ts";
import { dynamicJobStore } from "./birth-time-dynamic-job-memory-store.ts";
import { memoryStore } from "./birth-time-journey-memory-store.ts";
test("dynamic processing claims are reclaimed only after the lease", async () => {
const memory = memoryStore(dynamicCase());
const jobs = dynamicJobStore(memory.store, () => {
const current = memory.savedCase();
return current?.journeyProtocol === "dynamic-choice-v2" ? current : null;
});
const service = createBirthTimeJourneyService({
store: jobs.store,
now: () => new Date("2026-07-18T08:00:00.000Z"),
engine: {
async scan() { throw new Error("unexpected scan"); },
async score() { throw new Error("unexpected score"); },
async scoreEvents() { throw new Error("unexpected event score"); },
},
});
const pending = await service.answerDynamicChoice(ownerId, {
caseId: dynamicCase().id,
actionId: "9a921af8-ddcc-4d20-b4c8-fbbb3e6a814d",
turnVersion: 7,
questionId: persistedQuestion.questionId,
optionId: persistedQuestion.options[0].optionId,
});
if (pending.nextAction.kind !== "score_pending") throw new Error("expected pending score");
const stored = memory.savedCase();
if (!stored || stored.journeyProtocol !== "dynamic-choice-v2") throw new Error("missing v2 case");
const claim = jobs.store.claimDynamicScoringJob;
if (!claim) throw new Error("missing dynamic claim");
const identity = {
userId: ownerId,
caseId: stored.id,
jobId: pending.nextAction.jobId,
evidenceFingerprint: dynamicEvidenceFingerprint(stored.choiceEvidence),
algorithmVersion: dynamicChoiceScoringAlgorithmVersion,
} as const;
assert.equal((await claim({ ...identity, now: "2026-07-18T08:00:00.000Z" })).kind, "claimed");
assert.equal((await claim({ ...identity, now: "2026-07-18T08:00:59.999Z" })).kind, "processing");
assert.equal((await claim({ ...identity, now: "2026-07-18T08:01:00.000Z" })).kind, "claimed");
const processing = memory.savedCase();
if (!processing || processing.journeyProtocol !== "dynamic-choice-v2") throw new Error("missing processing case");
const candidate = {
resultId: "097b7b4c-60f3-4ed8-b290-64b2084182e7",
confidence: "low" as const,
canApply: false,
winningSegment: null,
eventCount: 1,
domainCount: 1,
topScore: 10,
secondScore: 9,
marginPercent: 10,
reasons: ["insufficient_effective_evidence"],
evidence: [],
algorithmVersion: dynamicChoiceScoringAlgorithmVersion,
};
const completedTurn = completeDynamicScoreTransition({
stored: processing,
candidate,
usefulOpportunityCount: 1,
repeatedOnly: false,
nextVersion: processing.turnVersion + 1,
});
await jobs.store.completeDynamicScoringJob(completedTurn, {
expectedVersion: processing.turnVersion,
jobId: identity.jobId,
evidenceFingerprint: identity.evidenceFingerprint,
algorithmVersion: identity.algorithmVersion,
});
assert.equal((await claim({ ...identity, now: "2026-07-18T08:02:00.000Z" })).kind, "completed");
const coherent = memory.savedCase();
if (!coherent || coherent.journeyProtocol !== "dynamic-choice-v2") throw new Error("missing completed case");
memory.replaceCase({ ...coherent, candidateResult: null });
await assert.rejects(
claim({ ...identity, now: "2026-07-18T08:02:01.000Z" }),
BirthTimeScoringJobError,
);
});