Integrate PageSpeed Insights audits
This commit is contained in:
191
tests/pagespeed-audit-input-integration.test.ts
Normal file
191
tests/pagespeed-audit-input-integration.test.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
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",
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user