import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { chatSessionWriteSchema, writeChatSession, type ChatSessionWrite } from "../src/lib/chat-session-write-contract.ts"; const sessionId = "11111111-1111-4111-8111-111111111111"; const values = { title: "事业方向", theme: "career", model_id: "gpt-test", messages: [{ role: "user", text: "你好" }], session_type: "consultation", rectification_case_id: null, updated_at: "2026-07-22T00:00:00.000Z", } satisfies ChatSessionWrite; test("chat session schema preserves the safe agent execution receipt", () => { const receipt = { runId: "run-1", runtime: "mastra-agentic" as const, skill: { name: "jyotish-vedic-astrology" as const, loaded: true }, steps: [{ sequence: 1, kind: "skill" as const, name: "jyotish-vedic-astrology", status: "completed" as const }], workflow: { route: "career", status: "ready", preciseTiming: "blocked", missingLayers: [] }, techniqueTruth: "verified", }; const parsed = chatSessionWriteSchema.parse({ ...values, messages: [{ role: "assistant", text: "回答", agentExecutionReceipt: receipt }], }); assert.deepEqual(parsed.messages[0]?.agentExecutionReceipt, receipt); }); test("chat session writes use same-origin API instead of browser-to-Supabase requests", async () => { const calls: Array<{ url: string; init?: RequestInit }> = []; await writeChatSession(sessionId, values, "update", async (url, init) => { calls.push({ url: String(url), init }); return Response.json({ ok: true }); }); assert.equal(calls.length, 1); assert.equal(calls[0]?.url, `/api/sessions/${sessionId}`); assert.equal(calls[0]?.init?.method, "PATCH"); assert.equal(calls[0]?.init?.credentials, "same-origin"); }); test("transient Load failed is retried and never exposed as raw browser copy", async () => { let attempts = 0; await assert.rejects( writeChatSession(sessionId, values, "update", async () => { attempts += 1; throw new TypeError("Load failed"); }), (error: unknown) => error instanceof Error && error.message === "网络暂时不可用,云端记录尚未更新", ); assert.equal(attempts, 2); }); test("owner or validation failures are not retried", async () => { let attempts = 0; await assert.rejects( writeChatSession(sessionId, values, "update", async () => { attempts += 1; return Response.json({ error: "聊天记录不存在或已被删除" }, { status: 404 }); }), /聊天记录不存在或已被删除/, ); assert.equal(attempts, 1); }); test("session API owns create and update while answer UI keeps sync failures out of reply errors", () => { const page = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8"); const collectionRoute = readFileSync(new URL("../src/app/api/sessions/route.ts", import.meta.url), "utf8"); const itemRoute = readFileSync(new URL("../src/app/api/sessions/[id]/route.ts", import.meta.url), "utf8"); assert.match(page, /writeChatSession\(session\.id, values, mode\)/); assert.doesNotMatch(page, /云端同步失败.*回答仍保留在当前页面/); assert.match(collectionRoute, /export async function POST/); assert.match(itemRoute, /export async function PATCH/); assert.match(itemRoute, /\.eq\("user_id", user\.id\)/); }); test("self-hosted staging bootstrap reads profile and sessions through same-origin APIs", () => { const page = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8"); const accountRoute = readFileSync(new URL("../src/app/api/account/route.ts", import.meta.url), "utf8"); assert.doesNotMatch(page, /createBrowserSupabaseClient/); assert.match(page, /fetch\("\/api\/account"/); assert.match(page, /fetch\("\/api\/sessions"/); assert.match(page, /writeChatSession\(initialSession\.id,[\s\S]*?"create"\)/); assert.match(page, /fetch\(`\/api\/sessions\/\$\{encodeURIComponent\(sessionId\)\}`/); assert.match(accountRoute, /AUTH_PROVIDER\?\.trim\(\) === "self-hosted"/); assert.match(accountRoute, /profile,/); assert.doesNotMatch(accountRoute, /rectificationCase/); });