Files
Jyotisha/frontend/tests/chat-session-url.test.ts
T
Jesse_Chen 54269fcfcc
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled
refactor(chat): extract home helpers, chart library, and starter surfaces
page.tsx still owns the chat main chain, but the first product surfaces now
live in their own modules so later splits can land without editing the 4k-line
Home. Source-lock tests follow the moved tokens; the orphan user-data contract
is aligned and added to the quick gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 22:58:02 +08:00

267 lines
9.8 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
SESSION_MISSING_NOTICE,
SESSION_URL_QUERY_KEY,
SESSION_URL_RETURN_STORAGE_KEY,
parseSessionUrlQuery,
persistLoginSessionReturn,
readLoginSessionReturn,
resolveBootstrapSessionSelection,
sessionHref,
writeSessionUrl,
} from "../src/lib/chat-session-url.ts";
import { homeSurface as page } from "./home-surface.ts";
const login = readFileSync(new URL("../src/components/email-otp-login.tsx", import.meta.url), "utf8");
const loginPage = readFileSync(new URL("../src/app/login/page.tsx", import.meta.url), "utf8");
const sessionA = "11111111-1111-4111-8111-111111111111";
const sessionB = "22222222-2222-4222-8222-222222222222";
const sessionC = "33333333-3333-4333-8333-333333333333";
function sourceBetween(source: string, startMarker: string, endMarker: string) {
const start = source.indexOf(startMarker);
const end = source.indexOf(endMarker, start);
assert.notEqual(start, -1, startMarker);
assert.notEqual(end, -1, endMarker);
return source.slice(start, end);
}
test("bootstrap reads a listed ?c= session and ignores login storage", () => {
assert.deepEqual(
resolveBootstrapSessionSelection({
listedIds: [sessionA, sessionB],
defaultSessionId: sessionA,
search: `?${SESSION_URL_QUERY_KEY}=${sessionB}`,
storedReturnId: sessionC,
}),
{
sessionId: sessionB,
urlAction: "keep",
missing: false,
clearStoredReturn: true,
},
);
});
test("bootstrap restores a stored login return only when the URL has no c", () => {
assert.deepEqual(
resolveBootstrapSessionSelection({
listedIds: [sessionA, sessionB],
defaultSessionId: sessionA,
search: "",
storedReturnId: sessionB,
}),
{
sessionId: sessionB,
urlAction: "replace-selected",
missing: false,
clearStoredReturn: true,
},
);
});
test("bootstrap clears an illegal or unknown session query", () => {
assert.equal(parseSessionUrlQuery("").present, false);
assert.deepEqual(parseSessionUrlQuery(`?${SESSION_URL_QUERY_KEY}=not-a-uuid`), {
present: true,
sessionId: null,
});
assert.deepEqual(
resolveBootstrapSessionSelection({
listedIds: [sessionA],
defaultSessionId: sessionA,
search: `?${SESSION_URL_QUERY_KEY}=not-a-uuid`,
storedReturnId: sessionB,
}),
{
sessionId: sessionA,
urlAction: "replace-clear",
missing: true,
clearStoredReturn: true,
},
);
assert.deepEqual(
resolveBootstrapSessionSelection({
listedIds: [sessionA],
defaultSessionId: sessionA,
search: `?${SESSION_URL_QUERY_KEY}=${sessionB}`,
storedReturnId: null,
}),
{
sessionId: sessionA,
urlAction: "replace-clear",
missing: true,
clearStoredReturn: true,
},
);
});
test("default bootstrap selection does not write a session URL", () => {
assert.deepEqual(
resolveBootstrapSessionSelection({
listedIds: [sessionA, sessionB],
defaultSessionId: sessionA,
search: "",
storedReturnId: null,
}),
{
sessionId: sessionA,
urlAction: "none",
missing: false,
clearStoredReturn: false,
},
);
const bootstrap = sourceBetween(page, "async function loadCloudData()", "void loadCloudData();");
assert.match(bootstrap, /defaultSessionId: nextSessions\[0\]\.id/);
assert.match(bootstrap, /setActiveSessionId\(bootstrapSelection\.sessionId\)/);
assert.match(bootstrap, /urlAction === "replace-clear"/);
assert.match(bootstrap, /urlAction === "replace-selected"/);
assert.doesNotMatch(bootstrap, /writeSessionUrl\([^)]*, "push"\)/);
assert.match(bootstrap, /setComposerNotice\(SESSION_MISSING_NOTICE\)/);
});
test("session href keeps sibling query keys and can drop a dead c", () => {
assert.equal(sessionHref("", sessionA), `/?${SESSION_URL_QUERY_KEY}=${sessionA}`);
assert.equal(
sessionHref("?preview=conversation", sessionA),
`/?preview=conversation&${SESSION_URL_QUERY_KEY}=${sessionA}`,
);
assert.equal(sessionHref(`?${SESSION_URL_QUERY_KEY}=${sessionA}`, null), "/");
assert.equal(
sessionHref(`?preview=conversation&${SESSION_URL_QUERY_KEY}=${sessionA}`, null),
"/?preview=conversation",
);
});
test("user session switches push history; popstate reuses selectSession without a second push", () => {
const selectSession = sourceBetween(
page,
"function selectSession(sessionId: string)",
"async function selectSessionModel",
);
assert.match(selectSession, /sessionSelectionSource\.current === "user"/);
assert.match(selectSession, /writeSessionUrl\(sessionId, "push"\)/);
assert.match(selectSession, /sessionSelectionSource\.current = "history";\n selectSession\(/);
assert.equal((selectSession.match(/writeSessionUrl\([^)]*, "push"\)/g) ?? []).length, 1);
assert.match(page, /window\.addEventListener\("popstate", onPopState\)/);
});
test("creating and leaving a session keep the address bar in sync", () => {
const startNewChat = sourceBetween(page, "async function startNewChat()", "function selectSession(");
assert.match(startNewChat, /writeSessionUrl\(nextSession\.id, "push"\)/);
assert.match(startNewChat, /window\.history\.replaceState\(null, "", previousHref\)/);
const deleteSession = sourceBetween(page, "async function deleteSession(", "function togglePinnedSession");
assert.match(deleteSession, /writeSessionUrl\(fallbackId \|\| null, "replace"\)/);
const archiveSession = sourceBetween(page, "function toggleArchivedSession(", "async function shareSession");
assert.match(archiveSession, /writeSessionUrl\(fallbackId \|\| null, "replace"\)/);
const recovery = sourceBetween(
page,
"if (status.status === \"completed\") {",
"pendingConsultation.current = null;",
);
assert.match(recovery, /setActiveSessionId\(\(current\) => current \|\| detailed\.id\)/);
assert.doesNotMatch(recovery, /writeSessionUrl/);
});
test("401 login redirect stashes the current session id without changing the login page", () => {
const redirect = sourceBetween(page, "function redirectToLogin(): never {", "function waitForUndoWindow");
// Former value: the body was only `window.location.replace("/login")`.
// 401 now stashes a UUID ?c= so returning to `/` can restore the session
// without adding a `next` query to the login page.
assert.match(redirect, /persistLoginSessionReturn\(\);/);
assert.match(redirect, /window\.location\.replace\("\/login"\);/);
assert.doesNotMatch(login, /sessionStorage|SESSION_URL_RETURN|searchParams\.get\("next"\)/);
assert.match(login, /successPath\?: "\/" \| "\/admin"/);
assert.doesNotMatch(loginPage, /next=/);
assert.equal(SESSION_URL_RETURN_STORAGE_KEY, "jyotisha.session-url-return");
});
test("home stays a client-read query on a static route", () => {
const lib = readFileSync(new URL("../src/lib/chat-session-url.ts", import.meta.url), "utf8");
assert.doesNotMatch(page, /useSearchParams/);
assert.doesNotMatch(page, /export const dynamic/);
assert.match(page, /writeSessionUrl\(sessionId, "push"\)/);
assert.match(page, /window\.history\.replaceState/);
assert.match(lib, /history\.pushState\(null, "", next\)/);
assert.match(lib, /history\.replaceState\(null, "", next\)/);
const preview = sourceBetween(
page,
"if (previewMode) {",
"const [nextAccount, modelCatalogResult, sessionsPayload]",
);
assert.doesNotMatch(preview, /writeSessionUrl|persistLoginSessionReturn|readLoginSessionReturn/);
assert.match(page, /SESSION_MISSING_NOTICE/);
assert.equal(SESSION_MISSING_NOTICE, "该对话不存在或已被删除");
});
test("login return storage only accepts a UUID session id", () => {
const memory = new Map<string, string>();
const previousWindow = globalThis.window;
Object.defineProperty(globalThis, "window", {
configurable: true,
value: {
location: { search: `?${SESSION_URL_QUERY_KEY}=${sessionA}` },
},
});
Object.defineProperty(globalThis, "sessionStorage", {
configurable: true,
value: {
setItem(key: string, value: string) { memory.set(key, value); },
getItem(key: string) { return memory.get(key) ?? null; },
removeItem(key: string) { memory.delete(key); },
},
});
try {
persistLoginSessionReturn();
assert.equal(readLoginSessionReturn(), sessionA);
memory.set(SESSION_URL_RETURN_STORAGE_KEY, "not-a-uuid");
assert.equal(readLoginSessionReturn(), null);
} finally {
if (previousWindow === undefined) {
Reflect.deleteProperty(globalThis, "window");
} else {
Object.defineProperty(globalThis, "window", { configurable: true, value: previousWindow });
}
Reflect.deleteProperty(globalThis, "sessionStorage");
}
});
test("writeSessionUrl no-ops when the address is already the target", () => {
const calls: string[] = [];
const previousWindow = globalThis.window;
Object.defineProperty(globalThis, "window", {
configurable: true,
value: {
location: { pathname: "/", search: `?${SESSION_URL_QUERY_KEY}=${sessionA}` },
history: {
pushState(_state: unknown, _unused: string, url: string) { calls.push(`push:${url}`); },
replaceState(_state: unknown, _unused: string, url: string) { calls.push(`replace:${url}`); },
},
},
});
try {
writeSessionUrl(sessionA, "push");
assert.deepEqual(calls, []);
writeSessionUrl(sessionB, "push");
assert.deepEqual(calls, [`push:/?${SESSION_URL_QUERY_KEY}=${sessionB}`]);
writeSessionUrl(null, "replace");
assert.deepEqual(calls, [
`push:/?${SESSION_URL_QUERY_KEY}=${sessionB}`,
"replace:/",
]);
} finally {
if (previousWindow === undefined) {
Reflect.deleteProperty(globalThis, "window");
} else {
Object.defineProperty(globalThis, "window", { configurable: true, value: previousWindow });
}
}
});