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)); const userId = "82000000-0000-4000-8000-000000000082"; const personId = "82000000-0000-4000-8000-000000000182"; const partnerId = "82000000-0000-4000-8000-000000000282"; const reportId = "82000000-0000-4000-8000-000000000382"; const secondReportId = "82000000-0000-4000-8000-000000000482"; const duplicateReportId = "82000000-0000-4000-8000-000000001282"; const changedReportId = "82000000-0000-4000-8000-000000000582"; const requestId = "82000000-0000-4000-8000-000000000682"; const secondRequestId = "82000000-0000-4000-8000-000000000782"; const changedRequestId = "82000000-0000-4000-8000-000000000882"; const refundRequestId = "82000000-0000-4000-8000-000000000982"; const settleRequestId = "82000000-0000-4000-8000-000000001082"; const releaseRequestId = "82000000-0000-4000-8000-000000001182"; const fingerprint = "a".repeat(64); const otherFingerprint = "b".repeat(64); const changedFingerprint = "c".repeat(64); const rulesVersion = "bde5d615:marriage_match_product_v1"; const skillHash = "d".repeat(64); function dockerAvailable(): boolean { return spawnSync("docker", ["version", "--format", "{{.Server.Version}}"], { encoding: "utf8", stdio: "ignore" }).status === 0; } function lastLine(value: string): string { const lines = value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean); return lines[lines.length - 1] ?? ""; } test("synastry reports reuse a pair, drop with the person, and settle the synastry.full reservation", { skip: dockerAvailable() ? false : "docker unavailable on this host", }, () => { const fixture = startPostgresFixture(); try { const migration = spawnSync(process.execPath, [runnerPath], { encoding: "utf8", env: { ...process.env, SCHEMA_DATABASE_URL: fixture.connectionUrl("schema_owner", "schema-owner-test-password") }, }); assert.equal(migration.status, 0, `${migration.stdout}${migration.stderr}`); fixture.psql(` insert into identity.users (id, name, email, email_verified, email_verified_at) values ('${userId}', 'Fictional Synastry', '${userId}@example.invalid', true, now()); update public.profiles set credits = 10 where id = '${userId}'; insert into public.chart_profiles (id, user_id, role, profile) values ('${personId}', '${userId}', 'other', '{}'::jsonb), ('${partnerId}', '${userId}', 'other', '{}'::jsonb); `); const insertReport = (id: string, request: string, lowPrint: string) => ` insert into public.personal_reports ( id, user_id, chart_profile_id, partner_chart_profile_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, subject_low, subject_high, fingerprint_low, fingerprint_high, synastry_rules_version ) values ( '${id}', '${userId}', '${personId}', '${partnerId}', '${request}', '${fingerprint}', 'synastry_full', 'generating', 'synastry_report.v1', 'default', array[]::text[], 'standard', 'jyotish-full-synastry-reading', '1.0.0', '${"e".repeat(40)}', '${skillHash}', '${personId}', '${partnerId}', '${lowPrint}', '${otherFingerprint}', '${rulesVersion}' ); `; const closeJob = ` update public.personal_report_jobs set status = 'cancelled', finished_at = now() where user_id = '${userId}' and status = 'queued'; `; fixture.psql(insertReport(reportId, requestId, fingerprint)); fixture.psql(` update public.personal_reports set status = 'ready', completed_at = now(), report_document = '{}'::jsonb, calculation_hash = '${skillHash}', evidence_hash = '${skillHash}' where id = '${reportId}'; ${closeJob} `); // 原值:第二份 ready 插入被 personal_reports_synastry_pair_ready_uidx 拒绝,count 为 1 // 新值:同一对可以再有一份 ready,count 为 2 // 原因:K6 推翻复用,旧报告保留(BUG-1408) fixture.psql(` begin; ${insertReport(duplicateReportId, secondRequestId, fingerprint)} update public.personal_reports set status = 'ready', completed_at = now(), report_document = '{}'::jsonb, calculation_hash = '${skillHash}', evidence_hash = '${skillHash}' where id = '${duplicateReportId}'; commit; `); assert.equal(lastLine(fixture.psql(` select count(*) from public.personal_reports where user_id = '${userId}' and fingerprint_low = '${fingerprint}' and status = 'ready'; `)), "2"); fixture.psql(closeJob); fixture.psql(insertReport(changedReportId, changedRequestId, changedFingerprint)); fixture.psql(` update public.personal_reports set status = 'ready', completed_at = now(), report_document = '{}'::jsonb, calculation_hash = '${skillHash}', evidence_hash = '${skillHash}' where id = '${changedReportId}'; update public.personal_report_jobs set status = 'cancelled', finished_at = now() where user_id = '${userId}' and status = 'queued'; `); assert.equal(lastLine(fixture.psql(` select count(*) from public.personal_reports where user_id = '${userId}' and fingerprint_low = '${changedFingerprint}' and status = 'ready'; `)), "1"); assert.equal( fixture.psql(`select success::text || '|' || credits::text from public.authorize_usage('${userId}','synastry.full',null,'${settleRequestId}',2)`), "true|8", ); assert.equal( fixture.psql(`select success::text from public.complete_usage('${userId}','${settleRequestId}','{"actualModelId":"standard-model","inputTokens":1,"outputTokens":1,"costMicrousd":1,"durationMs":1}'::jsonb)`), "true", ); assert.equal(fixture.psql(`select status from public.usage_reservations where request_id = '${settleRequestId}'`), "completed"); assert.equal(fixture.psql(`select credits from public.profiles where id = '${userId}'`), "8"); assert.equal( fixture.psql(`select success::text || '|' || credits::text from public.authorize_usage('${userId}','synastry.full',null,'${releaseRequestId}',2)`), "true|6", ); assert.equal( fixture.psql(`select success::text || '|' || credits::text from public.release_usage('${userId}','${releaseRequestId}','generation failed')`), "true|8", ); assert.equal(fixture.psql(`select status from public.usage_reservations where request_id = '${releaseRequestId}'`), "released"); assert.equal( fixture.psql(`select success::text || '|' || credits::text from public.authorize_usage('${userId}','synastry.full',null,'${refundRequestId}',2)`), "true|6", ); fixture.psql(` 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 ( '${secondReportId}', '${userId}', '${refundRequestId}', '${fingerprint}', 'synastry_full', 'generating', 'synastry_report.v1', 'default', array[]::text[], 'standard', 'jyotish-full-synastry-reading', '1.0.0', '${"e".repeat(40)}', '${skillHash}' ); delete from public.personal_reports where id = '${secondReportId}'; update public.personal_report_jobs set status = 'cancelled', finished_at = now() where user_id = '${userId}' and status = 'queued'; `); assert.equal(fixture.psql(`select status from public.usage_reservations where request_id = '${refundRequestId}'`), "released"); assert.equal(fixture.psql(`select credits from public.profiles where id = '${userId}'`), "8"); fixture.psqlAs( "app_runtime", "app-runtime-test-password", `set role authenticated; select set_config('request.jwt.claim.sub', '${userId}', true); select public.delete_chart_subject('${personId}');`, ); assert.equal(fixture.psql(`select count(*) from public.personal_reports where user_id = '${userId}' and report_type = 'synastry_full'`), "0"); } finally { fixture.stop(); } });