Files
Jyotisha/frontend/tests/chat-session-authority.test.ts
T
Jesse_Chen ce6a8a7e72
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled
fix(chat): make cloud the only truth for charts, synastry, and pin/archive
Local fallbacks were creating fake saves and resurrecting deleted rows.
Pin and archive now live on chat_sessions so they follow the account.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 21:38:21 +08:00

80 lines
4.5 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
const page = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
const listRoute = 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");
const consultRoute = readFileSync(new URL("../src/app/api/consult/route.ts", import.meta.url), "utf8");
const sql = readFileSync(
new URL("../supabase/migrations/20260901010000_append_consultation_question.sql", import.meta.url),
"utf8",
);
const sendSource = page.slice(page.indexOf(" async function send("), page.indexOf("\n\n consultationReplay.current"));
test("session list GET omits messages while detail GET returns them", () => {
// Former list/detail column strings ended at updated_at; pinned and archived_at
// were added when those flags moved off localStorage.
assert.match(
listRoute,
/SESSION_LIST_COLUMNS = "id,title,theme,model_id,session_type,rectification_case_id,chart_profile_id,chart_profile_name,chart_profile_role,updated_at,pinned,archived_at"/,
);
assert.doesNotMatch(listRoute, /select\(SESSION_LIST_COLUMNS\)[\s\S]*messages/);
assert.match(itemRoute, /export async function GET/);
assert.match(
itemRoute,
/sessionSelect = "id,title,theme,model_id,messages,session_type,rectification_case_id,chart_profile_id,chart_profile_name,chart_profile_role,updated_at,pinned,archived_at"/,
);
assert.match(page, /async function fetchSessionDetail\(/);
assert.match(page, /async function ensureSessionMessages\(/);
assert.match(page, /session-messages-loading/);
assert.match(page, /messagesHydrated: messagesPresent/);
});
test("consult reads stored history and never applies the client history field", () => {
assert.match(consultRoute, /history: z\s*\.array\([\s\S]*?\)\s*\.max\(20\)\s*\.optional\(\)\s*\.default\(\[\]\)/);
assert.match(consultRoute, /const storedHistory = consultationHistoryFromStoredMessages\(chatSession\.messages\)/);
assert.match(consultRoute, /const history = storedHistory/);
assert.doesNotMatch(consultRoute, /parsed\.data\.history/);
assert.match(sendSource, /history: currentSession\.messages\.slice\(-12\)/);
});
test("consult appends the user question after reserve and returns session_full", () => {
assert.match(consultRoute, /accounting\.rpc\("append_consultation_question"/);
assert.match(consultRoute, /error_code === "session_full"/);
assert.match(consultRoute, /code: "session_full"/);
assert.match(consultRoute, /这段对话已写满,开个新对话继续吧/);
assert.match(sql, /create or replace function public\.append_consultation_question/);
assert.match(sql, /pg_advisory_xact_lock/);
assert.match(sql, /session_type = 'consultation'/);
assert.match(sql, /'session_full'::text/);
assert.match(sql, /grant execute on function public\.append_consultation_question/);
assert.match(sendSource, /caught\.code === "session_full"/);
assert.match(sendSource, /label: "开新对话"/);
assert.match(sendSource, /continueInNewChat\(\{ question: originalQuestion, theme \}\)/);
});
test("PATCH compatibility accepts and ignores a legacy messages write", () => {
assert.match(itemRoute, /logIgnoredSessionMessages\(id, user\.id, ignoredMessageCount\(payload\)\)/);
assert.match(itemRoute, /if \(!values && payloadHasMessages\(payload\)\) \{/);
assert.match(itemRoute, /return NextResponse\.json\(\{ ok: true \}\)/);
assert.doesNotMatch(itemRoute, /\.update\(\{[\s\S]*messages:/);
});
test("pin and archive flags are session metadata, not localStorage", () => {
const pinSql = readFileSync(
new URL("../supabase/migrations/20260901020000_chat_session_pin_archive.sql", import.meta.url),
"utf8",
);
assert.match(pinSql, /add column if not exists pinned boolean not null default false/);
assert.match(pinSql, /add column if not exists archived_at timestamptz null/);
assert.match(pinSql, /grant update \(pinned, archived_at\)/);
assert.match(page, /function applyLegacySessionControls\(/);
assert.match(page, /writeChatSession\(sessionId, \{ pinned: nextPinned \}, "update"\)/);
assert.match(page, /writeChatSession\(sessionId, \{ archived_at: nextArchivedAt \}, "update"\)/);
assert.match(page, /discardLegacyCloudMirrorKeys\(/);
assert.doesNotMatch(page, /setPinnedSessionIds/);
assert.doesNotMatch(page, /localStorage\.setItem\(`\$\{prefix\}pinned`/);
assert.doesNotMatch(page, /writeSynastryHistory\(/);
});