import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { join } from "node:path"; import test from "node:test"; const source = async (relativePath: string) => { return await readFile( join(process.cwd(), ...relativePath.split("/")), "utf8", ); }; function extractExportSource(sourceText: string, name: string) { const marker = `export const ${name} = `; const declarationIndex = sourceText.indexOf(marker); assert.notEqual(declarationIndex, -1, `Expected declaration for ${name}.`); const openBraceIndex = sourceText.indexOf("{", declarationIndex); let depth = 0; let end = -1; for (let index = openBraceIndex; index < sourceText.length; index += 1) { const char = sourceText[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 sourceText.slice(openBraceIndex, end + 1); } function assertRequiresOperatorBeforeDataAccess( moduleSource: string, exportName: string, helperName?: string, ) { const functionSource = extractExportSource(moduleSource, exportName); const authIndex = functionSource.indexOf("await requireOperator(ctx)"); const dbIndex = functionSource.indexOf("ctx.db"); const helperIndex = helperName === undefined ? -1 : functionSource.indexOf(helperName); const dataAccessIndex = dbIndex === -1 ? helperIndex : dbIndex; assert.notEqual( authIndex, -1, `${exportName} should call requireOperator before DB access.`, ); assert.notEqual( dataAccessIndex, -1, `${exportName} should access ctx.db or call its DB helper.`, ); assert.ok( authIndex < dataAccessIndex, `${exportName} should require operator auth before its first data access.`, ); } test("lead public APIs require operator auth before DB access", async () => { const leadsSource = await source("convex/leads.ts"); assert.match( leadsSource, /const requireOperator\s*=\s*async\s*\(\s*ctx:\s*(?:MutationCtx\s*\|\s*QueryCtx|QueryCtx\s*\|\s*MutationCtx)\s*\)/, "leads.ts should define a local requireOperator helper.", ); assert.match( leadsSource, /ctx\.auth\.getUserIdentity\(\)[\s\S]*throw new Error\(["']Nicht autorisiert\.["']\)/, "requireOperator should derive operator identity from Convex auth.", ); for (const exportName of [ "create", "reviewUpdate", "get", "list", "listFunnel", ]) { assertRequiresOperatorBeforeDataAccess( leadsSource, exportName, exportName === "reviewUpdate" ? "reviewUpdateLead" : undefined, ); } }); test("lead internal APIs exist for audit-generation action callsites", async () => { const [leadsSource, actionSource] = await Promise.all([ source("convex/leads.ts"), source("convex/auditGenerationAction.ts"), ]); assert.match( leadsSource, /import\s*{[\s\S]*internalMutation[\s\S]*internalQuery[\s\S]*}/, "leads.ts should import internal Convex builders.", ); assert.match( leadsSource, /export const getInternal\s*=\s*internalQuery\(/, "leads.ts should expose an internal lead get query for actions.", ); assert.match( leadsSource, /export const reviewUpdateInternal\s*=\s*internalMutation\(/, "leads.ts should expose an internal lead review mutation for actions.", ); assert.match( actionSource, /internal\.leads\.getInternal/, "auditGenerationAction should load leads through internal.leads.getInternal.", ); assert.match( actionSource, /internal\.leads\.reviewUpdateInternal/, "auditGenerationAction should update leads through internal.leads.reviewUpdateInternal.", ); assert.doesNotMatch( actionSource, /api\.leads\.(get|reviewUpdate)/, "auditGenerationAction should not use public lead APIs for internal calls.", ); }); test("run public APIs require operator auth before DB access", async () => { const runsSource = await source("convex/runs.ts"); assert.match( runsSource, /const requireOperator\s*=\s*async\s*\(\s*ctx:\s*(?:MutationCtx\s*\|\s*QueryCtx|QueryCtx\s*\|\s*MutationCtx)\s*\)/, "runs.ts should define a local requireOperator helper.", ); assert.match( runsSource, /ctx\.auth\.getUserIdentity\(\)[\s\S]*throw new Error\(["']Nicht autorisiert\.["']\)/, "requireOperator should derive operator identity from Convex auth.", ); for (const exportName of [ "create", "updateStatus", "list", "appendEvent", "listEvents", ]) { assertRequiresOperatorBeforeDataAccess( runsSource, exportName, exportName === "appendEvent" ? "appendRunEvent" : undefined, ); } }); test("actions append run events through internal run mutation", async () => { const [runsSource, auditAction, pageSpeedAction, enrichmentAction] = await Promise.all([ source("convex/runs.ts"), source("convex/auditGenerationAction.ts"), source("convex/pageSpeedAction.ts"), source("convex/websiteEnrichmentAction.ts"), ]); assert.match( runsSource, /export const appendEventInternal\s*=\s*internalMutation\(/, "runs.ts should expose an internal append event mutation for actions.", ); for (const [name, actionSource] of [ ["auditGenerationAction", auditAction], ["pageSpeedAction", pageSpeedAction], ["websiteEnrichmentAction", enrichmentAction], ] as const) { assert.match( actionSource, /internal\.runs\.appendEventInternal/, `${name} should append events through internal.runs.appendEventInternal.`, ); assert.doesNotMatch( actionSource, /api\.runs\.appendEvent/, `${name} should not append events through the public runs API.`, ); } });