Files
Jyotisha/frontend/tests/session-open-preserves-identity.test.ts
T
jesse-ux 2d7698ead3
Independent Staging Quality Gate / validate (push) Successful in 11m43s
Independent Staging Quality Gate / publish (push) Successful in 2m3s
fix(chat): keep stored rectification titles on open
Opening a saved birth-time session no longer restamps the title or updatedAt from the wall clock. Occupies BUG-699, recurrence of BUG-553.
2026-09-15 14:48:03 +08:00

105 lines
4.0 KiB
TypeScript

import assert from "node:assert/strict";
import { readdirSync, readFileSync, statSync } from "node:fs";
import { extname, join } from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
import { rectificationOpenIdentity } from "../src/lib/rectification-session-open.ts";
const surface = readFileSync(
new URL("../src/hooks/use-rectification-surface.ts", import.meta.url),
"utf8",
);
const srcRoot = fileURLToPath(new URL("../src", import.meta.url));
function owningObjectLiteral(source: string, index: number): string {
let depth = 0;
for (let i = index; i >= 0; i -= 1) {
const char = source[i];
if (char === "}") depth += 1;
else if (char === "{") {
if (depth === 0) {
let open = 0;
for (let j = i; j < source.length; j += 1) {
if (source[j] === "{") open += 1;
else if (source[j] === "}") {
open -= 1;
if (open === 0) return source.slice(i, j + 1);
}
}
return source.slice(i);
}
depth -= 1;
}
}
return "";
}
function walkTypeScript(directory: string): string[] {
const files: string[] = [];
for (const entry of readdirSync(directory)) {
const path = join(directory, entry);
const stat = statSync(path);
if (stat.isDirectory()) files.push(...walkTypeScript(path));
else if (stat.isFile() && (extname(path) === ".ts" || extname(path) === ".tsx")) files.push(path);
}
return files;
}
function unconditionalExistingSessionClocks(source: string): string[] {
const hits: string[] = [];
const needle = /updatedAt:\s*timestamp\(\)/g;
for (const match of source.matchAll(needle)) {
const index = match.index ?? 0;
const lineStart = source.lastIndexOf("\n", index) + 1;
const line = source.slice(lineStart, source.indexOf("\n", index));
if (/\bexisting\?\.updatedAt\s*\?\?/.test(line)) continue;
const literal = owningObjectLiteral(source, index);
if (!/\bid\s*:/.test(literal)) continue;
if (!/\bsessionType\s*:/.test(literal) && !/\bmessages\s*:/.test(literal)) continue;
if (/randomUUID\s*\(/.test(literal)) continue;
if (/session-clock-exempt/.test(literal)) continue;
hits.push(literal.slice(0, 180));
}
return hits;
}
test("opening a stored rectification session keeps its title and updatedAt", () => {
const existing = { title: "9月12日 · 生时校正", updatedAt: 1_757_635_445_000 };
const minted = { title: "9月15日 · 生时校正", updatedAt: 1_757_894_400_000 };
assert.deepEqual(rectificationOpenIdentity(existing, minted), existing);
});
test("minting a new rectification session still uses the wall-clock title", () => {
const minted = { title: "9月15日 · 生时校正", updatedAt: 1_757_894_400_000 };
assert.deepEqual(rectificationOpenIdentity(undefined, minted), minted);
});
test("openRectificationCase inherits identity from existing before minting", () => {
const open = surface.slice(
surface.indexOf("async function openRectificationCase"),
surface.indexOf("async function openRectificationFromHomepage"),
);
const merged = open.slice(open.indexOf("const merged: ChatSession = {"), open.indexOf("setSessions((current)"));
assert.match(open, /rectificationOpenIdentity\(\s*existing,/);
assert.match(merged, /title: openIdentity\.title/);
assert.match(merged, /updatedAt: openIdentity\.updatedAt/);
assert.doesNotMatch(merged, /resolveSessionTitle/);
assert.doesNotMatch(merged, /updatedAt: timestamp\(\)/);
assert.match(open, /pinned: existing\?\.pinned \?\? false/);
assert.match(open, /void persistSession\(merged\)/);
});
test("ChatSession literals for an existing id cannot stamp updatedAt with the wall clock", () => {
const offenders: string[] = [];
for (const directory of ["hooks", "lib"]) {
for (const file of walkTypeScript(join(srcRoot, directory))) {
const source = readFileSync(file, "utf8");
for (const hit of unconditionalExistingSessionClocks(source)) {
offenders.push(`${file}: ${hit.replace(/\s+/g, " ")}`);
}
}
}
assert.deepEqual(offenders, []);
});