168 lines
5.1 KiB
JavaScript
168 lines
5.1 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { 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 sourcePath = path.resolve(testDir, "../src/features/article-detail/model.ts");
|
|
const source = readFileSync(sourcePath, "utf8");
|
|
const compiled = ts.transpileModule(source, {
|
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 },
|
|
});
|
|
|
|
const moduleExports = {};
|
|
new Function("exports", compiled.outputText)(moduleExports);
|
|
|
|
const { buildArticleTimeline, buildJobDetailRows, buildDetailSummary, buildJobControlState } = moduleExports;
|
|
|
|
const detail = {
|
|
article: {
|
|
id: "a1",
|
|
target_site_id: "site1",
|
|
status: "PARALLEL_PRODUCTION_RUNNING",
|
|
publishing_status: "PUBLISH_NOT_STARTED",
|
|
brief_description: "desc",
|
|
language: "en",
|
|
content_type: "longform_guide",
|
|
created_at: "2026-05-21T00:00:00Z",
|
|
updated_at: "2026-05-21T00:00:00Z",
|
|
},
|
|
target_site: { name: "Demo Site" },
|
|
workflow_template_snapshot: {
|
|
id: "workflow-1",
|
|
name: "Demo workflow",
|
|
slug: "demo-workflow",
|
|
version: 2,
|
|
stage_summary: [
|
|
{
|
|
id: "stage-2",
|
|
stable_key: "draft",
|
|
display_name: "Draft assembly",
|
|
position: 2,
|
|
owner_role: "EDITOR",
|
|
runner_profile_key: "draft-agent",
|
|
requires_human_approval: false,
|
|
},
|
|
{
|
|
id: "stage-1",
|
|
stable_key: "intake",
|
|
display_name: "Intake",
|
|
position: 1,
|
|
owner_role: "EDITOR",
|
|
runner_profile_key: "intake-agent",
|
|
requires_human_approval: true,
|
|
},
|
|
],
|
|
},
|
|
timeline: [
|
|
{
|
|
id: "2",
|
|
article_id: "a1",
|
|
entry_type: "AGENT_JOB",
|
|
source: "AGENT",
|
|
event_type: "SECTION_SCAFFOLD_FAILED",
|
|
from_status: null,
|
|
to_status: null,
|
|
actor_user_id: null,
|
|
job_id: "job2",
|
|
job_type: "SECTION_SCAFFOLD",
|
|
job_status: "FAILED",
|
|
retry_eligible: true,
|
|
cancel_eligible: false,
|
|
safe_failure_summary: "Section failed.",
|
|
payload: {},
|
|
created_at: "2026-05-21T00:00:02Z",
|
|
},
|
|
{
|
|
id: "1",
|
|
article_id: "a1",
|
|
entry_type: "WORKFLOW_EVENT",
|
|
source: "USER",
|
|
event_type: "PLAN_APPROVED",
|
|
from_status: "PLAN_REVIEW_REQUIRED",
|
|
to_status: "RESEARCH_RUNNING",
|
|
actor_user_id: "u1",
|
|
job_id: null,
|
|
job_type: null,
|
|
job_status: null,
|
|
retry_eligible: false,
|
|
cancel_eligible: false,
|
|
safe_failure_summary: null,
|
|
payload: {},
|
|
created_at: "2026-05-21T00:00:01Z",
|
|
},
|
|
],
|
|
workflow_events: [],
|
|
agent_jobs: [
|
|
{
|
|
id: "job2",
|
|
article_id: "a1",
|
|
parent_job_id: null,
|
|
attempt: 1,
|
|
job_type: "SECTION_SCAFFOLD",
|
|
agent_profile: "fake-section-scaffold",
|
|
status: "FAILED",
|
|
workspace_path: null,
|
|
input_files: [],
|
|
output_files: [],
|
|
payload: { last_successful_step: "Outline written" },
|
|
error_category: "UNSUPPORTED_CLAIMS_FOUND",
|
|
error_message: "Unsupported claims introduced during scaffolding: 1",
|
|
stdout: "token=[REDACTED]",
|
|
stderr: "",
|
|
exit_code: 1,
|
|
duration_ms: 5,
|
|
queued_at: "2026-05-21T00:00:00Z",
|
|
started_at: "2026-05-21T00:00:01Z",
|
|
finished_at: "2026-05-21T00:00:02Z",
|
|
retry_eligible: true,
|
|
retry_block_reason: null,
|
|
cancel_eligible: false,
|
|
safe_failure_summary: "Section failed safely.",
|
|
},
|
|
],
|
|
publish_commit: null,
|
|
};
|
|
|
|
const timeline = buildArticleTimeline(detail);
|
|
assert.equal(timeline.length, 2);
|
|
assert.equal(timeline[0].eventType, "PLAN_APPROVED");
|
|
assert.equal(timeline[1].eventType, "SECTION_SCAFFOLD_FAILED");
|
|
assert.equal(timeline[1].source, "AGENT");
|
|
assert.equal(timeline[1].retryEligible, true);
|
|
|
|
const jobRows = buildJobDetailRows(detail.agent_jobs);
|
|
assert.equal(jobRows.length, 1);
|
|
assert.equal(jobRows[0].jobType, "SECTION_SCAFFOLD");
|
|
assert.equal(jobRows[0].status, "FAILED");
|
|
assert.equal(jobRows[0].errorCategory, "UNSUPPORTED_CLAIMS_FOUND");
|
|
assert.equal(jobRows[0].errorMessage, "Unsupported claims introduced during scaffolding: 1");
|
|
assert.equal(jobRows[0].lastSuccessfulStep, "Outline written");
|
|
assert.equal(jobRows[0].retryEligible, true);
|
|
assert.deepEqual(
|
|
buildJobControlState(jobRows[0], "ADMIN"),
|
|
{ showRetry: true, showCancel: false, showAdminLogs: true, showSafeSummary: false },
|
|
);
|
|
assert.deepEqual(
|
|
buildJobControlState(jobRows[0], "EDITOR"),
|
|
{ showRetry: false, showCancel: false, showAdminLogs: false, showSafeSummary: true },
|
|
);
|
|
|
|
const summary = buildDetailSummary(detail, {
|
|
viewerRole: "EDITOR",
|
|
viewerEmail: "editor@example.com",
|
|
});
|
|
assert.equal(summary.viewerRole, "EDITOR");
|
|
assert.equal(summary.viewerEmail, "editor@example.com");
|
|
assert.equal(summary.targetSite, "Demo Site");
|
|
assert.equal(summary.workflowTemplate.name, "Demo workflow");
|
|
assert.equal(summary.workflowTemplate.version, 2);
|
|
assert.deepEqual(
|
|
summary.workflowTemplate.stages.map((stage) => stage.stableKey),
|
|
["intake", "draft"],
|
|
);
|
|
assert.equal(summary.timeline.length, 2);
|
|
assert.equal(summary.jobDetails.length, 1);
|