Files
Jyotisha/frontend/tests/chat-session-write.test.ts
T
Jesse_Chen ff70ba87b0 feat(consult): deliver the route's strict method with the evidence instead of listing 1592 filenames
Activating the skill returned 129,651 bytes, of which 99KB was a flat list of
1,592 undifferentiated file paths against 30KB of actual method. The one line
telling the model to open the strict-workflow router sat inside that method,
so no reference was ever opened and every answer was composed from the model's
own background knowledge over server evidence.

The route is already decided server-side and the skill already states which
checklist each route requires, so the selection needs no model turn: read the
mandated sections from the hash-pinned package and hand them to the model with
the evidence they apply to. A route the router declares no checklist for is
reported as such rather than filled in with another route's.

The receipt now reports delivered sections separately from model-initiated
reads, because only one of those is under the model's control.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 20:53:07 +08:00

106 lines
4.4 KiB
TypeScript

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, referenceReads: 0, methodologySections: 0 },
steps: [{ sequence: 1, kind: "skill" as const, name: "jyotish-vedic-astrology", status: "completed" as const }],
workflow: { route: "multi-domain", status: "ready", preciseTiming: "allowed", missingLayers: [], domains: ["general", "timing"] },
techniqueTruth: "verified",
};
const workflowReceipt = {
route: "multi-domain",
status: "ready",
preciseTiming: "allowed",
missingLayers: [],
domains: ["general", "timing"] as const,
};
const parsed = chatSessionWriteSchema.parse({
...values,
messages: [{ role: "assistant", text: "回答", workflowReceipt, agentExecutionReceipt: receipt }],
});
assert.deepEqual(parsed.messages[0]?.workflowReceipt, workflowReceipt);
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/);
});