Files
Jyotisha/frontend/tests/database-personal-report-card-summary.test.ts
T
Jesse_Chen b466a6fc8c fix(report): stop listing reports via PostgREST JSON paths (BUG-574)
Staging PostgREST rejects the executiveSummary JSON-path alias, so GET /api/reports 500s. Persist a plain card_summary column from the Markdown excerpt instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 11:08:00 +08:00

200 lines
6.3 KiB
TypeScript

import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import test from "node:test";
import { startPostgresFixture } from "./helpers/postgres-fixture.ts";
const runnerPath = fileURLToPath(
new URL("../scripts/db-migrate.mjs", import.meta.url),
);
function dockerAvailable(): boolean {
return spawnSync("docker", ["version", "--format", "{{.Server.Version}}"], {
encoding: "utf8",
stdio: "ignore",
}).status === 0;
}
const skipWithoutDocker = dockerAvailable() ? false : "docker unavailable on this host";
const USER_A = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
const USER_B = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb";
const REPORT_ID = "cccccccc-cccc-4ccc-8ccc-cccccccccccc";
const REQUEST_ID = "dddddddd-dddd-4ddd-8ddd-dddddddddddd";
const HASH = "1111111111111111111111111111111111111111111111111111111111111111";
const COMMIT = "2222222222222222222222222222222222222222";
const SKILL_NAME = "jyotish-personal-report";
const SKILL_VERSION = "1.0.0";
function sqlLiteral(value: string): string {
return `'${value.replaceAll("'", "''")}'`;
}
function selectAsAuthenticated(userId: string, sql: string): string {
return `
set role authenticated;
select set_config('request.jwt.claim.sub', '${userId}', true);
${sql}
`;
}
function serviceSql(sql: string): string {
return `set role service_role;\n${sql}`;
}
function documentFor(reportId: string, summary: string): string {
return JSON.stringify({
schemaVersion: "report_document.v2",
reportId,
provenance: {
calculationHash: HASH,
evidenceHash: HASH,
skillName: SKILL_NAME,
skillVersion: SKILL_VERSION,
skillSourceCommit: COMMIT,
skillSnapshotSha256: HASH,
},
executiveSummary: { summary },
});
}
test("personal_reports.card_summary is nullable, owner-read, service-written, and length-capped", { skip: skipWithoutDocker }, () => {
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 20260907010000_personal_report_card_summary\.sql/);
fixture.psql(`
insert into identity.users (id, name, email, email_verified, email_verified_at)
values
('${USER_A}', 'Card Summary User A', 'card-a@example.com', true, now()),
('${USER_B}', 'Card Summary User B', 'card-b@example.com', true, now());
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 (
'${REPORT_ID}', '${USER_A}', '${REQUEST_ID}', '${HASH}', 'personal_full', 'generating',
'report_document.v2', 'default', array['career']::text[], 'standard',
'${SKILL_NAME}', '${SKILL_VERSION}', '${COMMIT}', '${HASH}'
);
`);
assert.equal(
fixture.psql(`
select (card_summary is null)::text from public.personal_reports where id = '${REPORT_ID}'
`),
"true",
);
assert.throws(
() => fixture.psqlAs(
"app_runtime",
"app-runtime-test-password",
selectAsAuthenticated(USER_A, `
update public.personal_reports
set card_summary = 'owner write'
where id = '${REPORT_ID}'
`),
),
/permission denied for table personal_reports/,
);
const jobId = fixture.psql(`
select id from public.personal_report_jobs
where user_id = '${USER_A}' and request_id = '${REQUEST_ID}'
`);
assert.match(jobId, /^[0-9a-f-]{36}$/);
const claim = fixture.psqlAs(
"service_runtime",
"service-runtime-test-password",
`set role service_role;
select lease_token::text
from public.claim_personal_report_job('card-summary-worker', 60, '${jobId}');`,
);
const leaseToken = claim.split("\n").at(-1)!;
assert.match(leaseToken, /^[0-9a-f-]{36}$/);
assert.equal(
fixture.psqlAs(
"service_runtime",
"service-runtime-test-password",
serviceSql(`
select status
from public.complete_personal_report_job(
'${jobId}', '${leaseToken}', '${USER_A}', '${REPORT_ID}',
'${REQUEST_ID}', '${HASH}', 'report_document.v2',
${sqlLiteral(documentFor(REPORT_ID, "事业方向保持观察"))}::jsonb,
'${HASH}', '${HASH}', '${SKILL_NAME}',
'${SKILL_VERSION}', '${COMMIT}', '${HASH}'
);
`),
),
"SET\nready",
);
assert.equal(
fixture.psql(`select card_summary from public.personal_reports where id = '${REPORT_ID}'`),
"事业方向保持观察",
);
assert.equal(
fixture.psqlAs(
"app_runtime",
"app-runtime-test-password",
selectAsAuthenticated(USER_A, `
select card_summary from public.personal_reports where id = '${REPORT_ID}'
`),
),
`SET\n${USER_A}\n事业方向保持观察`,
);
assert.equal(
fixture.psqlAs(
"app_runtime",
"app-runtime-test-password",
selectAsAuthenticated(USER_B, `
select count(*) from public.personal_reports where id = '${REPORT_ID}'
`),
),
`SET\n${USER_B}\n0`,
);
fixture.psqlAs(
"service_runtime",
"service-runtime-test-password",
serviceSql(`
update public.personal_reports
set card_summary = '${"测".repeat(500)}'
where id = '${REPORT_ID}'
`),
);
assert.equal(
fixture.psql(`
select char_length(card_summary) from public.personal_reports where id = '${REPORT_ID}'
`),
"500",
);
assert.throws(
() => fixture.psqlAs(
"service_runtime",
"service-runtime-test-password",
serviceSql(`
update public.personal_reports
set card_summary = '${"测".repeat(501)}'
where id = '${REPORT_ID}'
`),
),
/personal_reports_card_summary_length_check/,
);
} finally {
fixture.stop();
}
});