import assert from "node:assert/strict"; import { existsSync, readFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import ts from "typescript"; const testDir = path.dirname(fileURLToPath(import.meta.url)); const failures = []; function loadTsModule(sourcePath, requireOverrides = {}) { const source = readFileSync(sourcePath, "utf8"); const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, }, }); const moduleExports = {}; const module = { exports: moduleExports }; const localRequire = (specifier) => { if (specifier in requireOverrides) { return requireOverrides[specifier]; } throw new Error(`Unexpected runtime import in model test: ${specifier}`); }; new Function("exports", "module", "require", compiled.outputText)( moduleExports, module, localRequire, ); return module.exports; } function loadExpectedModule(sourcePath, label, expectedExports = []) { if (!existsSync(sourcePath)) { const expected = expectedExports.join(", "); failures.push( `${label} is missing at ${path.relative(process.cwd(), sourcePath)}; expected exports: ${expected}`, ); return null; } try { return loadTsModule(sourcePath); } catch (error) { failures.push(`${label} failed to load: ${error.message}`); return null; } } function expectFunction(moduleExports, exportName, label) { if (!moduleExports) { return null; } const value = moduleExports?.[exportName]; if (typeof value !== "function") { failures.push(`${label} must export ${exportName}()`); return null; } return value; } async function recordAssertion(label, assertion) { try { await assertion(); } catch (error) { failures.push(`${label}: ${error.message}`); } } const workflowModelPath = path.resolve( testDir, "../src/features/admin-workflows/model.ts", ); const apiPath = path.resolve(testDir, "../src/shared/pipeline-api.ts"); const navigationModelPath = path.resolve( testDir, "../src/widgets/role-navigation/model.ts", ); const expectedWorkflowModelExports = [ "defaultWorkflowDraft", "buildCreateWorkflowPayload", "defaultWorkflowStageDraft", "buildCreateWorkflowStagePayload", "moveWorkflowStage", "buildReorderWorkflowStagesPayload", ]; const workflowDraft = { name: "Article production workflow", slug: "article-production-v1", description: "Reusable editorial workflow for longform article production.", }; const workflowId = "11111111-1111-1111-1111-111111111111"; const intakeStageId = "22222222-2222-2222-2222-222222222222"; const draftStageId = "33333333-3333-3333-3333-333333333333"; const workflowPart = { key: "required-context-checklist", type: "checklist", title: "Required context checklist", payload: { prompt: "Identify missing audience, keyword, and source constraints.", config: { required_fields: ["audience", "primary_keyword", "source_rules"], block_on_missing: true, }, }, acceptance_criteria: [ "Missing required context is listed explicitly.", "No free-text parsing is needed to read the checklist.", ], }; const intakeStageDraft = { stableKey: "intake-boundary-questions", displayName: "Boundary question intake", description: "Collect required editorial context before planning.", position: 1, ownerRole: "EDITOR", runnerProfileKey: "boundary-question-agent-v1", requiredInputs: ["article_brief", "target_site"], expectedOutputs: ["answered_boundary_questions"], acceptanceCriteria: ["All required boundary questions are answered."], requiresHumanApproval: true, retryPolicy: { max_attempts: 1, backoff_seconds: 0 }, parts: [workflowPart], }; const expectedIntakeStagePayload = { stable_key: "intake-boundary-questions", display_name: "Boundary question intake", description: "Collect required editorial context before planning.", position: 1, owner_role: "EDITOR", runner_profile_key: "boundary-question-agent-v1", required_inputs: ["article_brief", "target_site"], expected_outputs: ["answered_boundary_questions"], acceptance_criteria: ["All required boundary questions are answered."], requires_human_approval: true, retry_policy: { max_attempts: 1, backoff_seconds: 0 }, parts: [workflowPart], }; const workflowModelExports = loadExpectedModule( workflowModelPath, "features/admin-workflows/model", expectedWorkflowModelExports, ); const apiExports = loadExpectedModule(apiPath, "shared/pipeline-api"); const navigationExports = loadExpectedModule( navigationModelPath, "widgets/role-navigation/model", ); const defaultWorkflowDraft = expectFunction( workflowModelExports, "defaultWorkflowDraft", "features/admin-workflows/model", ); const buildCreateWorkflowPayload = expectFunction( workflowModelExports, "buildCreateWorkflowPayload", "features/admin-workflows/model", ); const defaultWorkflowStageDraft = expectFunction( workflowModelExports, "defaultWorkflowStageDraft", "features/admin-workflows/model", ); const buildCreateWorkflowStagePayload = expectFunction( workflowModelExports, "buildCreateWorkflowStagePayload", "features/admin-workflows/model", ); const moveWorkflowStage = expectFunction( workflowModelExports, "moveWorkflowStage", "features/admin-workflows/model", ); const buildReorderWorkflowStagesPayload = expectFunction( workflowModelExports, "buildReorderWorkflowStagesPayload", "features/admin-workflows/model", ); const createWorkflowTemplate = expectFunction( apiExports, "createWorkflowTemplate", "shared/pipeline-api", ); const addWorkflowStage = expectFunction( apiExports, "addWorkflowStage", "shared/pipeline-api", ); const reorderWorkflowStages = expectFunction( apiExports, "reorderWorkflowStages", "shared/pipeline-api", ); const getRoleNavigationItems = expectFunction( navigationExports, "getRoleNavigationItems", "widgets/role-navigation/model", ); if ( defaultWorkflowDraft && buildCreateWorkflowPayload && defaultWorkflowStageDraft && buildCreateWorkflowStagePayload && moveWorkflowStage && buildReorderWorkflowStagesPayload ) { await recordAssertion("workflow draft payload maps model state to API request", () => { const createPayload = buildCreateWorkflowPayload({ ...defaultWorkflowDraft(), ...workflowDraft, }); assert.deepEqual(createPayload, workflowDraft); }); await recordAssertion("workflow stage payload preserves structured parts", () => { const stagePayload = buildCreateWorkflowStagePayload({ ...defaultWorkflowStageDraft(), ...intakeStageDraft, }); assert.deepEqual(stagePayload, expectedIntakeStagePayload); }); await recordAssertion("workflow stage reorder returns ordered IDs for API", () => { const stages = [ { id: intakeStageId, stableKey: "intake-boundary-questions", position: 1 }, { id: draftStageId, stableKey: "draft-assembly", position: 2 }, ]; const reorderedStages = moveWorkflowStage(stages, draftStageId, 0); assert.deepEqual( reorderedStages.map((stage) => ({ id: stage.id, position: stage.position })), [ { id: draftStageId, position: 1 }, { id: intakeStageId, position: 2 }, ], ); assert.deepEqual(buildReorderWorkflowStagesPayload(reorderedStages), { stage_ids: [draftStageId, intakeStageId], }); }); } if (createWorkflowTemplate && addWorkflowStage && reorderWorkflowStages) { await recordAssertion("workflow API client posts create/add/reorder requests", async () => { const originalFetch = globalThis.fetch; const requests = []; const responses = [ { workflow: { id: workflowId, ...workflowDraft, version: 1, status: "DRAFT", stages: [], created_at: "2026-05-22T00:00:00Z", updated_at: "2026-05-22T00:00:00Z", }, }, { stage: { id: intakeStageId, workflow_id: workflowId, ...expectedIntakeStagePayload, }, }, { stage: { id: draftStageId, workflow_id: workflowId, ...expectedIntakeStagePayload, stable_key: "draft-assembly", display_name: "Draft assembly", position: 2, }, }, { workflow: { id: workflowId, ...workflowDraft, version: 1, status: "DRAFT", stages: [ { id: draftStageId, stable_key: "draft-assembly", position: 1 }, { id: intakeStageId, stable_key: "intake-boundary-questions", position: 2, }, ], created_at: "2026-05-22T00:00:00Z", updated_at: "2026-05-22T00:00:00Z", }, }, ]; globalThis.fetch = async (url, options) => { requests.push({ path: new URL(String(url)).pathname, method: options.method, userEmail: options.headers["X-Demo-User-Email"], body: options.body ? JSON.parse(options.body) : undefined, }); return { ok: true, status: 200, statusText: "OK", text: async () => JSON.stringify(responses.shift()), }; }; try { const created = await createWorkflowTemplate(workflowDraft); const intakeStage = await addWorkflowStage( created.workflow.id, expectedIntakeStagePayload, ); const draftStage = await addWorkflowStage(created.workflow.id, { ...expectedIntakeStagePayload, stable_key: "draft-assembly", display_name: "Draft assembly", position: 2, }); const reordered = await reorderWorkflowStages(created.workflow.id, { stage_ids: [draftStage.stage.id, intakeStage.stage.id], }); assert.equal(created.workflow.status, "DRAFT"); assert.deepEqual( reordered.workflow.stages.map((stage) => stage.id), [draftStageId, intakeStageId], ); } finally { globalThis.fetch = originalFetch; } assert.deepEqual(requests, [ { path: "/api/admin/workflows", method: "POST", userEmail: "admin@example.com", body: workflowDraft, }, { path: `/api/admin/workflows/${workflowId}/stages`, method: "POST", userEmail: "admin@example.com", body: expectedIntakeStagePayload, }, { path: `/api/admin/workflows/${workflowId}/stages`, method: "POST", userEmail: "admin@example.com", body: { ...expectedIntakeStagePayload, stable_key: "draft-assembly", display_name: "Draft assembly", position: 2, }, }, { path: `/api/admin/workflows/${workflowId}/stages/reorder`, method: "POST", userEmail: "admin@example.com", body: { stage_ids: [draftStageId, intakeStageId] }, }, ]); }); } if (getRoleNavigationItems) { await recordAssertion("Admin navigation exposes Workflows builder route", () => { const adminItems = getRoleNavigationItems("ADMIN"); assert.deepEqual( adminItems.find((item) => item.id === "workflows"), { id: "workflows", label: "Workflows", href: "/admin/workflows", roles: ["ADMIN"], }, ); }); await recordAssertion("Editor navigation does not expose Workflows", () => { const editorItems = getRoleNavigationItems("EDITOR"); assert.equal( editorItems.some( (item) => item.id === "workflows" || item.href === "/admin/workflows" || item.label === "Workflows", ), false, ); }); } if (failures.length > 0) { assert.fail( `Admin workflow builder frontend pre-requirements missing:\n- ${failures.join("\n- ")}`, ); }