Opening a saved birth-time session no longer restamps the title or updatedAt from the wall clock. Occupies BUG-699, recurrence of BUG-553.
105 lines
4.0 KiB
TypeScript
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, []);
|
|
});
|