import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { completeUsage } from "../src/lib/consultation-billing.ts"; const root = new URL("../", import.meta.url); const rectificationRoute = readFileSync(new URL("src/app/api/rectification/agent/route.ts", root), "utf8"); const consultRoute = readFileSync(new URL("src/app/api/consult/route.ts", root), "utf8"); const packagesRoute = readFileSync(new URL("src/app/api/admin/packages/route.ts", root), "utf8"); test("Agentic rectification reuses one case-level usage authorization and the session-pinned model version", () => { assert.match(rectificationRoute, /select\("id,messages,session_type,model_id,model_config_version"\)/); assert.match(rectificationRoute, /resolveSessionLanguageModel\(\s*chatSession\.model_id,\s*chatSession\.model_config_version,?\s*\)/); assert.match(rectificationRoute, /modelConfigVersion: selectedModel\.configVersion/); assert.doesNotMatch(rectificationRoute, /loadLanguageModelCatalog|resolveLanguageModelFromCatalog|\bresolveLanguageModel\(|\bdefaultLanguageModel\(/); assert.match(rectificationRoute, /const billingRequestPrefix = `rectification:\$\{sessionId\}`/); assert.match(rectificationRoute, /from\("usage_reservations"\)[\s\S]*\.eq\("feature_key", "rectification"\)[\s\S]*\.like\("request_id", `\$\{billingRequestPrefix\}%`\)/); assert.match(rectificationRoute, /return reservations.length === 0[\s\S]*`\$\{billingRequestPrefix\}:retry:\$\{reservations.length\}`/); assert.match(rectificationRoute, /authorizeUsage\(accounting, \{[\s\S]*requestId: billingRequestId/); assert.match(rectificationRoute, /completeUsage\(accounting, userId, billingRequestId,/); assert.match(rectificationRoute, /releaseUsage\(accounting, userId, billingRequestId,/); }); test("standard consultation resolves and settles the session-pinned model version", () => { assert.match(consultRoute, /sessionId: z\.string\(\)\.uuid\(\)/); assert.match(consultRoute, /select\("id,model_id,model_config_version,session_type"\)/); assert.match(consultRoute, /resolveSessionLanguageModel\(\s*chatSession\.model_id,\s*chatSession\.model_config_version,?\s*\)/); assert.match(consultRoute, /actualModelId: selectedModel\.id/); assert.match(consultRoute, /modelConfigVersion: selectedModel\.configVersion/); }); test("standard consultation awaits real usage before its only permanent settlement", () => { assert.doesNotMatch(consultRoute, /recordActualUsage|void usage\.then/); assert.doesNotMatch(consultRoute, /inputTokens: 0,[\s\S]*outputTokens: 0,[\s\S]*costMicrousd: 0/); assert.match(consultRoute, /async function complete\(usage: Promise<\{ inputTokens\?: number; outputTokens\?: number \}>\)/); assert.match(consultRoute, /const resolved = await usage;/); assert.match(consultRoute, /await completeUsage\(accounting, userId, requestId, \{[\s\S]*inputTokens,[\s\S]*outputTokens,[\s\S]*costMicrousd/); assert.match(consultRoute, /const completeWithUsage = \(\) => complete\(result\.totalUsage\)/g); }); test("standard consultation forwards its stable reservation request as the usage event key", async () => { const eventKey = "00000000-0000-4000-8000-000000000002"; const calls: Record[] = []; const accounting = { async rpc(_rpcName: string, args: Record) { calls.push(args); return { data: { success: true, reservation_id: "00000000-0000-4000-8000-000000000003", credits: 9, error_code: null }, error: null, }; }, }; const usage = { eventKey, actualModelId: "model", inputTokens: 1, outputTokens: 2, costMicrousd: 3, durationMs: 4 }; await completeUsage(accounting, "00000000-0000-4000-8000-000000000001", eventKey, usage); await completeUsage(accounting, "00000000-0000-4000-8000-000000000001", eventKey, usage); assert.deepEqual(calls.map((call) => (call.p_actual_usage as { eventKey: string }).eventKey), [eventKey, eventKey]); assert.match(consultRoute, /completeUsage\(accounting, userId, requestId, \{[\s\S]*eventKey: requestId,/); assert.doesNotMatch(consultRoute, /eventKey:\s*(?:globalThis\.)?crypto\.randomUUID\(\)/); }); test("legacy admin packages cannot mutate detached payment_packages", () => { assert.doesNotMatch(packagesRoute, /payment_packages|queryAdminRows|insert into|update public/); assert.match(packagesRoute, /export function GET\(request: Request\)/); assert.match(packagesRoute, /const replacement = "\/api\/admin\/products"/); assert.match(packagesRoute, /NextResponse\.redirect\(new URL\(replacement, request\.url\), 308\)/); for (const method of ["POST", "PATCH", "DELETE"]) { assert.match(packagesRoute, new RegExp(`export function ${method}\\(\\)`)); } assert.match(packagesRoute, /status: 410/); assert.match(packagesRoute, /error: "旧套餐写接口已停用,请使用统一商品管理。",[\s\S]*replacement,/); });