import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { PersonalReportJobStateError, assertPersonalReportJobProgressPhase, assertPersonalReportJobTransition, canHeartbeatPersonalReportJobLease, canRetryPersonalReportJob, canTransitionPersonalReportJob, classifyPersonalReportJobRequest, isPersonalReportJobLeaseExpired, isPersonalReportJobTerminal, isPersonalReportRequestFingerprint, recoverExpiredPersonalReportJobLease, } from "../src/lib/personal-report-job-state.ts"; const now = new Date("2026-08-14T03:00:00.000Z"); test("rejects illegal status transitions", () => { assert.equal(canTransitionPersonalReportJob("queued", "running"), true); assert.equal(canTransitionPersonalReportJob("running", "retrying"), true); assert.equal(canTransitionPersonalReportJob("queued", "ready"), false); assert.equal(canTransitionPersonalReportJob("suspended", "ready"), false); assert.throws( () => assertPersonalReportJobTransition("queued", "ready"), (error) => error instanceof PersonalReportJobStateError && error.reason === "invalid_transition", ); }); test("bounds retries at maxAttempts", () => { assert.equal(canRetryPersonalReportJob(0, 3), true); assert.equal(canRetryPersonalReportJob(2, 3), true); assert.equal(canRetryPersonalReportJob(3, 3), false); assert.throws( () => canRetryPersonalReportJob(4, 3), (error) => error instanceof PersonalReportJobStateError && error.reason === "invalid_retry_budget", ); }); test("recovers an expired running lease into retrying while budget remains", () => { const job = { status: "running" as const, leaseOwner: "worker-a", leaseExpiresAt: "2026-08-14T02:59:59.000Z", attemptCount: 1, maxAttempts: 3, }; assert.equal(isPersonalReportJobLeaseExpired(job, now), true); assert.deepEqual( recoverExpiredPersonalReportJobLease(job, now), { kind: "retry", nextStatus: "retrying" }, ); }); test("fails expired work after the final permitted attempt", () => { assert.deepEqual( recoverExpiredPersonalReportJobLease({ status: "running", leaseOwner: "worker-a", leaseExpiresAt: "2026-08-14T02:59:59.000Z", attemptCount: 3, maxAttempts: 3, }, now), { kind: "exhausted", nextStatus: "failed" }, ); }); test("keeps active leases owned by the claiming worker", () => { const lease = { status: "running" as const, leaseOwner: "worker-a", leaseExpiresAt: "2026-08-14T03:00:30.000Z", }; assert.equal(isPersonalReportJobLeaseExpired(lease, now), false); assert.equal(canHeartbeatPersonalReportJobLease(lease, "worker-a", now), true); assert.equal(canHeartbeatPersonalReportJobLease(lease, "worker-b", now), false); }); test("terminal states are immutable", () => { for (const status of ["ready", "failed", "cancelled"] as const) { assert.equal(isPersonalReportJobTerminal(status), true); for (const target of ["queued", "running", "suspended", "retrying", "ready", "failed", "cancelled"] as const) { assert.equal(canTransitionPersonalReportJob(status, target), false); } } }); test("classifies request-id replay and fingerprint conflict", () => { const fingerprint = "a".repeat(64); const existing = { requestId: "request-1", requestFingerprint: fingerprint }; assert.equal(isPersonalReportRequestFingerprint(fingerprint), true); assert.equal(isPersonalReportRequestFingerprint("A".repeat(64)), false); assert.equal(classifyPersonalReportJobRequest(existing, existing), "replay"); assert.equal(classifyPersonalReportJobRequest(existing, { requestId: "request-1", requestFingerprint: "b".repeat(64), }), "conflict"); assert.equal(classifyPersonalReportJobRequest(existing, { requestId: "request-2", requestFingerprint: fingerprint, }), "new"); }); test("accepts machine-readable progress phases only", () => { assert.doesNotThrow(() => assertPersonalReportJobProgressPhase("evidence_collection")); assert.throws( () => assertPersonalReportJobProgressPhase("Evidence Collection"), (error) => error instanceof PersonalReportJobStateError && error.reason === "invalid_progress_phase", ); }); const localJobMigration = readFileSync( new URL("../db/migrations/20260814040000_personal_report_jobs_v2.sql", import.meta.url), "utf8", ); const supabaseJobMigration = readFileSync( new URL("../supabase/migrations/20260814040000_personal_report_jobs_v2.sql", import.meta.url), "utf8", ); test("job migration mirror is exact and preserves personal_reports as the document projection", () => { assert.equal(localJobMigration, supabaseJobMigration); assert.match(supabaseJobMigration, /create table if not exists public\.personal_report_jobs/); assert.doesNotMatch(supabaseJobMigration, /alter table public\.personal_reports/); assert.match(supabaseJobMigration, /references public\.personal_reports \(user_id, request_id\)[\s\S]*on delete cascade/); }); test("job migration persists the full state, lease, retry and progress contract", () => { assert.match(supabaseJobMigration, /status in \([\s\S]*'queued'[\s\S]*'running'[\s\S]*'suspended'[\s\S]*'retrying'[\s\S]*'ready'[\s\S]*'failed'[\s\S]*'cancelled'[\s\S]*\)/); assert.match(supabaseJobMigration, /attempt_count integer not null default 0/); assert.match(supabaseJobMigration, /max_attempts integer not null default 3/); assert.match(supabaseJobMigration, /attempt_count between 0 and max_attempts/); assert.match(supabaseJobMigration, /lease_token uuid/); assert.match(supabaseJobMigration, /lease_expires_at timestamptz/); assert.match(supabaseJobMigration, /heartbeat_at timestamptz/); assert.match(supabaseJobMigration, /progress_phase text not null default 'queued'/); assert.match(supabaseJobMigration, /personal_report_jobs_one_active_per_user[\s\S]*where status in \('queued', 'running', 'suspended', 'retrying'\)/); }); test("job migration enforces recovery and terminal immutability in PostgreSQL", () => { assert.match(supabaseJobMigration, /personal_report_job_terminal_immutable/); assert.match(supabaseJobMigration, /old\.status = 'running' and new\.status in \('suspended', 'retrying', 'ready', 'failed', 'cancelled'\)/); assert.match(supabaseJobMigration, /create or replace function public\.claim_personal_report_job/); assert.match(supabaseJobMigration, /create or replace function public\.heartbeat_personal_report_job/); assert.match(supabaseJobMigration, /create or replace function public\.complete_personal_report_job/); assert.match(supabaseJobMigration, /v_job\.status <> 'running'[\s\S]*v_job\.lease_token is distinct from p_lease_token[\s\S]*v_job\.lease_expires_at <= v_completed_at/); assert.match(supabaseJobMigration, /update public\.personal_reports[\s\S]*status = 'ready'[\s\S]*update public\.personal_report_jobs[\s\S]*status = 'ready'/); assert.match(supabaseJobMigration, /create or replace function public\.fail_personal_report_job/); assert.match(supabaseJobMigration, /v_job\.status <> 'running'[\s\S]*v_job\.lease_token is distinct from p_lease_token[\s\S]*v_job\.lease_expires_at <= v_failed_at/); assert.match(supabaseJobMigration, /update public\.personal_reports[\s\S]*status = 'failed'[\s\S]*update public\.personal_report_jobs[\s\S]*status = 'failed'/); assert.match(supabaseJobMigration, /create or replace function public\.recover_expired_personal_report_jobs/); assert.match(supabaseJobMigration, /when candidates\.report_status = 'ready' then 'ready'[\s\S]*when candidates\.report_status = 'failed' then 'failed'[\s\S]*when job\.attempt_count < job\.max_attempts then 'retrying'[\s\S]*else 'failed'/); }); test("job migration keeps owner access select-only and service_role owns writes", () => { assert.match(supabaseJobMigration, /alter table public\.personal_report_jobs enable row level security/); assert.match(supabaseJobMigration, /create policy personal_report_jobs_select_own[\s\S]*for select[\s\S]*to authenticated[\s\S]*using \(auth\.uid\(\) = user_id\)/); assert.doesNotMatch(supabaseJobMigration, /create policy personal_report_jobs_delete_own[\s\S]*for delete/); assert.match(supabaseJobMigration, /grant select on table public\.personal_report_jobs to authenticated/); assert.doesNotMatch(supabaseJobMigration, /grant (?:select, )?(?:insert|update|delete)[^;]* on table public\.personal_report_jobs to authenticated/); assert.match(supabaseJobMigration, /grant select, insert, update, delete on table public\.personal_report_jobs to service_role/); for (const signature of [ "claim_personal_report_job(text, integer, uuid)", "complete_personal_report_job(uuid, uuid, uuid, uuid, uuid, text, text, jsonb, text, text, text, text, text, text)", "fail_personal_report_job(uuid, uuid, uuid, uuid, uuid, text, text, text)", ]) { const escaped = signature.replace(/[()]/g, "\\$&"); assert.match(supabaseJobMigration, new RegExp(`grant execute on function public\\.${escaped} to service_role`)); assert.doesNotMatch(supabaseJobMigration, new RegExp(`grant execute on function public\\.${escaped} to authenticated`)); } });