Files
Jyotisha/frontend/tests/database-synastry-full-create.test.ts
T
jesse-uxGrok (agent loop)Cursor
57ee79cc5f fix(synastry): let the same pair start another report
A finished match used to reopen the old report. Starting again now writes a new one and charges again; a report still being written is reused.

Co-Authored-By: Grok (agent loop)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 23:08:21 +08:00

199 lines
9.2 KiB
TypeScript

import assert from "node:assert/strict";
import { randomUUID } from "node:crypto";
import { spawnSync } from "node:child_process";
import { register } from "node:module";
import test from "node:test";
import { fileURLToPath } from "node:url";
import { closeLocalPostgresDataPool, createLocalPostgresDataClient } from "../src/lib/db/local-postgres-client-core.ts";
import { startPostgresFixture, type PostgresFixture } from "./helpers/postgres-fixture.ts";
// The synastry create path must reach a real reservation and a queued job
// through the self-hosted client. The model catalog is stubbed. Fictional rows only.
const runner = fileURLToPath(new URL("../scripts/db-migrate.mjs", import.meta.url));
const docker = spawnSync("docker", ["version", "--format", "{{.Server.Version}}"], { stdio: "ignore" }).status === 0;
const MODEL_ID = "synastry-fixture-model";
function applyMigrations(fixture: PostgresFixture): void {
const result = spawnSync(process.execPath, [runner], {
encoding: "utf8",
env: {
...process.env,
SCHEMA_DATABASE_URL: fixture.connectionUrl("schema_owner", "schema-owner-test-password"),
},
});
assert.equal(result.status, 0, result.stderr || result.stdout);
}
test("createSynastryFullReport reserves synastry.full and enqueues a job on the real client", {
skip: docker ? false : "docker unavailable",
}, async () => {
await register(new URL("./support/stub-server-only.mjs", import.meta.url).href, import.meta.url);
const fixture = startPostgresFixture();
const serviceUrl = fixture.connectionUrl("service_runtime", "service-runtime-test-password");
const appUrl = fixture.connectionUrl("app_runtime", "app-runtime-test-password");
const adminUrl = fixture.connectionUrl("admin_runtime", "admin-runtime-test-password");
const previousAuth = process.env.AUTH_PROVIDER;
const previousAdminUrl = process.env.ADMIN_DATABASE_URL;
process.env.AUTH_PROVIDER = "self-hosted";
process.env.ADMIN_DATABASE_URL = adminUrl;
try {
applyMigrations(fixture);
const userId = randomUUID();
const personId = randomUUID();
fixture.psql(`
insert into identity.users (id, name, email, email_verified, email_verified_at)
values ('${userId}', '虚构甲', '${userId}@example.invalid', true, now());
update public.profiles set
name = '虚构甲',
credits = 100,
birth_date = '1991-04-07',
reported_birth_time = '09:15',
birth_time_status = 'reported',
birth_time_source = 'family_exact',
latitude = 31.2,
longitude = 121.4,
timezone_offset = 8,
timezone_id = 'Asia/Shanghai',
ayanamsa = 'lahiri',
birth_place_label = '虚构市'
where id = '${userId}';
insert into public.chart_profiles (
id, user_id, role, profile, updated_at, name, birth_date, reported_birth_time,
birth_time_source, latitude, longitude, timezone_id, timezone_offset,
birth_place_label, ayanamsa, birth_time_status
) values (
'${personId}', '${userId}', 'other', '{"name":"虚构乙"}'::jsonb, now(),
'虚构乙', '1992-05-06', '10:20', 'family_exact', 22.3, 114.1,
'Asia/Shanghai', 8, '虚构港', 'lahiri', 'reported'
);
with provider as (
insert into public.model_providers (code, name, provider_type, encrypted_api_key, enabled)
values ('synastry-fixture', 'Synastry Fixture', 'openai', 'test-ciphertext', true)
returning id
), config as (
insert into public.model_configs (model_id) values ('${MODEL_ID}') returning id
)
insert into public.model_config_versions (
config_id, version, provider_id, label, provider_model, enabled, is_default, status, published_at
)
select config.id, 1, provider.id, 'Fixture', 'gpt-test', true, true, 'published', now()
from config cross join provider;
`);
const catalog = await import("../src/lib/model-catalog.ts");
catalog.setModelCatalogIoForTests({
queryAdminRows: (async () => [{
model_id: MODEL_ID,
version: 1,
label: "Fixture",
description: "",
provider_model: "gpt-test",
credit_cost: 1,
is_default: true,
provider_code: "synastry-fixture",
provider_type: "openai" as const,
base_url: null,
encrypted_api_key: "test-ciphertext",
input_cost: 0,
output_cost: 0,
context_window: null,
}]) as typeof catalog.setModelCatalogIoForTests extends (next: infer T) => void
? T extends { queryAdminRows?: infer Q } ? Q : never
: never,
decryptModelProviderApiKey: () => "sk-test",
});
catalog.invalidateLanguageModelCatalog();
const { createSynastryFullReport, readSynastryPairState } = await import("../src/lib/synastry-full-create.ts");
const user = createLocalPostgresDataClient(appUrl, { id: userId, email: `${userId}@example.invalid` });
const admin = createLocalPostgresDataClient(serviceUrl, null, "service_role");
const created = await createSynastryFullReport({
userId,
body: { reportType: "synastry_full", subjectA: "self", subjectB: personId },
supabase: user,
admin,
});
assert.equal(created.status, 202, JSON.stringify(created.body));
const report = created.body.report as { id?: string; status?: string } | undefined;
assert.equal(typeof report?.id, "string");
assert.equal(report?.status, "generating");
assert.equal(typeof created.body.jobId, "string");
assert.equal(fixture.psql(`select status from public.personal_reports where id = '${report?.id}'`), "generating");
assert.equal(fixture.psql(`select status from public.personal_report_jobs where user_id = '${userId}'`), "queued");
assert.equal(fixture.psql(`select status from public.usage_reservations where user_id = '${userId}'`), "reserved");
assert.equal(fixture.psql(`select credits from public.profiles where id = '${userId}'`), "81");
assert.equal(fixture.psql(`select payload->>'first' from public.personal_report_sections where user_id = '${userId}' and section_id = 'synastry_order'`), "self");
assert.equal(fixture.psql(`select payload->>'second' from public.personal_report_sections where user_id = '${userId}' and section_id = 'synastry_order'`), personId);
assert.equal(fixture.psql(`select subject_low from public.personal_reports where id = '${report?.id}'`), personId);
assert.equal(fixture.psql(`select subject_high from public.personal_reports where id = '${report?.id}'`), "self");
const again = await createSynastryFullReport({
userId,
body: { reportType: "synastry_full", subjectA: "self", subjectB: personId },
supabase: user,
admin,
});
assert.equal(again.status, 202, JSON.stringify(again.body));
assert.equal(again.body.reused, true);
assert.equal((again.body.report as { id?: string }).id, report?.id);
assert.equal(fixture.psql(`select credits from public.profiles where id = '${userId}'`), "81");
const hash = "d".repeat(64);
fixture.psql(`
update public.personal_reports
set status = 'ready', completed_at = now(), report_document = '{}'::jsonb,
calculation_hash = '${hash}', evidence_hash = '${hash}'
where id = '${report?.id}';
update public.personal_report_jobs
set status = 'cancelled', finished_at = now()
where user_id = '${userId}' and status = 'queued';
`);
const readyState = await readSynastryPairState({
userId,
supabase: user,
admin,
subjectA: "self",
subjectB: personId,
});
assert.equal(readyState.status, 200, JSON.stringify(readyState.body));
assert.equal(readyState.body.readyCount, 1);
assert.equal(readyState.body.generating, false);
const second = await createSynastryFullReport({
userId,
body: { reportType: "synastry_full", subjectA: "self", subjectB: personId },
supabase: user,
admin,
});
assert.equal(second.status, 202, JSON.stringify(second.body));
assert.equal(second.body.reused, undefined);
const secondId = (second.body.report as { id?: string }).id;
assert.equal(typeof secondId, "string");
assert.notEqual(secondId, report?.id);
assert.equal(fixture.psql(`select status from public.personal_reports where id = '${secondId}'`), "generating");
assert.equal(fixture.psql(`select credits from public.profiles where id = '${userId}'`), "62");
const generatingState = await readSynastryPairState({
userId,
supabase: user,
admin,
subjectA: "self",
subjectB: personId,
});
assert.equal(generatingState.body.readyCount, 1);
assert.equal(generatingState.body.generating, true);
catalog.setModelCatalogIoForTests(null);
catalog.invalidateLanguageModelCatalog();
} finally {
const database = await import("../src/lib/admin/database.ts");
await database.adminDatabasePool().end().catch(() => undefined);
if (previousAuth === undefined) delete process.env.AUTH_PROVIDER;
else process.env.AUTH_PROVIDER = previousAuth;
if (previousAdminUrl === undefined) delete process.env.ADMIN_DATABASE_URL;
else process.env.ADMIN_DATABASE_URL = previousAdminUrl;
await closeLocalPostgresDataPool(serviceUrl);
await closeLocalPostgresDataPool(appUrl);
fixture.stop();
}
});