Files
pitchfast/tests/pagespeed-audit-input-integration.test.ts

192 lines
5.1 KiB
TypeScript

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<string>();
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",
);
});