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"); function sourceBetween(source: string, start: string, end: string): string { const startIndex = source.indexOf(start); const endIndex = source.indexOf(end, startIndex + start.length); assert.ok(startIndex >= 0, `missing start marker: ${start}`); assert.ok(endIndex > startIndex, `missing end marker: ${end}`); return source.slice(startIndex, endIndex); } 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,agentic_rectification_case_id"\)/); 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:case:\$\{caseId\}`/); 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("free Agentic rectification turns bypass reservation, completion, and cancellation settlement", () => { const reserveBilling = sourceBetween(rectificationRoute, "async reserve() {", "async complete(usage) {"); const completeBilling = sourceBetween(rectificationRoute, "async complete(usage) {", "async release() {"); const releaseBilling = sourceBetween(rectificationRoute, "async release() {", " };\n\n try {"); assert.match(rectificationRoute, /action: z\.enum\(\["opening", "message", "read_only"\]\)/); assert.equal(rectificationRoute.match(/if \(action !== "message"\)/g)?.length, 3); assert.match(reserveBilling, /if \(action !== "message"\) \{[\s\S]*return \{ success: true, status: 200 \};/); assert.match(reserveBilling, /authorizeUsage\(/); assert.match(completeBilling, /if \(action !== "message"\) return true;/); assert.match(completeBilling, /completeUsage\(/); assert.match(releaseBilling, /if \(action !== "message"\) return true;/); assert.match(releaseBilling, /releaseUsage\(/); }); 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 durable response 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 usagePayload\(usage: Promise<\{ inputTokens\?: number; outputTokens\?: number \}>\)/); assert.match(consultRoute, /const resolved = await usage;/); assert.match(consultRoute, /const actualUsage = await usagePayload\(usage\);[\s\S]*p_actual_usage: actualUsage/); assert.match(consultRoute, /function mergeUsage\(usages: Promise\[\]\): Promise \{[\s\S]*Promise\.all\(usages\)/); assert.equal(consultRoute.match(/usages\.push\(result\.totalUsage\)/g)?.length, 3); // Three agentic first streams: public/general, declared-window, and natal. // Window and natal each have a contract retry plus an answer retry; general // has only an answer retry. Every one of those model calls spends tokens. assert.equal(consultRoute.match(/usages\.push\(retried\.totalUsage\)/g)?.length, 5); assert.equal(consultRoute.match(/const retryForAnswer = async \(\) => \{/g)?.length, 3); }); 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, /eventKey: requestId,/); assert.match(consultRoute, /const actualUsage = await usagePayload\(usage\);[\s\S]*p_actual_usage: actualUsage/); 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,/); });