import assert from "node:assert/strict"; import { existsSync, readFileSync } from "node:fs"; import path from "node:path"; import test from "node:test"; import ts from "typescript"; const auditInputsPath = path.join(process.cwd(), "convex", "auditInputs.ts"); const auditInputsSource = existsSync(auditInputsPath) ? readFileSync(auditInputsPath, "utf8") : ""; const sourceFile = ts.createSourceFile( "auditInputs.ts", auditInputsSource, ts.ScriptTarget.ES2022, true, ts.ScriptKind.TS, ); function getExportedConstNames(file: ts.SourceFile) { const names = new Set(); const visit = (node: ts.Node) => { if (ts.isVariableStatement(node)) { const isExported = node.modifiers?.some( (mod) => mod.kind === ts.SyntaxKind.ExportKeyword, ); if (!isExported) { ts.forEachChild(node, visit); return; } const isConst = node.declarationList.flags & ts.NodeFlags.Const; if (!isConst) { ts.forEachChild(node, visit); return; } for (const declaration of node.declarationList.declarations) { if (ts.isIdentifier(declaration.name)) { names.add(declaration.name.text); } } } ts.forEachChild(node, visit); }; ts.forEachChild(file, visit); return names; } function hasPattern(source: string, pattern: RegExp) { return pattern.test(source); } function extractExportSource(name: string) { const marker = `export const ${name} = `; const declarationIndex = auditInputsSource.indexOf(marker); assert.notEqual(declarationIndex, -1, `Expected declaration for ${name}`); const openBraceIndex = auditInputsSource.indexOf("{", declarationIndex); let depth = 0; let end = -1; for (let index = openBraceIndex; index < auditInputsSource.length; index += 1) { const char = auditInputsSource[index]; if (char === "{") { depth += 1; } else if (char === "}") { depth -= 1; if (depth === 0) { end = index; break; } } } assert.notEqual(end, -1, `Expected balanced braces for ${name}`); return auditInputsSource.slice(openBraceIndex, end + 1); } test("auditInputs module exists and exports pageSpeed input translator query", () => { assert.equal( existsSync(auditInputsPath), true, "convex/auditInputs.ts should be present", ); const exports = getExportedConstNames(sourceFile); assert.equal( exports.has("getPageSpeedAuditInputs"), true, "auditInputs module should export getPageSpeedAuditInputs", ); }); test("auditInputs module calls buildPageSpeedAuditInputs for lead/audit PageSpeed results", () => { const querySource = extractExportSource("getPageSpeedAuditInputs"); assert.equal( hasPattern( auditInputsSource, /buildPageSpeedAuditInputs[\s\S]*?from\s*["']\.\.\/lib\/pagespeed-audit-input["']/, ), true, "auditInputs should import buildPageSpeedAuditInputs", ); assert.equal( hasPattern(querySource, /buildPageSpeedAuditInputs\(results\.map\(/), true, "auditInputs should call buildPageSpeedAuditInputs", ); }); test("auditInputs query fetches stored pageSpeedResults by lead or audit", () => { const querySource = extractExportSource("getPageSpeedAuditInputs"); assert.equal( hasPattern( auditInputsSource, /getPageSpeedAuditInputs\s*=\s*internalQuery\s*\(/, ), true, "getPageSpeedAuditInputs should be registered as an internal query", ); assert.equal( hasPattern( querySource, /handler\s*:\s*async\s*\(/, ), true, "getPageSpeedAuditInputs source block should include an async handler", ); assert.equal( hasPattern( auditInputsSource, /ctx\.db[\s\S]*?\.query\([\s\S]*?["']pageSpeedResults["']\s*\)/, ), true, "auditInputs should read from pageSpeedResults table", ); assert.equal( hasPattern( auditInputsSource, /withIndex\(["']by_auditId["'][\s\S]*?eq\([\s\S]*?auditId[\s\S]*?\)/, ), true, "auditInputs should support audit-scoped PageSpeed results", ); assert.equal( hasPattern( auditInputsSource, /withIndex\(["']by_leadId["'][\s\S]*?eq\([\s\S]*?leadId[\s\S]*?\)/, ), true, "auditInputs should support lead-scoped PageSpeed results", ); }); test("auditInputs returns only plain-language prompt fields", () => { const querySource = extractExportSource("getPageSpeedAuditInputs"); assert.equal( hasPattern( querySource, /technicalSignals\s*:\s*string\[\][\s\S]*customerImplications\s*:\s*string\[\][\s\S]*internalNotes\s*:\s*string\[\]/, ), true, "Return type should expose only technicalSignals, customerImplications, and internalNotes", ); const returnConstruction = querySource.match( /buildPageSpeedAuditInputs\([\s\S]*?\);/, ); assert.notEqual( returnConstruction, null, "auditInputs should return buildPageSpeedAuditInputs output", ); assert.equal( /rawStorageId/.test(returnConstruction?.[0] ?? ""), false, "Returned fields must not include rawStorageId", ); assert.equal( /\bscores\b/.test(returnConstruction?.[0] ?? ""), false, "Returned fields must not include scores", ); });