Files
pitchfast/tests/audit-generation-schema.test.ts

292 lines
8.2 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import test from "node:test";
const schemaSource = readFileSync(
join(process.cwd(), "convex", "schema.ts"),
"utf8",
);
const domainSource = readFileSync(
join(process.cwd(), "convex", "domain.ts"),
"utf8",
);
function extractTableSection(tableName: string) {
const marker = `${tableName}: defineTable({`;
const markerIndex = schemaSource.indexOf(marker);
assert.notEqual(
markerIndex,
-1,
`Expected schema table definition for ${tableName}.`,
);
const objectStart = schemaSource.indexOf("{", markerIndex);
let depth = 0;
let objectEnd = -1;
for (let index = objectStart; index < schemaSource.length; index += 1) {
if (schemaSource[index] === "{") {
depth += 1;
} else if (schemaSource[index] === "}") {
depth -= 1;
if (depth === 0) {
objectEnd = index;
break;
}
}
}
assert.notEqual(objectEnd, -1, `Could not parse schema object for ${tableName}.`);
const remainder = schemaSource.slice(objectEnd + 1);
const nextTableMatch = remainder.match(
/^\s*[a-zA-Z_][\w]*:\s*defineTable\(/m,
);
const sectionEnd =
nextTableMatch === null
? schemaSource.length
: objectEnd + 1 + nextTableMatch.index!;
const section = schemaSource.slice(markerIndex, sectionEnd);
const objectBlock = schemaSource.slice(markerIndex, objectEnd + 1);
return { section, objectBlock };
}
function assertHas(pattern: RegExp, source: string, message: string) {
assert.equal(pattern.test(source), true, message);
}
test("auditGenerations table has contract fields", () => {
const { section, objectBlock } = extractTableSection("auditGenerations");
assertHas(
/leadId:\s*v\.id\(["']leads["']\)/,
objectBlock,
"auditGenerations.leadId must be required lead id.",
);
assertHas(
/auditId:\s*v\.optional\(\s*v\.id\(["']audits["']\)\s*\)/,
objectBlock,
"auditGenerations.auditId should be optional audit id.",
);
assertHas(
/runId:\s*v\.id\(["']agentRuns["']\)/,
objectBlock,
"auditGenerations.runId should be required agent run id.",
);
assertHas(
/stage:\s*auditGenerationStage/,
objectBlock,
"auditGenerations.stage should use auditGenerationStage validator.",
);
assertHas(
/modelProfile:\s*v\.string\(\)/,
objectBlock,
"auditGenerations.modelProfile should be required string.",
);
assertHas(
/modelId:\s*v\.string\(\)/,
objectBlock,
"auditGenerations.modelId should be required string.",
);
assertHas(
/prompt:\s*v\.string\(\)/,
objectBlock,
"auditGenerations.prompt should be required string.",
);
assertHas(
/systemPrompt:\s*v\.optional\(\s*v\.string\(\)\s*\)/,
objectBlock,
"auditGenerations.systemPrompt should be optional string.",
);
assertHas(
/rawResponse:\s*v\.optional\(\s*v\.string\(\)\s*\)/,
objectBlock,
"auditGenerations.rawResponse should be optional string.",
);
assertHas(
/parsedJson:\s*v\.optional\(\s*auditGenerationParsedJson\s*\)/,
objectBlock,
"auditGenerations.parsedJson should allow string or structured object.",
);
assertHas(
/usage:\s*v\.optional\(\s*auditGenerationUsage\s*\)/,
objectBlock,
"auditGenerations.usage should be optional token usage object.",
);
assertHas(
/finishReason:\s*v\.optional\(\s*v\.string\(\)\s*\)/,
objectBlock,
"auditGenerations.finishReason should be optional string.",
);
assertHas(
/status:\s*auditGenerationStatus/,
objectBlock,
"auditGenerations.status should use auditGenerationStatus validator.",
);
assertHas(
/errorSummary:\s*v\.optional\(\s*v\.string\(\)\s*\)/,
objectBlock,
"auditGenerations.errorSummary should be optional string.",
);
assertHas(
/createdAt:\s*v\.number\(\)/,
objectBlock,
"auditGenerations.createdAt should be required number.",
);
assertHas(
/updatedAt:\s*v\.number\(\)/,
objectBlock,
"auditGenerations.updatedAt should be required number.",
);
assertHas(
/index\("by_leadId",\s*\["leadId"\]\)/,
section,
"auditGenerations should have by_leadId index.",
);
assertHas(
/index\("by_auditId",\s*\["auditId"\]\)/,
section,
"auditGenerations should have by_auditId index.",
);
assertHas(
/index\("by_runId",\s*\["runId"\]\)/,
section,
"auditGenerations should have by_runId index.",
);
assertHas(
/index\("by_stage",\s*\["stage"\]\)/,
section,
"auditGenerations should have by_stage index.",
);
assertHas(
/index\("by_leadId_and_stage",\s*\["leadId",\s*"stage"\]\)/,
section,
"auditGenerations should have by_leadId_and_stage index.",
);
});
test("audit-generation validators are declared", () => {
assertHas(
/const\s+auditGenerationStage\s*=\s*v\.union\([\s\S]*\)/,
schemaSource,
"schema should define auditGenerationStage union.",
);
assertHas(
/const\s+auditGenerationStatus\s*=\s*v\.union\([\s\S]*\)/,
schemaSource,
"schema should define auditGenerationStatus union.",
);
assertHas(
/AUDIT_GENERATION_STAGES\s*=\s*\[[\s\S]*["']classification["'][\s\S]*\]/,
domainSource,
"auditGenerationStage should include classification.",
);
assertHas(
/AUDIT_GENERATION_STAGES\s*=\s*\[[\s\S]*["']multimodalAudit["'][\s\S]*\]/,
domainSource,
"auditGenerationStage should include multimodalAudit.",
);
assertHas(
/AUDIT_GENERATION_STAGES\s*=\s*\[[\s\S]*["']germanCopy["'][\s\S]*\]/,
domainSource,
"auditGenerationStage should include germanCopy.",
);
assertHas(
/AUDIT_GENERATION_STAGES\s*=\s*\[[\s\S]*["']qualityReview["'][\s\S]*\]/,
domainSource,
"auditGenerationStage should include qualityReview.",
);
});
test("auditFindings table stores verified specialist findings with evidence refs", () => {
const { section, objectBlock } = extractTableSection("auditFindings");
assertHas(
/auditId:\s*v\.id\(["']audits["']\)/,
objectBlock,
"auditFindings.auditId must be required audit id.",
);
assertHas(
/runId:\s*v\.id\(["']agentRuns["']\)/,
objectBlock,
"auditFindings.runId must be required run id.",
);
assertHas(
/skillId:\s*v\.string\(\)/,
objectBlock,
"auditFindings.skillId must identify the source specialist skill.",
);
assertHas(
/claim:\s*v\.string\(\)/,
objectBlock,
"auditFindings.claim must store the verified claim.",
);
assertHas(
/recommendation:\s*v\.string\(\)/,
objectBlock,
"auditFindings.recommendation must store the concrete fix.",
);
assertHas(
/customerBenefit:\s*v\.string\(\)/,
objectBlock,
"auditFindings.customerBenefit must store customer-facing impact.",
);
assertHas(
/severity:\s*v\.union\(\s*v\.literal\(1\),\s*v\.literal\(2\),\s*v\.literal\(3\)\s*\)/,
objectBlock,
"auditFindings.severity should be a 1-3 literal union.",
);
assertHas(
/confidence:\s*v\.number\(\)/,
objectBlock,
"auditFindings.confidence must be persisted for review calibration.",
);
assertHas(
/evidenceRefs:\s*v\.array\(\s*auditFindingEvidenceRef\s*\)/,
objectBlock,
"auditFindings.evidenceRefs must persist typed evidence refs.",
);
assertHas(
/reviewStatus:\s*auditFindingReviewStatus/,
objectBlock,
"auditFindings.reviewStatus should use a review-status validator.",
);
assertHas(
/index\("by_auditId",\s*\["auditId"\]\)/,
section,
"auditFindings should have by_auditId index.",
);
assertHas(
/index\("by_runId",\s*\["runId"\]\)/,
section,
"auditFindings should have by_runId index.",
);
assertHas(
/index\("by_auditId_and_reviewStatus",\s*\["auditId",\s*"reviewStatus"\]\)/,
section,
"auditFindings should support review-status filtering per audit.",
);
});
test("specialist fan-out audit stages are declared in domain", () => {
for (const stage of [
"localSeoSpecialist",
"conversionUxSpecialist",
"visualTrustSpecialist",
"critiqueSpecialist",
"performanceAccessibilitySpecialist",
"evidenceVerifier",
]) {
assertHas(
new RegExp(`AUDIT_GENERATION_STAGES\\s*=\\s*\\[[\\s\\S]*["']${stage}["'][\\s\\S]*\\]`),
domainSource,
`auditGenerationStage should include ${stage}.`,
);
}
});