Externalize audit pipeline services

This commit is contained in:
2026-06-07 23:06:31 +02:00
parent 470fb0f348
commit a45b92ea0a
42 changed files with 3141 additions and 247 deletions

View File

@@ -1,10 +1,13 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import test from "node:test";
import {
buildAuditEvidenceInput,
type SkillRegistryEntryEvidence,
} from "../lib/ai/audit-evidence";
import { parseSkillsRegistry } from "../lib/skills-registry";
const SAMPLE_SKILL_REGISTRY: SkillRegistryEntryEvidence[] = [
{
@@ -335,3 +338,159 @@ test("buildAuditEvidenceInput selects deterministic skills and supports design/u
assert.equal(selectedCategories.has(category), true);
}
});
test("buildAuditEvidenceInput prioritizes local-audit v3 skills before cap", () => {
const source = readFileSync(
join(process.cwd(), "v2_elemente", "skills.md"),
"utf8",
);
const skillRegistry = parseSkillsRegistry(source);
assert.equal(
skillRegistry.some((skill) => skill.id === "visual-design" && !skill.category),
true,
);
const actual = buildAuditEvidenceInput({
lead: {
companyName: "Bäckerei Muster",
niche: "Bäckerei",
city: "Berlin",
websiteDomain: "example.com",
},
crawlPages: [
{
sourceUrl: "https://example.com",
finalUrl: "https://example.com",
pageKind: "homepage",
title: "Bäckerei Muster Berlin",
visibleTextExcerpt:
"Frische Backwaren in Berlin. Rufen Sie uns an oder schreiben Sie uns fuer eine Bestellung.",
hasContactCtaSignal: true,
},
{
sourceUrl: "https://example.com/kontakt",
finalUrl: "https://example.com/kontakt",
pageKind: "contact",
title: "Kontakt",
visibleTextExcerpt:
"Telefon 030 123456, E-Mail hallo@example.com, Öffnungszeiten und Kontaktformular.",
hasContactFormSignal: true,
hasContactCtaSignal: true,
},
],
technicalChecks: [
{
sourceUrl: "https://example.com",
finalUrl: "https://example.com",
usesHttps: true,
missingMetaDescription: true,
hasVisibleContactPath: true,
},
],
screenshots: [
{
storageId: "desktop-storage",
sourceUrl: "https://example.com",
viewport: "desktop",
width: 1280,
height: 900,
mimeType: "image/png",
capturedAt: 1700000000000,
},
{
storageId: "mobile-storage",
sourceUrl: "https://example.com",
viewport: "mobile",
width: 390,
height: 844,
mimeType: "image/png",
capturedAt: 1700000001000,
},
],
pageSpeedInputs: [
{
strategy: "mobile",
status: "succeeded",
sourceUrl: "https://example.com",
normalized: {
implications: [
"Die wichtigsten Inhalte erscheinen auf dem Smartphone spürbar verzögert.",
],
},
},
],
skillRegistry,
});
const selectedIds = new Set(actual.selectedSkills.map((skill) => skill.id));
assert.deepEqual(actual.selectedSkills.map((skill) => skill.id), [
"visual-design",
"contact-conversion",
"local-seo-basics",
"performance-experience",
"mobile-usability",
"conversion-copy",
]);
assert.equal(actual.selectedSkills.length, 6);
for (const id of [
"visual-design",
"contact-conversion",
"local-seo-basics",
"performance-experience",
]) {
assert.equal(selectedIds.has(id), true, `${id} should be inside the cap.`);
}
assert.equal(
actual.selectedSkills.every((skill) => skill.category === undefined),
true,
);
});
test("buildAuditEvidenceInput gates v3 skills when declared inputs are missing", () => {
const source = readFileSync(
join(process.cwd(), "v2_elemente", "skills.md"),
"utf8",
);
const skillRegistry = parseSkillsRegistry(source);
const actual = buildAuditEvidenceInput({
lead: {
companyName: "Bäckerei Muster",
websiteDomain: "example.com",
},
crawlPages: [
{
sourceUrl: "https://example.com",
finalUrl: "https://example.com",
pageKind: "homepage",
title: "Bäckerei Muster",
},
],
screenshots: [
{
storageId: "desktop-storage",
sourceUrl: "https://example.com",
viewport: "desktop",
width: 1280,
height: 900,
mimeType: "image/png",
capturedAt: 1700000000000,
},
],
skillRegistry,
});
const selectedIds = new Set(actual.selectedSkills.map((skill) => skill.id));
for (const id of [
"visual-design",
"first-impression-clarity",
"contact-conversion",
"mobile-usability",
"conversion-copy",
"performance-experience",
]) {
assert.equal(selectedIds.has(id), false, `${id} should require missing inputs.`);
}
assert.equal(selectedIds.has("accessibility-basics"), true);
});

View File

@@ -285,6 +285,29 @@ test("sanitizer masks env-backed secret values in persistence", () => {
);
});
test("persistence sanitizer handles external service secrets with regex metacharacters", () => {
for (const secretKey of ["SCREENSHOTONE_API_KEY", "JINA_API_KEY"]) {
assert.equal(
hasPattern(auditGenerationSource, new RegExp(`["']${secretKey}["']`)),
true,
`Persistence sanitizer should redact ${secretKey}.`,
);
}
assert.equal(
auditGenerationSource.includes(
'return value.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&");',
),
true,
"escapeRegExp should escape regex metacharacters with the canonical character class.",
);
assert.equal(
auditGenerationSource.includes("/[.*+?^${}()|[\\\\]\\\\]/g"),
false,
"escapeRegExp should not keep the malformed bracket/backslash character class.",
);
});
test("finishAuditGenerationRun updates run status/counters/currentStep", () => {
const finishSource = extractExportSource("finishAuditGenerationRun");

View File

@@ -0,0 +1,87 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import test from "node:test";
import { parseSkillsRegistry, toAuditUsedSkill } from "../lib/skills-registry";
test("parseSkillsRegistry parses v3 yaml metablocks from v2 source", async () => {
const source = await readFile(join(process.cwd(), "v2_elemente", "skills.md"), "utf8");
const parsed = parseSkillsRegistry(source);
assert.equal(parsed.length, 9);
const visualDesign = parsed.find((entry) => entry.id === "visual-design");
assert.ok(visualDesign);
assert.equal(visualDesign.title, "Visueller Gesamteindruck & Zeitgemäßheit");
assert.equal(visualDesign.name, "Visueller Gesamteindruck & Zeitgemäßheit");
assert.equal(visualDesign.appliesWhen, "website_exists");
assert.deepEqual(visualDesign.inputs, [
"desktop_screenshot",
"mobile_screenshot",
]);
assert.equal(visualDesign.outputs, "findings");
const instructions = visualDesign.instructions;
if (typeof instructions !== "string") {
assert.fail("Expected visual-design instructions to be parsed.");
}
assert.match(instructions, /Beurteile den ersten visuellen Eindruck/);
});
test("toAuditUsedSkill exposes stable ids for v3 registry entries", async () => {
const source = await readFile(join(process.cwd(), "v2_elemente", "skills.md"), "utf8");
const parsed = parseSkillsRegistry(source);
const skill = parsed.find((entry) => entry.id === "contact-conversion");
assert.ok(skill);
assert.deepEqual(toAuditUsedSkill(skill), {
id: "contact-conversion",
name: "Kontaktaufnahme & Handlungsaufforderung",
});
});
test("parseSkillsRegistry does not infer categories for v3 entries without explicit metadata", async () => {
const source = await readFile(join(process.cwd(), "v2_elemente", "skills.md"), "utf8");
const parsed = parseSkillsRegistry(source);
const skill = parsed.find((entry) => entry.id === "performance-experience");
assert.ok(skill);
assert.equal(skill.category, undefined);
assert.deepEqual(toAuditUsedSkill(skill), {
id: "performance-experience",
name: "Tempo & Ladeerlebnis",
});
});
test("parseSkillsRegistry can read legacy and v3 skill sections from one registry", () => {
const source = `
## Legacy Copy Skill
Purpose: Improve customer-facing copy.
When to use: Use when page text is unclear.
When not to use: Skip when copy is not available.
Required input: Markdown copy.
Expected output: Copy recommendations.
Category: copy
## mobile-usability
\`\`\`yaml
id: mobile-usability
title: Mobile Nutzbarkeit
applies_when: has_mobile_screenshot
inputs: [mobile_screenshot, pagespeed]
outputs: findings
\`\`\`
Pruefe mobile Lesbarkeit und Tap-Ziele.
`;
const parsed = parseSkillsRegistry(source);
assert.equal(parsed.length, 2);
assert.equal(parsed[0].name, "Legacy Copy Skill");
assert.equal(parsed[0].category, "copy");
assert.equal(parsed[1].id, "mobile-usability");
assert.equal(parsed[1].category, undefined);
assert.deepEqual(parsed[1].inputs, ["mobile_screenshot", "pagespeed"]);
});

View File

@@ -127,8 +127,13 @@ test("audits schema stores compact usedSkills metadata", () => {
);
hasPattern(
usedSkillsSection,
/category:\s*v\.string\(\)/,
"usedSkills.category should be string.",
/id:\s*v\.optional\(\s*v\.string\(\)\s*\)/,
"usedSkills.id should be optional string.",
);
hasPattern(
usedSkillsSection,
/category:\s*v\.optional\(\s*v\.string\(\)\s*\)/,
"usedSkills.category should be optional string.",
);
hasPattern(
usedSkillsSection,
@@ -179,8 +184,8 @@ test("audits.create accepts usedSkills validator and persists metadata payloads"
);
hasPattern(
auditsSource,
/v\.object\([\s\S]*?name:\s*v\.string\(\)[\s\S]*?category:\s*v\.string\(\)[\s\S]*?version:\s*v\.optional\(\s*v\.string\(\)\s*\)[\s\S]*?source:\s*v\.optional\(\s*v\.string\(\)\s*\)/,
"audits.ts should define a reusable usedSkillsValidator.",
/v\.object\([\s\S]*?id:\s*v\.optional\(\s*v\.string\(\)\s*\)[\s\S]*?name:\s*v\.string\(\)[\s\S]*?category:\s*v\.optional\(\s*v\.string\(\)\s*\)[\s\S]*?version:\s*v\.optional\(\s*v\.string\(\)\s*\)[\s\S]*?source:\s*v\.optional\(\s*v\.string\(\)\s*\)/,
"audits.ts should define reusable v3-compatible usedSkillsValidator fields.",
);
hasPattern(

View File

@@ -0,0 +1,73 @@
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);
}
test("audit admin APIs require operator auth before database access", async () => {
const auditsSource = await source("convex/audits.ts");
assert.match(
auditsSource,
/const requireOperator\s*=\s*async\s*\(\s*ctx:\s*(?:MutationCtx\s*\|\s*QueryCtx|QueryCtx\s*\|\s*MutationCtx)\s*\)[\s\S]*ctx\.auth\.getUserIdentity\(\)[\s\S]*throw new Error\(["']Nicht autorisiert\.["']\)/,
"audits should define the local requireOperator auth guard.",
);
for (const name of ["create", "getDetail", "get", "getBySlug", "list"]) {
const exportSource = extractExportSource(auditsSource, name);
const authIndex = exportSource.indexOf("await requireOperator(ctx)");
const dbIndex = exportSource.indexOf("ctx.db");
assert.notEqual(authIndex, -1, `${name} should require operator auth.`);
assert.notEqual(dbIndex, -1, `${name} should access ctx.db.`);
assert.equal(
authIndex < dbIndex,
true,
`${name} should require operator auth before accessing ctx.db.`,
);
}
});
test("public audit slug lookup remains unauthenticated", async () => {
const auditsSource = await source("convex/audits.ts");
const publicSource = extractExportSource(auditsSource, "getPublicBySlug");
assert.match(publicSource, /ctx\.db/, "public audit lookup should keep reading public audit data.");
assert.doesNotMatch(
publicSource,
/requireOperator\(ctx\)/,
"getPublicBySlug should remain public and unauthenticated.",
);
});

View File

@@ -0,0 +1,335 @@
import assert from "node:assert/strict";
import { existsSync, readFileSync } from "node:fs";
import path from "node:path";
import test from "node:test";
const actionPath = path.join(process.cwd(), "convex", "auditGenerationAction.ts");
const actionSource = existsSync(actionPath) ? readFileSync(actionPath, "utf8") : "";
const generationPath = path.join(process.cwd(), "convex", "auditGeneration.ts");
const generationSource = existsSync(generationPath)
? readFileSync(generationPath, "utf8")
: "";
function extractFunctionSource(functionName: string) {
const declarationPattern = new RegExp(
`(?:async\\s+)?function\\s+${functionName}\\s*\\([\\s\\S]*?\\n\\)\\s*(?::\\s*[^\\{]+)?\\{`,
);
const match = declarationPattern.exec(actionSource);
assert.notEqual(match, null, `Expected function ${functionName}.`);
const openBraceIndex = match!.index + match![0].lastIndexOf("{");
let depth = 0;
let end = -1;
for (let index = openBraceIndex; index < actionSource.length; index += 1) {
const char = actionSource[index];
if (char === "{") {
depth += 1;
} else if (char === "}") {
depth -= 1;
if (depth === 0) {
end = index;
break;
}
}
}
assert.notEqual(end, -1, `Expected balanced braces for ${functionName}.`);
return actionSource.slice(match!.index, end + 1);
}
test("audit generation action orchestrates external capture helpers when legacy crawl artifacts are absent", () => {
assert.match(
actionSource,
/buildScreenshotOneRequests[\s\S]*buildJinaReaderAuditInput[\s\S]*estimateExternalAuditCostUsd|estimateExternalAuditCostUsd[\s\S]*buildScreenshotOneRequests[\s\S]*buildJinaReaderAuditInput/,
"Action should import and use the approved external audit service helpers.",
);
assert.match(
actionSource,
/SCREENSHOTONE_API_KEY/,
"ScreenshotOne capture should be guarded by the managed SCREENSHOTONE_API_KEY env key.",
);
assert.match(
actionSource,
/JINA_API_KEY/,
"Jina capture should be compatible with the optional managed JINA_API_KEY env key.",
);
assert.match(
actionSource,
/evidence\.screenshots\.length\s*===\s*0[\s\S]*(started\.lead\.websiteUrl|started\.lead\.websiteDomain)/,
"External capture should be prepared from the started lead URL/domain when legacy screenshots are missing.",
);
});
test("audit generation action records provider usage events for capture and OpenRouter generation", () => {
assert.match(
actionSource,
/internal\.usageEvents\.recordUsageEvent/,
"Action should record usage through internal.usageEvents.recordUsageEvent.",
);
for (const provider of ["screenshotone", "jina", "openrouter"]) {
assert.match(
actionSource,
new RegExp(`provider:\\s*["']${provider}["']`),
`Action should record ${provider} usage.`,
);
}
assert.match(
actionSource,
/provider:\s*["']openrouter["'][\s\S]*operation:\s*["']audit_generation["']/,
"OpenRouter usage should be recorded as audit_generation.",
);
assert.match(
actionSource,
/provider:\s*["']screenshotone["'][\s\S]*operation:\s*["']audit_capture["']/,
"ScreenshotOne usage should be recorded as audit_capture.",
);
assert.match(
actionSource,
/provider:\s*["']jina["'][\s\S]*operation:\s*["']audit_capture["']/,
"Jina usage should be recorded as audit_capture.",
);
});
test("Jina markdown joins the evidence prompt without requiring Playwright crawl pages", () => {
assert.match(
actionSource,
/jina(?:Reader)?AuditInput[\s\S]*markdown/,
"Action should keep Jina reader markdown as an audit evidence input.",
);
assert.match(
actionSource,
/buildAuditEvidenceInput\(\{[\s\S]*externalMarkdown|externalMarkdown[\s\S]*buildAuditEvidenceInput\(\{/,
"Action should pass external markdown into the evidence builder.",
);
assert.match(
generationSource,
/externalMarkdown/,
"Audit generation evidence types should expose external markdown for prompts.",
);
});
test("external capture fetches use timeout, abort signal, and bounded response readers", () => {
for (const constantName of [
"EXTERNAL_CAPTURE_TIMEOUT_MS",
"MAX_SCREENSHOT_BYTES",
"MAX_JINA_MARKDOWN_BYTES",
"MAX_JINA_MARKDOWN_CHARS",
]) {
assert.match(
actionSource,
new RegExp(`const\\s+${constantName}\\s*=`),
`Action should define ${constantName}.`,
);
}
assert.match(
actionSource,
/AbortController/,
"External fetches should use AbortController for per-request timeouts.",
);
assert.match(
actionSource,
/fetch\([\s\S]*signal:/,
"External fetches should pass an AbortSignal.",
);
assert.doesNotMatch(
actionSource,
/response\.blob\(\)/,
"ScreenshotOne capture should not call unbounded response.blob().",
);
assert.doesNotMatch(
actionSource,
/response\.text\(\)/,
"Jina capture should not call unbounded response.text().",
);
});
test("audit generation action sanitizes raw errors before run events and run failure summaries", () => {
assert.match(
actionSource,
/function messageFromError[\s\S]*sanitizeSecretCandidates/,
"messageFromError should sanitize/redact before returning error text.",
);
for (const secretName of ["SCREENSHOTONE_API_KEY", "JINA_API_KEY"]) {
assert.match(
actionSource,
new RegExp(`["']${secretName}["']`),
`Secret sanitizer should know ${secretName}.`,
);
}
assert.doesNotMatch(
actionSource,
/value:\s*messageFromError\(error\)/,
"Run event details should not receive raw messageFromError calls inline.",
);
assert.doesNotMatch(
actionSource,
/errorSummary\s*=\s*messageFromError\(error\)/,
"Failure summaries should not assign unsanitized raw errors inline.",
);
});
test("german-copy OpenRouter usage event aggregates all six generation calls", () => {
assert.match(
actionSource,
/aggregateOpenRouterUsage/,
"Action should expose an aggregation helper for stage-level OpenRouter usage.",
);
assert.match(
actionSource,
/aggregateOpenRouterUsage\(\[[\s\S]*publicSummaryResult\.usage[\s\S]*germanBodyResult\.usage[\s\S]*germanSubjectResult\.usage[\s\S]*germanEmailResult\.usage[\s\S]*germanCallScriptResult\.usage[\s\S]*germanFollowUpResult\.usage[\s\S]*\]\)/,
"German-copy usage should aggregate public summary, body, subject, email, call script, and follow-up calls.",
);
});
test("usage event recording is best-effort and cannot fail audit generation", () => {
const usageRecorder = extractFunctionSource("recordAuditUsageEvent");
assert.match(
usageRecorder,
/try\s*\{[\s\S]*await ctx\.runMutation\(internal\.usageEvents\.recordUsageEvent/,
"Usage recorder should isolate recordUsageEvent in a try block.",
);
assert.match(
usageRecorder,
/catch\s*\(error\)\s*\{[\s\S]*messageFromError\(error\)[\s\S]*level:\s*["']warning["']/,
"Usage recorder should sanitize/log failures as warnings.",
);
assert.match(
usageRecorder,
/catch\s*\(error\)\s*\{[\s\S]*try\s*\{[\s\S]*appendRunEvent[\s\S]*\}\s*catch/,
"Warning logging for usage failures should also be best-effort.",
);
});
test("external capture timeout covers body streaming and cancels readers", () => {
const fetcher = extractFunctionSource("fetchExternalCapture");
const reader = extractFunctionSource("readLimitedResponseBytes");
assert.match(
fetcher,
/return\s*\{[\s\S]*response[\s\S]*abortController:\s*controller[\s\S]*timeout[\s\S]*\}/,
"fetchExternalCapture should return the active deadline context for body reads.",
);
assert.doesNotMatch(
fetcher,
/finally\s*\{[\s\S]*clearTimeout\(timeout\)/,
"fetchExternalCapture should not clear the timeout before body streaming completes.",
);
assert.match(
reader,
/signal\??:\s*AbortSignal/,
"Bounded response reader should accept an AbortSignal.",
);
assert.match(
reader,
/signal\?\.addEventListener\(\s*["']abort["'][\s\S]*reader\.cancel/,
"Bounded response reader should cancel the reader on timeout/abort.",
);
assert.match(
reader,
/totalBytes\s*>\s*maxBytes[\s\S]*await reader\.cancel\(/,
"Bounded response reader should cancel the stream when the byte cap is exceeded.",
);
assert.match(
actionSource,
/readLimitedResponseBytes\([\s\S]*MAX_SCREENSHOT_BYTES[\s\S]*abortController\.signal/,
"Screenshot body reads should use the active timeout signal.",
);
assert.match(
actionSource,
/readLimitedMarkdown\([\s\S]*abortController\.signal/,
"Jina markdown body reads should use the active timeout signal.",
);
assert.match(
actionSource,
/finally\s*\{[\s\S]*clearExternalCaptureTimeout/,
"Capture loops should clear the external timeout after fetch and body streaming finish.",
);
});
test("external capture request builders are provider-level best-effort", () => {
const capture = extractFunctionSource("captureExternalAuditArtifacts");
assert.match(
capture,
/if\s*\(args\.needsScreenshots\)[\s\S]*try\s*\{[\s\S]*buildScreenshotOneRequests/,
"ScreenshotOne request construction should be inside a provider-level try block.",
);
assert.match(
capture,
/buildScreenshotOneRequests[\s\S]*catch\s*\(error\)[\s\S]*messageFromError\(error\)[\s\S]*level:\s*["']warning["']/,
"ScreenshotOne request construction failures should degrade to sanitized warnings.",
);
assert.match(
capture,
/if\s*\(args\.needsMarkdown\)[\s\S]*try\s*\{[\s\S]*buildJinaReaderAuditInput/,
"Jina reader input construction should be inside a provider-level try block.",
);
assert.match(
capture,
/buildJinaReaderAuditInput[\s\S]*catch\s*\(error\)[\s\S]*messageFromError\(error\)[\s\S]*level:\s*["']warning["']/,
"Jina reader input construction failures should degrade to sanitized warnings.",
);
});
test("ScreenshotOne missing-key skip emits best-effort warning only when screenshots are needed", () => {
const capture = extractFunctionSource("captureExternalAuditArtifacts");
const needsScreenshotsIndex = capture.indexOf("if (args.needsScreenshots)");
const needsMarkdownIndex = capture.indexOf("if (args.needsMarkdown)");
const missingKeyWarningIndex = capture.indexOf(
"ScreenshotOne ist nicht konfiguriert; Screenshot-Erfassung wurde übersprungen.",
);
assert.notEqual(
needsScreenshotsIndex,
-1,
"External capture should branch on needsScreenshots.",
);
assert.notEqual(
needsMarkdownIndex,
-1,
"External capture should keep the later needsMarkdown branch.",
);
assert.notEqual(
missingKeyWarningIndex,
-1,
"Missing ScreenshotOne config should emit a clear warning message.",
);
assert.equal(
missingKeyWarningIndex > needsScreenshotsIndex &&
missingKeyWarningIndex < needsMarkdownIndex,
true,
"Missing-key warning should live inside the needsScreenshots branch, so legacy screenshots do not warn.",
);
assert.match(
capture,
/if\s*\(!screenshotOneApiKey\)\s*\{[\s\S]*try\s*\{[\s\S]*await appendRunEvent\(ctx,\s*\{[\s\S]*level:\s*["']warning["'][\s\S]*ScreenshotOne ist nicht konfiguriert; Screenshot-Erfassung wurde übersprungen\.[\s\S]*\}\s*\);[\s\S]*\}\s*catch\s*\{[\s\S]*\}/,
"Missing-key warning logging should be best-effort and unable to fail the audit run.",
);
});
test("external capture non-OK responses cancel bodies before continuing", () => {
const capture = extractFunctionSource("captureExternalAuditArtifacts");
const nonOkCancelCount = [
...capture.matchAll(
/if\s*\(!response\.ok\)\s*\{[\s\S]*?await cancelExternalResponseBody\(response\);[\s\S]*?continue;/g,
),
].length;
assert.match(
actionSource,
/async function cancelExternalResponseBody/,
"Action should centralize best-effort body cancellation for non-OK responses.",
);
assert.equal(
nonOkCancelCount,
2,
"Both ScreenshotOne and Jina non-OK branches should cancel bodies before continue.",
);
});

View File

@@ -0,0 +1,184 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
buildJinaReaderAuditInput,
buildScreenshotOneRequests,
estimateExternalAuditCostUsd,
} from "../lib/external-audit-services";
test("estimateExternalAuditCostUsd totals managed provider usage", () => {
const estimate = estimateExternalAuditCostUsd({
openRouter: {
inputTokens: 1_500_000,
outputTokens: 250_000,
inputUsdPerMillionTokens: 0.25,
outputUsdPerMillionTokens: 1.25,
},
screenshotOne: {
screenshots: 2,
usdPerScreenshot: 0.01,
},
jina: {
requests: 4,
pages: 4,
usdPerRequest: 0.001,
usdPerPage: 0.002,
},
pageSpeed: {
requests: 2,
},
});
assert.equal(estimate.totalUsd, 0.7195);
assert.deepEqual(estimate.byProvider, {
openRouter: 0.6875,
screenshotOne: 0.02,
jina: 0.012,
pageSpeed: 0,
});
});
test("estimateExternalAuditCostUsd clamps negative usage and prices to zero", () => {
const estimate = estimateExternalAuditCostUsd({
openRouter: {
inputTokens: -1_000_000,
outputTokens: 100_000,
inputUsdPerMillionTokens: 0.25,
outputUsdPerMillionTokens: -1.25,
},
screenshotOne: {
screenshots: -2,
usdPerScreenshot: 0.01,
},
jina: {
requests: 4,
pages: -4,
usdPerRequest: -0.001,
usdPerPage: 0.002,
},
});
assert.deepEqual(estimate.byProvider, {
openRouter: 0,
screenshotOne: 0,
jina: 0,
pageSpeed: 0,
});
assert.equal(estimate.totalUsd, 0);
});
test("buildScreenshotOneRequests creates stable desktop and mobile URLs", () => {
const requests = buildScreenshotOneRequests({
accessKey: "sso_secret_key",
targetUrl: "https://example.com/landing?utm=abc",
});
assert.equal(requests.length, 2);
assert.deepEqual(
requests.map((request) => request.viewport),
["desktop", "mobile"],
);
const desktop = new URL(requests[0]?.url ?? "");
assert.equal(desktop.searchParams.get("access_key"), "sso_secret_key");
assert.equal(desktop.searchParams.get("url"), "https://example.com/landing?utm=abc");
assert.equal(desktop.searchParams.get("viewport_width"), "1280");
assert.equal(desktop.searchParams.get("viewport_height"), "900");
assert.equal(desktop.searchParams.get("device_scale_factor"), "1");
assert.equal(desktop.searchParams.get("full_page"), "true");
assert.equal(desktop.searchParams.get("block_cookie_banners"), "true");
assert.equal(desktop.searchParams.get("block_ads"), "true");
assert.equal(desktop.searchParams.get("block_trackers"), "true");
const mobile = new URL(requests[1]?.url ?? "");
assert.equal(mobile.searchParams.get("viewport_width"), "390");
assert.equal(mobile.searchParams.get("viewport_height"), "844");
assert.equal(mobile.searchParams.get("device_scale_factor"), "2");
assert.equal(mobile.searchParams.get("full_page"), "true");
});
test("buildScreenshotOneRequests rejects non-web target URLs without leaking secrets", () => {
assert.throws(
() =>
buildScreenshotOneRequests({
accessKey: "sso_secret_key",
targetUrl: "ftp://example.com/landing",
}),
(error) => {
assert.equal(error instanceof Error, true);
assert.equal((error as Error).message.includes("sso_secret_key"), false);
assert.match((error as Error).message, /http.*https/i);
return true;
},
);
});
test("buildScreenshotOneRequests does not leak the access key in validation errors", () => {
assert.throws(
() =>
buildScreenshotOneRequests({
accessKey: "sso_secret_key",
targetUrl: "not a url",
}),
(error) => {
assert.equal(error instanceof Error, true);
assert.equal((error as Error).message.includes("sso_secret_key"), false);
assert.match((error as Error).message, /target url/i);
return true;
},
);
});
test("buildJinaReaderAuditInput rejects non-web base and page URLs", () => {
assert.throws(
() =>
buildJinaReaderAuditInput({
baseUrl: "file:///tmp/site.html",
maxMarkdownChars: 100,
}),
/http.*https/i,
);
assert.throws(
() =>
buildJinaReaderAuditInput({
baseUrl: "https://example.com",
pages: [{ url: "ftp://example.com/kontakt", markdown: "Kontakt" }],
maxMarkdownChars: 100,
}),
/http.*https/i,
);
});
test("buildJinaReaderAuditInput prepares capped markdown for relevant pages", () => {
const input = buildJinaReaderAuditInput({
baseUrl: "https://example.com",
pages: [
{ url: "https://example.com", markdown: "# Home\nWillkommen auf der Startseite." },
{ url: "https://example.com/kontakt", markdown: "Kontaktformular und Telefonnummer." },
{ url: "https://example.com/impressum", markdown: "Impressum mit Anbieterkennzeichnung." },
{ url: "https://example.com/leistungen", markdown: "Leistungen fuer Webdesign und SEO." },
{ url: "https://example.com/ueber-uns", markdown: "Ueber uns und Arbeitsweise." },
],
maxMarkdownChars: 95,
});
assert.deepEqual(
input.pages.map((page) => page.path),
["/", "/kontakt", "/impressum", "/leistungen", "/ueber-uns"],
);
assert.deepEqual(
input.readerUrls,
[
"https://r.jina.ai/https://example.com/",
"https://r.jina.ai/https://example.com/kontakt",
"https://r.jina.ai/https://example.com/impressum",
"https://r.jina.ai/https://example.com/leistungen",
"https://r.jina.ai/https://example.com/ueber-uns",
],
);
assert.equal(input.markdown.length <= 95, true);
assert.match(input.markdown, /Source: https:\/\/example.com/);
assert.match(input.markdown, /\[truncated to 95 chars\]$/);
});

View File

@@ -0,0 +1,195 @@
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.`,
);
}
});

View File

@@ -13,10 +13,11 @@ test("integration readiness covers all MVP providers", () => {
"google",
"pagespeed",
"openrouter",
"playwright",
"screenshotone",
"smtp",
"convex_jobs",
"rybbit",
"jina",
],
);
});
@@ -36,3 +37,45 @@ test("integration readiness reports missing configuration without leaking values
assert.equal(JSON.stringify(rows).includes("secret-google"), false);
assert.equal(JSON.stringify(rows).includes("secret-places"), false);
});
test("integration readiness treats ScreenshotOne as required and Jina as optional", () => {
const rows = getIntegrationReadiness({
GOOGLE_GEOCODING_API_KEY: "secret-google",
GOOGLE_PLACES_API_KEY: "secret-places",
PAGESPEED_API_KEY: "secret-pagespeed",
PAGESPEED_TIMEOUT_MS: "60000",
OPENROUTER_API_KEY: "secret-openrouter",
SMTP_HOST: "smtp.example.com",
SMTP_USER: "user",
SMTP_PASSWORD: "password",
SMTP_FROM: "Audit <audit@example.com>",
NEXT_PUBLIC_CONVEX_URL: "https://example.convex.cloud",
CONVEX_DEPLOYMENT: "prod:example",
RYBBIT_API_URL: "https://analytics.example.com",
RYBBIT_API_KEY: "secret-rybbit",
NEXT_PUBLIC_RYBBIT_SITE_ID: "site-id",
});
const screenshotOne = rows.find((row) => row.id === "screenshotone");
const jina = rows.find((row) => row.id === "jina");
assert.equal(screenshotOne?.status, "missing");
assert.deepEqual(screenshotOne?.missingEnv, ["SCREENSHOTONE_API_KEY"]);
assert.equal(jina?.status, "configured");
assert.deepEqual(jina?.missingEnv, []);
});
test("integration readiness no longer requires Playwright for the new pipeline", () => {
const definitionIds = integrationReadinessDefinitions.map((definition) => definition.id as string);
assert.equal(
definitionIds.includes("playwright"),
false,
);
assert.equal(
integrationReadinessDefinitions.some((definition) =>
definition.requiredEnv.includes("TASK8_BROWSER_ASSET_URL"),
),
false,
);
});

View File

@@ -19,14 +19,20 @@ test("settings page surfaces integration status instead of a placeholder", () =>
"Google",
"PageSpeed",
"OpenRouter",
"Playwright",
"ScreenshotOne",
"SMTP",
"Convex Jobs",
"Rybbit",
"Jina",
"Konfiguration fehlt",
]) {
assert.match(`${componentSource}\n${helperSource}`, new RegExp(label));
}
assert.doesNotMatch(helperSource, /id: "playwright"/);
assert.doesNotMatch(helperSource, /requiredEnv: \["TASK8_BROWSER_ASSET_URL"\]/);
assert.match(helperSource, /requiredEnv: \["SCREENSHOTONE_API_KEY"\]/);
assert.match(helperSource, /requiredEnv: \[\]/);
});
test("verification notes cover critical MVP flows", () => {

View File

@@ -238,7 +238,7 @@ test("pageSpeedAction stores and persists results and writes events", () => {
);
assert.equal(
/api\.runs\.appendEvent,\s*{\s*[\s\S]*runId:\s*args\.runId,\s*[\s\S]*level:\s*["']info["']/.test(
/internal\.runs\.appendEventInternal,\s*{\s*[\s\S]*runId:\s*args\.runId,\s*[\s\S]*level:\s*["']info["']/.test(
actionSource,
),
true,
@@ -283,7 +283,7 @@ test("pageSpeedAction does not expose API key in event messages/details", () =>
assert.equal(
hasPattern(
actionSource,
/api\.runs\.appendEvent[\s\S]{0,500}PAGESPEED_API_KEY/,
/internal\.runs\.appendEventInternal[\s\S]{0,500}PAGESPEED_API_KEY/,
),
false,
"Action events should not include raw PAGESPEED_API_KEY",

View File

@@ -0,0 +1,356 @@
import assert from "node:assert/strict";
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import test from "node:test";
import ts from "typescript";
const schemaPath = join(process.cwd(), "convex", "schema.ts");
const domainPath = join(process.cwd(), "convex", "domain.ts");
const usageEventsPath = join(process.cwd(), "convex", "usageEvents.ts");
const schemaSource = readFileSync(schemaPath, "utf8");
const domainSource = readFileSync(domainPath, "utf8");
const usageEventsSource = existsSync(usageEventsPath)
? readFileSync(usageEventsPath, "utf8")
: "";
const usageEventsSourceFile = ts.createSourceFile(
"usageEvents.ts",
usageEventsSource,
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,
);
const isConst = node.declarationList.flags & ts.NodeFlags.Const;
if (isExported && isConst) {
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 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!;
return {
objectBlock: schemaSource.slice(markerIndex, objectEnd + 1),
section: schemaSource.slice(markerIndex, sectionEnd),
};
}
function extractExportSource(name: string) {
const marker = `export const ${name} = `;
const declarationIndex = usageEventsSource.indexOf(marker);
assert.notEqual(declarationIndex, -1, `Expected declaration for ${name}`);
const openBraceIndex = usageEventsSource.indexOf("{", declarationIndex);
let depth = 0;
let end = -1;
for (let index = openBraceIndex; index < usageEventsSource.length; index += 1) {
const char = usageEventsSource[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 usageEventsSource.slice(openBraceIndex, end + 1);
}
function assertHas(pattern: RegExp, source: string, message: string) {
assert.equal(pattern.test(source), true, message);
}
const usageReadQueries = [
{
name: "listLatestUsageEvents",
indexAssertion:
/withIndex\("by_createdAt"\)/,
message: "latest query should use by_createdAt.",
},
{
name: "listUsageEventsByRun",
indexAssertion:
/withIndex\("by_runId_and_createdAt"[\s\S]*?eq\("runId",\s*args\.runId\)/,
message: "run query should use by_runId_and_createdAt with runId equality.",
},
{
name: "listUsageEventsByLead",
indexAssertion:
/withIndex\("by_leadId_and_createdAt"[\s\S]*?eq\("leadId",\s*args\.leadId\)/,
message: "lead query should use by_leadId_and_createdAt with leadId equality.",
},
{
name: "listUsageEventsByAudit",
indexAssertion:
/withIndex\("by_auditId_and_createdAt"[\s\S]*?eq\("auditId",\s*args\.auditId\)/,
message: "audit query should use by_auditId_and_createdAt with auditId equality.",
},
{
name: "listUsageEventsByProvider",
indexAssertion:
/withIndex\("by_provider_and_createdAt"[\s\S]*?eq\("provider",\s*args\.provider\)/,
message: "provider query should use by_provider_and_createdAt with provider equality.",
},
] as const;
test("usage domain constants declare supported providers and operations", () => {
assertHas(
/USAGE_EVENT_PROVIDERS\s*=\s*\[[\s\S]*"openrouter"[\s\S]*"screenshotone"[\s\S]*"jina"[\s\S]*"pagespeed"[\s\S]*"google_places"[\s\S]*\]\s*as const/,
domainSource,
"Domain should declare usage providers for all managed external services.",
);
assertHas(
/USAGE_EVENT_OPERATIONS\s*=\s*\[[\s\S]*"audit_capture"[\s\S]*"audit_generation"[\s\S]*"lead_lookup"[\s\S]*\]\s*as const/,
domainSource,
"Domain should declare usage operations for capture, generation, and lookup.",
);
});
test("usageEvents schema stores cost and usage dimensions with bounded indexes", () => {
const { objectBlock, section } = extractTableSection("usageEvents");
assertHas(/provider:\s*usageEventProvider/, objectBlock, "provider should use the provider validator.");
assertHas(/operation:\s*usageEventOperation/, objectBlock, "operation should use the operation validator.");
assertHas(
/runId:\s*v\.optional\(\s*v\.id\(["']agentRuns["']\)\s*\)/,
objectBlock,
"runId should be optional for SaaS-ready attribution.",
);
assertHas(
/leadId:\s*v\.optional\(\s*v\.id\(["']leads["']\)\s*\)/,
objectBlock,
"leadId should be optional for lead-level attribution.",
);
assertHas(
/auditId:\s*v\.optional\(\s*v\.id\(["']audits["']\)\s*\)/,
objectBlock,
"auditId should be optional for audit-level attribution.",
);
assertHas(
/estimatedCostUsd:\s*v\.number\(\)/,
objectBlock,
"estimatedCostUsd should be a required normalized number.",
);
assertHas(
/tokens:\s*v\.optional\(\s*v\.object\([\s\S]*?inputTokens:\s*v\.optional\(v\.number\(\)\)[\s\S]*?outputTokens:\s*v\.optional\(v\.number\(\)\)[\s\S]*?promptTokens:\s*v\.optional\(v\.number\(\)\)[\s\S]*?completionTokens:\s*v\.optional\(v\.number\(\)\)[\s\S]*?totalTokens:\s*v\.optional\(v\.number\(\)\)[\s\S]*?\)\s*\)/,
objectBlock,
"tokens should capture OpenRouter-compatible token dimensions.",
);
assertHas(
/callCounts:\s*v\.optional\(\s*v\.object\(/,
objectBlock,
"callCounts should be an optional object.",
);
for (const countName of ["requests", "pages", "screenshots", "lookups"]) {
assertHas(
new RegExp(`${countName}:\\s*v\\.optional\\(v\\.number\\(\\)\\)`),
objectBlock,
`callCounts.${countName} should be optional number.`,
);
}
assertHas(/createdAt:\s*v\.number\(\)/, objectBlock, "createdAt should be required.");
for (const [indexName, fields] of [
["by_runId_and_createdAt", '"runId",\\s*"createdAt"'],
["by_leadId_and_createdAt", '"leadId",\\s*"createdAt"'],
["by_auditId_and_createdAt", '"auditId",\\s*"createdAt"'],
["by_provider_and_createdAt", '"provider",\\s*"createdAt"'],
["by_createdAt", '"createdAt"'],
] as const) {
assertHas(
new RegExp(`index\\("${indexName}",\\s*\\[${fields}\\]\\)`),
section,
`usageEvents should define ${indexName}.`,
);
}
});
test("usageEvents module exposes internal recorder and authenticated bounded read queries", () => {
assert.equal(existsSync(usageEventsPath), true, "usageEvents.ts should be present.");
const exports = getExportedConstNames(usageEventsSourceFile);
for (const exportName of [
"recordUsageEvent",
...usageReadQueries.map((readQuery) => readQuery.name),
]) {
assert.equal(exports.has(exportName), true, `Expected export: ${exportName}`);
}
assertHas(
/export const recordUsageEvent = internalMutation\s*\(/,
usageEventsSource,
"recordUsageEvent should be an internalMutation.",
);
for (const { name } of usageReadQueries) {
assertHas(
new RegExp(`export const ${name} = query\\s*\\(`),
usageEventsSource,
`${name} should remain a public authenticated bounded query.`,
);
}
});
test("usageEvents queries use indexes and bounded take without filters or collect", () => {
const querySources = usageReadQueries.map((readQuery) => ({
...readQuery,
source: extractExportSource(readQuery.name),
}));
for (const { source } of querySources) {
assertHas(/limit:\s*v\.optional\(v\.number\(\)\)/, source, "read query should validate limit.");
}
for (const { source, indexAssertion, message } of querySources) {
assertHas(indexAssertion, source, message);
}
for (const source of [usageEventsSource, ...querySources.map((querySource) => querySource.source)]) {
assert.doesNotMatch(source, /\.filter\s*\(/, "usageEvents should not use query filters.");
assert.doesNotMatch(source, /\.collect\s*\(/, "usageEvents should not use unbounded collect.");
}
for (const { source } of querySources) {
assertHas(
/\.take\(\s*normalizeListLimit\(args\.limit\)\s*\)/,
source,
"read query should be bounded.",
);
}
});
test("usageEvents read queries require operator auth before reading telemetry", () => {
assertHas(
/const\s+requireOperator\s*=\s*async\s*\(\s*ctx:\s*QueryCtx\s*\)[\s\S]*ctx\.auth\.getUserIdentity\(\)[\s\S]*throw new Error\(["']Nicht autorisiert\.["']\)/,
usageEventsSource,
"usageEvents should define the local requireOperator auth guard.",
);
for (const { name } of usageReadQueries) {
const source = extractExportSource(name);
const authIndex = source.indexOf("await requireOperator(ctx)");
const readIndex = source.indexOf("ctx.db");
assert.notEqual(authIndex, -1, `${name} should require operator auth.`);
assert.notEqual(readIndex, -1, `${name} should read from ctx.db.`);
assert.equal(
authIndex < readIndex,
true,
`${name} should require auth before reading usage telemetry.`,
);
}
});
test("recordUsageEvent guards finite non-negative usage numbers before insert", () => {
const recordSource = extractExportSource("recordUsageEvent");
const guardCallIndex = recordSource.indexOf("assertValidUsageEventNumbers(args)");
const insertIndex = recordSource.indexOf('ctx.db.insert("usageEvents"');
assert.notEqual(
guardCallIndex,
-1,
"recordUsageEvent should call assertValidUsageEventNumbers(args).",
);
assert.notEqual(insertIndex, -1, "recordUsageEvent should insert usageEvents.");
assert.equal(
guardCallIndex < insertIndex,
true,
"recordUsageEvent should validate usage numbers before inserting.",
);
assertHas(
/function\s+assertFiniteNonNegativeNumber[\s\S]*Number\.isFinite\(value\)[\s\S]*value\s*<\s*0/,
usageEventsSource,
"Cost guard should reject NaN, Infinity, and negative numbers.",
);
assertHas(
/function\s+assertFiniteNonNegativeInteger[\s\S]*Number\.isFinite\(value\)[\s\S]*value\s*<\s*0[\s\S]*Number\.isInteger\(value\)/,
usageEventsSource,
"Token/count guard should require finite non-negative integers.",
);
assertHas(
/assertFiniteNonNegativeNumber\(args\.estimatedCostUsd,\s*["']estimatedCostUsd["']\)/,
usageEventsSource,
"estimatedCostUsd should use the finite non-negative number guard.",
);
for (const tokenName of [
"inputTokens",
"outputTokens",
"promptTokens",
"completionTokens",
"totalTokens",
"cacheReadTokens",
]) {
assertHas(
new RegExp(
`assertFiniteNonNegativeInteger\\(args\\.tokens\\?\\.${tokenName},\\s*["']tokens\\.${tokenName}["']\\)`,
),
usageEventsSource,
`tokens.${tokenName} should use the finite non-negative integer guard.`,
);
}
for (const countName of ["requests", "pages", "screenshots", "lookups"]) {
assertHas(
new RegExp(
`assertFiniteNonNegativeInteger\\(args\\.callCounts\\?\\.${countName},\\s*["']callCounts\\.${countName}["']\\)`,
),
usageEventsSource,
`callCounts.${countName} should use the finite non-negative integer guard.`,
);
}
});