403 lines
15 KiB
TypeScript
403 lines
15 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import { spawnSync } from "node:child_process";
|
|
import test from "node:test";
|
|
import { startPostgresFixture } from "./helpers/postgres-fixture.ts";
|
|
|
|
const runnerPath = new URL("../scripts/db-migrate.mjs", import.meta.url).pathname;
|
|
const jobMigration = readFileSync(
|
|
new URL("../db/migrations/20260814040000_personal_report_jobs_v2.sql", import.meta.url),
|
|
"utf8",
|
|
);
|
|
|
|
const USER_ID = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
|
|
const REPORT_ID = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb";
|
|
const REQUEST_ID = "cccccccc-cccc-4ccc-8ccc-cccccccccccc";
|
|
const SECOND_REPORT_ID = "dddddddd-dddd-4ddd-8ddd-dddddddddddd";
|
|
const SECOND_REQUEST_ID = "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee";
|
|
const THIRD_REPORT_ID = "77777777-7777-4777-8777-777777777777";
|
|
const THIRD_REQUEST_ID = "88888888-8888-4888-8888-888888888888";
|
|
const FOURTH_REPORT_ID = "99999999-9999-4999-8999-999999999999";
|
|
const FOURTH_REQUEST_ID = "12121212-1212-4121-8121-121212121212";
|
|
const FIFTH_REPORT_ID = "34343434-3434-4343-8343-343434343434";
|
|
const FIFTH_REQUEST_ID = "56565656-5656-4565-8565-565656565656";
|
|
const WRONG_LEASE = "ffffffff-ffff-4fff-8fff-ffffffffffff";
|
|
const FINGERPRINT = "1".repeat(64);
|
|
const SECOND_FINGERPRINT = "2".repeat(64);
|
|
const THIRD_FINGERPRINT = "7".repeat(64);
|
|
const FOURTH_FINGERPRINT = "8".repeat(64);
|
|
const FIFTH_FINGERPRINT = "9".repeat(64);
|
|
const CALCULATION_HASH = "3".repeat(64);
|
|
const EVIDENCE_HASH = "4".repeat(64);
|
|
const SKILL_NAME = "jyotish-personal-report";
|
|
const SKILL_VERSION = "1.0.0";
|
|
const SKILL_COMMIT = "5".repeat(40);
|
|
const SKILL_SNAPSHOT = "6".repeat(64);
|
|
|
|
function sqlLiteral(value: string): string {
|
|
return `'${value.replaceAll("'", "''")}'`;
|
|
}
|
|
|
|
function documentFor(reportId: string): string {
|
|
return JSON.stringify({
|
|
schemaVersion: "report_document.v2",
|
|
reportId,
|
|
provenance: {
|
|
calculationHash: CALCULATION_HASH,
|
|
evidenceHash: EVIDENCE_HASH,
|
|
skillName: SKILL_NAME,
|
|
skillVersion: SKILL_VERSION,
|
|
skillSourceCommit: SKILL_COMMIT,
|
|
skillSnapshotSha256: SKILL_SNAPSHOT,
|
|
},
|
|
});
|
|
}
|
|
|
|
function insertGeneratingReport(reportId: string, requestId: string, fingerprint: string): string {
|
|
return `
|
|
insert into public.personal_reports (
|
|
id, user_id, request_id, request_fingerprint, report_type, status,
|
|
schema_version, presentation_mode, requested_themes, depth,
|
|
skill_name, skill_version, skill_source_commit, skill_snapshot_sha256
|
|
) values (
|
|
'${reportId}', '${USER_ID}', '${requestId}', '${fingerprint}',
|
|
'personal_full', 'generating', 'report_document.v2', 'default',
|
|
array['general']::text[], 'standard', '${SKILL_NAME}', '${SKILL_VERSION}',
|
|
'${SKILL_COMMIT}', '${SKILL_SNAPSHOT}'
|
|
);
|
|
`;
|
|
}
|
|
|
|
function completeSql(input: {
|
|
jobId: string;
|
|
leaseToken: string;
|
|
reportId: string;
|
|
requestId: string;
|
|
fingerprint: string;
|
|
}): string {
|
|
return `
|
|
set role service_role;
|
|
select status
|
|
from public.complete_personal_report_job(
|
|
'${input.jobId}', '${input.leaseToken}', '${USER_ID}', '${input.reportId}',
|
|
'${input.requestId}', '${input.fingerprint}', 'report_document.v2',
|
|
${sqlLiteral(documentFor(input.reportId))}::jsonb,
|
|
'${CALCULATION_HASH}', '${EVIDENCE_HASH}', '${SKILL_NAME}',
|
|
'${SKILL_VERSION}', '${SKILL_COMMIT}', '${SKILL_SNAPSHOT}'
|
|
);
|
|
`;
|
|
}
|
|
|
|
function failSql(input: {
|
|
jobId: string;
|
|
leaseToken: string;
|
|
reportId: string;
|
|
requestId: string;
|
|
fingerprint: string;
|
|
}): string {
|
|
return `
|
|
set role service_role;
|
|
select status
|
|
from public.fail_personal_report_job(
|
|
'${input.jobId}', '${input.leaseToken}', '${USER_ID}', '${input.reportId}',
|
|
'${input.requestId}', '${input.fingerprint}',
|
|
'report_schema_invalid', 'report_schema_invalid'
|
|
);
|
|
`;
|
|
}
|
|
|
|
test("personal report job migration is atomic, lease-bound, recoverable, and owner read-only", () => {
|
|
const fixture = startPostgresFixture();
|
|
const schemaUrl = fixture.connectionUrl("schema_owner", "schema-owner-test-password");
|
|
|
|
try {
|
|
const migration = spawnSync(process.execPath, [runnerPath], {
|
|
encoding: "utf8",
|
|
env: { ...process.env, SCHEMA_DATABASE_URL: schemaUrl },
|
|
});
|
|
assert.equal(migration.status, 0, migration.stderr);
|
|
assert.match(migration.stdout, /applied 20260814025000_personal_report_document_v2\.sql/);
|
|
assert.match(migration.stdout, /applied 20260814040000_personal_report_jobs_v2\.sql/);
|
|
|
|
fixture.psql(`
|
|
insert into identity.users (id, name, email, email_verified, email_verified_at)
|
|
values ('${USER_ID}', 'Report Test User', 'report-job-test@example.com', true, now());
|
|
${insertGeneratingReport(REPORT_ID, REQUEST_ID, FINGERPRINT)}
|
|
`);
|
|
|
|
const originalJobId = fixture.psql(`
|
|
select id from public.personal_report_jobs
|
|
where user_id = '${USER_ID}' and request_id = '${REQUEST_ID}'
|
|
`);
|
|
assert.match(originalJobId, /^[0-9a-f-]{36}$/);
|
|
|
|
// Simulate a generating row created before the durable trigger existed,
|
|
// then re-run the forward migration to verify its historical backfill.
|
|
fixture.psql(`delete from public.personal_report_jobs where id = '${originalJobId}'`);
|
|
assert.equal(fixture.psql(`select count(*) from public.personal_report_jobs where id = '${originalJobId}'`), "0");
|
|
fixture.psql(jobMigration);
|
|
const backfilledJobId = fixture.psql(`
|
|
select id from public.personal_report_jobs
|
|
where user_id = '${USER_ID}' and request_id = '${REQUEST_ID}'
|
|
`);
|
|
assert.match(backfilledJobId, /^[0-9a-f-]{36}$/);
|
|
|
|
assert.throws(
|
|
() => fixture.psqlAs(
|
|
"app_runtime",
|
|
"app-runtime-test-password",
|
|
`set role authenticated;
|
|
select set_config('request.jwt.claim.sub', '${USER_ID}', true);
|
|
delete from public.personal_report_jobs where id = '${backfilledJobId}';`,
|
|
),
|
|
/permission denied for table personal_report_jobs/,
|
|
);
|
|
assert.equal(fixture.psql(`select count(*) from public.personal_report_jobs where id = '${backfilledJobId}'`), "1");
|
|
|
|
const claim = fixture.psqlAs(
|
|
"service_runtime",
|
|
"service-runtime-test-password",
|
|
`set role service_role;
|
|
select id::text || ':' || lease_token::text
|
|
from public.claim_personal_report_job('report-worker', 60, '${backfilledJobId}');`,
|
|
);
|
|
const [jobId, leaseToken] = claim.split("\n").at(-1)!.split(":");
|
|
assert.equal(jobId, backfilledJobId);
|
|
assert.match(leaseToken, /^[0-9a-f-]{36}$/);
|
|
|
|
assert.throws(
|
|
() => fixture.psqlAs(
|
|
"service_runtime",
|
|
"service-runtime-test-password",
|
|
completeSql({
|
|
jobId,
|
|
leaseToken: WRONG_LEASE,
|
|
reportId: REPORT_ID,
|
|
requestId: REQUEST_ID,
|
|
fingerprint: FINGERPRINT,
|
|
}),
|
|
),
|
|
/personal_report_job_lease_lost/,
|
|
);
|
|
assert.equal(
|
|
fixture.psql(`
|
|
select report.status || ':' || job.status
|
|
from public.personal_reports report
|
|
join public.personal_report_jobs job
|
|
on job.user_id = report.user_id and job.request_id = report.request_id
|
|
where report.id = '${REPORT_ID}'
|
|
`),
|
|
"generating:running",
|
|
);
|
|
|
|
assert.equal(
|
|
fixture.psqlAs(
|
|
"service_runtime",
|
|
"service-runtime-test-password",
|
|
completeSql({
|
|
jobId,
|
|
leaseToken,
|
|
reportId: REPORT_ID,
|
|
requestId: REQUEST_ID,
|
|
fingerprint: FINGERPRINT,
|
|
}),
|
|
),
|
|
"SET\nready",
|
|
);
|
|
assert.equal(
|
|
fixture.psql(`
|
|
select report.status || ':' || job.status || ':' || job.progress_percent
|
|
from public.personal_reports report
|
|
join public.personal_report_jobs job
|
|
on job.user_id = report.user_id and job.request_id = report.request_id
|
|
where report.id = '${REPORT_ID}'
|
|
`),
|
|
"ready:ready:100",
|
|
);
|
|
|
|
fixture.psql(insertGeneratingReport(THIRD_REPORT_ID, THIRD_REQUEST_ID, THIRD_FINGERPRINT));
|
|
const thirdJobId = fixture.psql(`
|
|
select id from public.personal_report_jobs
|
|
where user_id = '${USER_ID}' and request_id = '${THIRD_REQUEST_ID}'
|
|
`);
|
|
const thirdClaim = fixture.psqlAs(
|
|
"service_runtime",
|
|
"service-runtime-test-password",
|
|
`set role service_role;
|
|
select lease_token from public.claim_personal_report_job('failure-worker', 60, '${thirdJobId}');`,
|
|
);
|
|
const thirdLeaseToken = thirdClaim.split("\n").at(-1)!;
|
|
assert.match(thirdLeaseToken, /^[0-9a-f-]{36}$/);
|
|
|
|
assert.throws(
|
|
() => fixture.psqlAs(
|
|
"service_runtime",
|
|
"service-runtime-test-password",
|
|
failSql({
|
|
jobId: thirdJobId,
|
|
leaseToken: WRONG_LEASE,
|
|
reportId: THIRD_REPORT_ID,
|
|
requestId: THIRD_REQUEST_ID,
|
|
fingerprint: THIRD_FINGERPRINT,
|
|
}),
|
|
),
|
|
/personal_report_job_lease_lost/,
|
|
);
|
|
assert.equal(
|
|
fixture.psql(`
|
|
select report.status || ':' || job.status
|
|
from public.personal_reports report
|
|
join public.personal_report_jobs job
|
|
on job.user_id = report.user_id and job.request_id = report.request_id
|
|
where report.id = '${THIRD_REPORT_ID}'
|
|
`),
|
|
"generating:running",
|
|
);
|
|
|
|
assert.equal(
|
|
fixture.psqlAs(
|
|
"service_runtime",
|
|
"service-runtime-test-password",
|
|
failSql({
|
|
jobId: thirdJobId,
|
|
leaseToken: thirdLeaseToken,
|
|
reportId: THIRD_REPORT_ID,
|
|
requestId: THIRD_REQUEST_ID,
|
|
fingerprint: THIRD_FINGERPRINT,
|
|
}),
|
|
),
|
|
"SET\nfailed",
|
|
);
|
|
assert.equal(
|
|
fixture.psql(`
|
|
select report.status || ':' || report.failure_code || ':'
|
|
|| job.status || ':' || job.last_error_code
|
|
from public.personal_reports report
|
|
join public.personal_report_jobs job
|
|
on job.user_id = report.user_id and job.request_id = report.request_id
|
|
where report.id = '${THIRD_REPORT_ID}'
|
|
`),
|
|
"failed:report_schema_invalid:failed:report_schema_invalid",
|
|
);
|
|
|
|
fixture.psql(insertGeneratingReport(SECOND_REPORT_ID, SECOND_REQUEST_ID, SECOND_FINGERPRINT));
|
|
const secondJobId = fixture.psql(`
|
|
select id from public.personal_report_jobs
|
|
where user_id = '${USER_ID}' and request_id = '${SECOND_REQUEST_ID}'
|
|
`);
|
|
fixture.psql(`
|
|
delete from public.personal_report_jobs where id = '${secondJobId}';
|
|
insert into public.personal_report_jobs (
|
|
id, user_id, request_id, request_fingerprint, status,
|
|
attempt_count, max_attempts, next_attempt_at, progress_phase,
|
|
last_error_code, last_error_at
|
|
) values (
|
|
'${secondJobId}', '${USER_ID}', '${SECOND_REQUEST_ID}', '${SECOND_FINGERPRINT}',
|
|
'retrying', 2, 3, clock_timestamp() - interval '1 second', 'retry_wait',
|
|
'lease_expired', clock_timestamp()
|
|
);
|
|
`);
|
|
const secondClaim = fixture.psqlAs(
|
|
"service_runtime",
|
|
"service-runtime-test-password",
|
|
`set role service_role;
|
|
select lease_token from public.claim_personal_report_job('split-worker', 60, '${secondJobId}');`,
|
|
);
|
|
assert.match(secondClaim.split("\n").at(-1)!, /^[0-9a-f-]{36}$/);
|
|
|
|
fixture.psql(`
|
|
update public.personal_reports
|
|
set status = 'ready',
|
|
report_document = ${sqlLiteral(documentFor(SECOND_REPORT_ID))}::jsonb,
|
|
calculation_hash = '${CALCULATION_HASH}',
|
|
evidence_hash = '${EVIDENCE_HASH}',
|
|
completed_at = clock_timestamp()
|
|
where id = '${SECOND_REPORT_ID}';
|
|
update public.personal_report_jobs
|
|
set lease_acquired_at = clock_timestamp() - interval '3 seconds',
|
|
heartbeat_at = clock_timestamp() - interval '2 seconds',
|
|
lease_expires_at = clock_timestamp() - interval '1 second'
|
|
where id = '${secondJobId}';
|
|
`);
|
|
|
|
assert.equal(
|
|
fixture.psqlAs(
|
|
"service_runtime",
|
|
"service-runtime-test-password",
|
|
"set role service_role; select * from public.recover_expired_personal_report_jobs(10);",
|
|
),
|
|
"SET\n0|0",
|
|
);
|
|
assert.equal(
|
|
fixture.psql(`select status || ':' || progress_percent from public.personal_report_jobs where id = '${secondJobId}'`),
|
|
"ready:100",
|
|
);
|
|
|
|
|
|
fixture.psql(insertGeneratingReport(FOURTH_REPORT_ID, FOURTH_REQUEST_ID, FOURTH_FINGERPRINT));
|
|
const fourthJobId = fixture.psql(`
|
|
select id from public.personal_report_jobs
|
|
where user_id = '${USER_ID}' and request_id = '${FOURTH_REQUEST_ID}'
|
|
`);
|
|
fixture.psql(`
|
|
delete from public.personal_report_jobs where id = '${fourthJobId}';
|
|
insert into public.personal_report_jobs (
|
|
id, user_id, request_id, request_fingerprint, status,
|
|
attempt_count, max_attempts, next_attempt_at, progress_phase,
|
|
last_error_code, last_error_at
|
|
) values (
|
|
'${fourthJobId}', '${USER_ID}', '${FOURTH_REQUEST_ID}', '${FOURTH_FINGERPRINT}',
|
|
'retrying', 2, 3, clock_timestamp() - interval '1 second', 'retry_wait',
|
|
'lease_expired', clock_timestamp()
|
|
);
|
|
`);
|
|
const fourthClaim = fixture.psqlAs(
|
|
"service_runtime",
|
|
"service-runtime-test-password",
|
|
`set role service_role;
|
|
select lease_token from public.claim_personal_report_job('exhausted-worker', 60, '${fourthJobId}');`,
|
|
);
|
|
assert.match(fourthClaim.split("\n").at(-1)!, /^[0-9a-f-]{36}$/);
|
|
fixture.psql(`
|
|
update public.personal_report_jobs
|
|
set lease_acquired_at = clock_timestamp() - interval '3 seconds',
|
|
heartbeat_at = clock_timestamp() - interval '2 seconds',
|
|
lease_expires_at = clock_timestamp() - interval '1 second'
|
|
where id = '${fourthJobId}';
|
|
`);
|
|
|
|
assert.equal(
|
|
fixture.psqlAs(
|
|
"service_runtime",
|
|
"service-runtime-test-password",
|
|
"set role service_role; select * from public.recover_expired_personal_report_jobs(10);",
|
|
),
|
|
"SET\n0|1",
|
|
);
|
|
assert.equal(
|
|
fixture.psql(`
|
|
select report.status || ':' || report.failure_code || ':'
|
|
|| job.status || ':' || job.last_error_code
|
|
from public.personal_reports report
|
|
join public.personal_report_jobs job
|
|
on job.user_id = report.user_id and job.request_id = report.request_id
|
|
where report.id = '${FOURTH_REPORT_ID}'
|
|
`),
|
|
"failed:calculation_unavailable:failed:lease_expired_attempts_exhausted",
|
|
);
|
|
fixture.psql(insertGeneratingReport(FIFTH_REPORT_ID, FIFTH_REQUEST_ID, FIFTH_FINGERPRINT));
|
|
assert.equal(
|
|
fixture.psql(`
|
|
select report.status || ':' || job.status
|
|
from public.personal_reports report
|
|
join public.personal_report_jobs job
|
|
on job.user_id = report.user_id and job.request_id = report.request_id
|
|
where report.id = '${FIFTH_REPORT_ID}'
|
|
`),
|
|
"generating:queued",
|
|
);
|
|
} finally {
|
|
fixture.stop();
|
|
}
|
|
});
|