import assert from "node:assert/strict"; import { existsSync, readFileSync } from "node:fs"; import path from "node:path"; import test from "node:test"; const source = (relativePath: string) => { return readFileSync(path.join(process.cwd(), ...relativePath.split("/")), "utf8"); }; const fileExists = (relativePath: string) => { return existsSync(path.join(process.cwd(), ...relativePath.split("/"))); }; test("Convex Workflow and Workpool dependencies and components are registered", () => { const packageSource = source("package.json"); const configSource = source("convex/convex.config.ts"); assert.match(packageSource, /"@convex-dev\/workflow"/); assert.match(packageSource, /"@convex-dev\/workpool"/); assert.match(configSource, /from\s+["@']@convex-dev\/workflow\/convex\.config["@']/); assert.match(configSource, /from\s+["@']@convex-dev\/workpool\/convex\.config["@']/); assert.match(configSource, /app\.use\(workflow/); assert.match(configSource, /app\.use\(auditWorkpool/); }); test("audit workflow defines durable workflow manager with retrying workpool options", () => { assert.equal(fileExists("convex/auditWorkflow.ts"), true); const workflowSource = source("convex/auditWorkflow.ts"); assert.match(workflowSource, /WorkflowManager/); assert.match(workflowSource, /components\.workflow/); assert.match(workflowSource, /workpoolOptions/); assert.match(workflowSource, /maxParallelism:\s*3/); assert.match(workflowSource, /retryActionsByDefault:\s*true/); assert.match(workflowSource, /maxAttempts:\s*3/); assert.match(workflowSource, /initialBackoffMs:\s*1000/); assert.match(workflowSource, /base:\s*2/); assert.match(workflowSource, /step\.runAction/); assert.match(workflowSource, /step\.runMutation/); assert.match(workflowSource, /Promise\.all/); }); test("requestLeadAudit creates a visible agentRun and starts the workflow", () => { const pageSpeedSource = source("convex/pageSpeed.ts"); assert.match(pageSpeedSource, /internal\.auditWorkflow\.startLeadAuditWorkflow/); assert.match(pageSpeedSource, /type:\s*"audit"/); assert.match(pageSpeedSource, /progressLabel:\s*"Audit vorbereitet"/); assert.match(pageSpeedSource, /workflowId/); }); test("workflow PageSpeed start accepts root runs already marked running", () => { const pageSpeedSource = source("convex/pageSpeed.ts"); assert.match( pageSpeedSource, /run\.status\s*!==\s*"pending"[\s\S]*run\.status\s*!==\s*"failed"[\s\S]*run\.status\s*!==\s*"running"/, ); }); test("workflow failure progress stays on the failing step instead of jumping to quality review", () => { const workflowSource = source("convex/auditWorkflow.ts"); const catchIndex = workflowSource.indexOf("} catch (error)"); assert.notEqual(catchIndex, -1, "Expected workflow catch block."); const nextExportIndex = workflowSource.indexOf("export const startLeadAuditWorkflow", catchIndex); assert.notEqual(nextExportIndex, -1, "Expected workflow catch block end."); const catchSource = workflowSource.slice(catchIndex, nextExportIndex); assert.doesNotMatch(catchSource, /progressPatch\(args\.runId,\s*"qualityReview"\)/); assert.doesNotMatch(catchSource, /progressStep|progressTotal|progressLabel|progressPercent/); assert.match(catchSource, /status:\s*"failed"/); }); test("audit dashboard query includes root audit runs and exposes progress and retry fields", () => { const auditsSource = source("convex/audits.ts"); assert.match(auditsSource, /\.eq\("type",\s*"audit"\)/); assert.match(auditsSource, /kind:\s*"generation"/); assert.match(auditsSource, /runType/); assert.match(auditsSource, /progress:/); assert.match(auditsSource, /retry:/); assert.match(auditsSource, /canRetry/); }); test("audit retry mutation restarts final failed or canceled runs through workflow", () => { const auditsSource = source("convex/audits.ts"); assert.match(auditsSource, /export const retryAuditRun = mutation/); assert.match(auditsSource, /requireOperator\(ctx\)/); assert.match(auditsSource, /status !== "failed"[\s\S]*status !== "canceled"/); assert.match(auditsSource, /internal\.auditWorkflow\.restartAuditWorkflow/); });