Skip to content

Commit f159c69

Browse files
committed
test(webapp): guard against a raw-SQL reference to the dropped chats.messages
The transcript moved out of `chats.messages` into `chat_messages`. TypeScript already rejects a reference through the Drizzle schema, but a raw-SQL reference compiles fine and only fails at runtime, and the earlier guard test went away with the column. Zero hits today is the point: the test exists so a reintroduction is caught rather than deployed.
1 parent d8fda07 commit f159c69

1 file changed

Lines changed: 75 additions & 0 deletions

File tree

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { readdirSync, readFileSync, statSync } from "node:fs";
2+
import path from "node:path";
3+
import { describe, expect, it } from "vitest";
4+
5+
/**
6+
* `chats.messages` is gone: the transcript lives in `chat_messages`, one row per message.
7+
*
8+
* TypeScript already catches a reference through the Drizzle schema — the column isn't
9+
* there, so it doesn't compile. A raw-SQL reference compiles fine and fails at runtime,
10+
* which is the hole this scan covers. Zero hits today is the point; the test exists so a
11+
* reintroduction is caught rather than deployed.
12+
*/
13+
14+
const ROOT = path.resolve(__dirname, "../../..");
15+
16+
const SCANNED = [
17+
"apps/webapp/app",
18+
"internal-packages/dashboard-agent/src",
19+
"internal-packages/dashboard-agent-db/src",
20+
];
21+
22+
/** A qualified reference to the dropped column, in any of the spellings Postgres accepts. */
23+
const QUALIFIED = /"?\bchats"?\s*\.\s*"?messages"?/i;
24+
25+
/** An unqualified one, inside a literal that is plainly SQL against `chats`. */
26+
const SQL_LITERAL = /`[^`]*`|"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'/g;
27+
const SQL_VERB = /\b(select|insert\s+into|update|delete\s+from)\b/i;
28+
const NAMES_CHATS = /(?<![\w."])chats\b/i;
29+
// Word-boundary on both sides and no leading `_`, so `chat_messages` is not a hit.
30+
const BARE_MESSAGES = /(?<![\w."])messages\b/i;
31+
32+
function sourceFiles(dir: string): string[] {
33+
const found: string[] = [];
34+
for (const entry of readdirSync(dir)) {
35+
const full = path.join(dir, entry);
36+
if (statSync(full).isDirectory()) {
37+
if (entry === "node_modules" || entry === "drizzle") continue;
38+
found.push(...sourceFiles(full));
39+
continue;
40+
}
41+
if (!/\.(ts|tsx)$/.test(entry)) continue;
42+
// Tests are excluded: they are allowed to name the old column to describe it.
43+
if (/\.(test|eval|spec)\.tsx?$/.test(entry)) continue;
44+
found.push(full);
45+
}
46+
return found;
47+
}
48+
49+
function offences(file: string): string[] {
50+
const text = readFileSync(file, "utf8");
51+
const found: string[] = [];
52+
53+
for (const [index, line] of text.split("\n").entries()) {
54+
if (QUALIFIED.test(line))
55+
found.push(`${path.relative(ROOT, file)}:${index + 1} ${line.trim()}`);
56+
}
57+
58+
for (const literal of text.match(SQL_LITERAL) ?? []) {
59+
if (!SQL_VERB.test(literal) || !NAMES_CHATS.test(literal)) continue;
60+
if (!BARE_MESSAGES.test(literal)) continue;
61+
found.push(`${path.relative(ROOT, file)} (sql literal) ${literal.slice(0, 120)}`);
62+
}
63+
64+
return found;
65+
}
66+
67+
describe("the dropped chats.messages column", () => {
68+
it("is not referenced by any production source, including in raw SQL", () => {
69+
const files = SCANNED.flatMap((dir) => sourceFiles(path.join(ROOT, dir)));
70+
// A scan that found nothing to read would pass vacuously.
71+
expect(files.length).toBeGreaterThan(200);
72+
73+
expect(files.flatMap(offences)).toEqual([]);
74+
});
75+
});

0 commit comments

Comments
 (0)