import assert from "node:assert/strict"; import { readdirSync, readFileSync, statSync } from "node:fs"; import path from "node:path"; import test from "node:test"; import { fileURLToPath } from "node:url"; // BUG-1399: a feature called `.gte()` on the self-hosted database client, which // did not implement it. Unit tests passed because they used a hand-written query // stub that had the method. This test reads every PostgREST-style filter or // modifier call in frontend/src and requires the self-hosted builder to declare it. const frontendDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const srcDir = path.join(frontendDir, "src"); const POSTGREST_METHODS = [ "eq", "neq", "gt", "gte", "lt", "lte", "like", "ilike", "is", "in", "not", "or", "contains", "containedBy", "overlaps", "range", "rangeGt", "rangeGte", "rangeLt", "rangeLte", "rangeAdjacent", "textSearch", "match", "filter", "order", "limit", "single", "maybeSingle", "csv", "abortSignal", "returns", "throwOnError", ] as const; function sourceFiles(dir: string): string[] { return readdirSync(dir).flatMap((name) => { const full = path.join(dir, name); if (statSync(full).isDirectory()) return sourceFiles(full); return /\.(ts|tsx)$/.test(name) ? [full] : []; }); } function builderMethods(): Set { const source = readFileSync(path.join(srcDir, "lib/db/local-postgres-client-core.ts"), "utf8"); const start = source.indexOf("class LocalPostgresQueryBuilder"); const end = source.indexOf("\nexport class LocalPostgresDataClient", start); assert.ok(start >= 0 && end > start, "LocalPostgresQueryBuilder class not found"); return new Set([...source.slice(start, end).matchAll(/^ {2}(?:async )?([a-zA-Z]+)\(/gm)].map((match) => match[1])); } // Lines that are clearly not database query chains: zod schemas and plain arrays. // `.contains(` is left out of the scan: DOM `element.contains()` uses the same name. function isDatabaseChain(line: string): boolean { if (/\bz\.|\.number\(\)|\.string\(\)|\.array\(|\.date\(\)/.test(line)) return false; return true; } test("every PostgREST method used in frontend/src exists on the self-hosted query builder", () => { const implemented = builderMethods(); const missing: string[] = []; const builderOnly = new Set(["gte", "lt", "lte", "gt", "neq", "ilike", "maybeSingle", "containedBy", "overlaps", "rangeGt", "rangeGte", "rangeLt", "rangeLte", "rangeAdjacent", "textSearch", "csv", "throwOnError"]); for (const file of sourceFiles(srcDir)) { const lines = readFileSync(file, "utf8").split("\n"); lines.forEach((line, index) => { if (!isDatabaseChain(line)) return; for (const method of POSTGREST_METHODS) { if (!builderOnly.has(method)) continue; if (new RegExp(`\\.${method}\\(`).test(line) && !implemented.has(method)) { missing.push(`${path.relative(frontendDir, file)}:${index + 1} .${method}()`); } } }); } assert.deepEqual(missing, [], `self-hosted builder lacks methods used in src:\n${missing.join("\n")}`); }); test("the self-hosted builder declares gte and lt (BUG-1399)", () => { const implemented = builderMethods(); assert.ok(implemented.has("gte")); assert.ok(implemented.has("lt")); });