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 SESSION_ID = "99999999-9999-4999-8999-999999999999"; function sqlLiteral(value: string): string { return value.replaceAll("'", "''"); } function appendQuestionSql( fixture: ReturnType, userId: string, requestId: string, textSql: string, sessionId = SESSION_ID, ): string { return fixture.psql(` select coalesce(success::text, 'null') || ':' || coalesce(error_code, 'null') from public.append_consultation_question( '${userId}'::uuid, '${sqlLiteral(requestId)}', '${sessionId}'::uuid, jsonb_build_object('role', 'user', 'text', ${textSql}) ); `); } function appendQuestion( fixture: ReturnType, userId: string, requestId: string, text: string, sessionId = SESSION_ID, ): string { return appendQuestionSql(fixture, userId, requestId, `'${sqlLiteral(text)}'`, sessionId); } function seedSession( fixture: ReturnType, userId: string, messagesSql: string, ): void { fixture.psql(` insert into public.chat_sessions ( id, user_id, title, theme, model_id, messages, session_type, updated_at ) values ( '${SESSION_ID}', '${userId}', '新对话', 'general', 'test-model', ${messagesSql}, 'consultation', now() ) on conflict (id) do update set messages = excluded.messages, title = excluded.title, updated_at = now(); `); } test("append_consultation_question ignores thinking fields and enforces the physical JSON cap", { 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 20260916010000_consultation_session_capacity\.sql/); fixture.psqlAs( "identity_runtime", "identity-runtime-test-password", ` insert into identity.users (name, email, email_verified, email_verified_at) values ('Capacity User', 'capacity@example.com', true, now()); `, ); const userId = fixture.psql( "select id from identity.users where email = 'capacity@example.com'", ); assert.equal( fixture.psql(` select has_function_privilege( 'service_role', 'public.append_consultation_question(uuid, text, uuid, jsonb)', 'execute' ) || ':' || has_function_privilege( 'authenticated', 'public.append_consultation_question(uuid, text, uuid, jsonb)', 'execute' ) || ':' || has_function_privilege( 'anon', 'public.append_consultation_question(uuid, text, uuid, jsonb)', 'execute' ) `), "true:f:f", ); assert.equal( appendQuestion(fixture, userId, "missing-session", "会话不存在"), "false:session_missing", ); seedSession(fixture, userId, "'[]'::jsonb"); assert.equal(appendQuestion(fixture, userId, "append-request-1", "第一问会不会丢"), "true:null"); assert.equal(appendQuestion(fixture, userId, "append-request-1", "不该写入的重复提问"), "true:null"); assert.equal( fixture.psql(`select jsonb_array_length(messages) from public.chat_sessions where id = '${SESSION_ID}'`), "1", ); assert.equal( appendQuestionSql(fixture, userId, "too-long", "repeat('x', 16001)"), "false:invalid_question_message", ); seedSession(fixture, userId, ` ( select coalesce(jsonb_agg(elem order by n), '[]'::jsonb) from ( select n, case when n % 2 = 1 then jsonb_build_object( 'role', 'user', 'text', '短问题', 'requestId', 'think-seed-' || n::text ) else jsonb_build_object( 'role', 'assistant', 'text', '短回答', 'thinkingText', repeat('T', 4000), 'thinkingSections', jsonb_build_object('pad', repeat('S', 3000)), 'techniqueTruth', 'verified', 'workflowReceipt', jsonb_build_object( 'route', 'career', 'status', 'ready', 'preciseTiming', 'allowed', 'missingLayers', jsonb_build_array() ) ) end as elem from generate_series(1, 60) as n ) as seeded ) `); const thinkingSums = fixture.psql(` select coalesce(sum(length(coalesce(elem->>'text', ''))), 0)::text || ':' || coalesce(sum( length(coalesce(elem->>'text', '')) + length(coalesce(elem->>'thinkingText', '')) + case when elem ? 'thinkingSections' then length((elem->'thinkingSections')::text) else 0 end ), 0)::text || ':' || coalesce(sum(length(elem::text)), 0)::text from public.chat_sessions, jsonb_array_elements(coalesce(messages, '[]'::jsonb)) as elem where id = '${SESSION_ID}' `); const [textOnly, oldFormula, physical] = thinkingSums.split(":").map(Number); assert.ok(textOnly < 50_000, `visible text was ${textOnly}`); assert.ok(oldFormula > 200_000, `old combined formula was ${oldFormula}`); assert.ok(physical < 1_000_000, `physical JSON was ${physical}`); assert.equal(appendQuestion(fixture, userId, "after-thinking", "思考不该占额度"), "true:null"); assert.equal( fixture.psql(`select jsonb_array_length(messages) from public.chat_sessions where id = '${SESSION_ID}'`), "61", ); seedSession(fixture, userId, ` ( select coalesce(jsonb_agg( jsonb_build_object('role', 'assistant', 'text', repeat('x', 19900), 'requestId', n::text) order by n ), '[]'::jsonb) from generate_series(1, 10) as n ) `); const quotaBoundary = fixture.psql(` select coalesce(sum(length(coalesce(elem->>'text', ''))), 0)::text || ':' || coalesce(sum(length(elem::text)), 0)::text from public.chat_sessions, jsonb_array_elements(coalesce(messages, '[]'::jsonb)) as elem where id = '${SESSION_ID}' `); const [quotaText, quotaPhysical] = quotaBoundary.split(":").map(Number); assert.equal(quotaText, 199_000); assert.ok(quotaPhysical < 1_000_000, `quota-boundary physical JSON was ${quotaPhysical}`); assert.equal( appendQuestionSql(fixture, userId, "quota-full", "repeat('y', 1001)"), "false:session_full", ); assert.equal( fixture.psql(`select jsonb_array_length(messages) from public.chat_sessions where id = '${SESSION_ID}'`), "10", ); seedSession(fixture, userId, ` jsonb_build_array( jsonb_build_object( 'role', 'assistant', 'text', '短', 'techniqueTruth', 'verified', 'workflowReceipt', jsonb_build_object('route', 'career', 'status', 'ready', 'preciseTiming', 'allowed', 'missingLayers', jsonb_build_array()), 'agentExecutionReceipt', jsonb_build_object('pad', repeat('R', 1000001)) ) ) `); const physicalOnly = fixture.psql(` select coalesce(sum(length(coalesce(elem->>'text', ''))), 0)::text || ':' || coalesce(sum(length(elem::text)), 0)::text from public.chat_sessions, jsonb_array_elements(coalesce(messages, '[]'::jsonb)) as elem where id = '${SESSION_ID}' `); const [shortText, hugePhysical] = physicalOnly.split(":").map(Number); assert.ok(shortText < 100, `short body was ${shortText}`); assert.ok(hugePhysical > 1_000_000, `receipt pad physical JSON was ${hugePhysical}`); assert.equal( appendQuestion(fixture, userId, "physical-full", "正文很短但行已经胀了"), "false:session_full", ); assert.equal( fixture.psql(`select jsonb_array_length(messages) from public.chat_sessions where id = '${SESSION_ID}'`), "1", ); seedSession(fixture, userId, ` ( select coalesce(jsonb_agg( jsonb_build_object('role', 'user', 'text', 'x', 'requestId', n::text) order by n ), '[]'::jsonb) from generate_series(1, 200) as n ) `); assert.equal( appendQuestion(fixture, userId, "count-full", "满了就不能再写"), "false:session_full", ); assert.equal( fixture.psql(`select jsonb_array_length(messages) from public.chat_sessions where id = '${SESSION_ID}'`), "200", ); } finally { fixture.stop(); } });