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>
199 lines
9.2 KiB
TypeScript
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();
|
|
}
|
|
});
|