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("popstate to a missing session query reuses selectSession side effects for the default chat", () => { const pop = sourceBetween( page, "applySessionPopStateRef.current =", "async function selectSessionModel", ); // Former empty-id branch: `if (!requestedId) { setActiveSessionId(""); return; }` // Back to `/` with a listed chat now runs the same selectSession side effects // as a sidebar click, still tagged history so it does not push a second URL. assert.match( pop, /if \(!requestedId\) \{\n if \(fallbackId\) \{\n sessionSelectionSource\.current = "history";\n selectSession\(fallbackId\);\n \} else \{\n setActiveSessionId\(""\);\n \}\n return;/, ); assert.equal((pop.match(/writeSessionUrl\([^)]*, "push"\)/g) ?? []).length, 0); }); 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(); 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 }); } } });