import assert from 'node:assert/strict' import { mapBackendSession } from './collabSync' /* ────────────────────────────────────────────────────────────────────── * roleWorkItems deserialization tests * * Locks the snake_case → camelCase conversion at the collabSync boundary * so a backend rename or a missing field surfaces here, not as a silent * UI regression in WorkItemProgressCard. The schema this matches is * produced by ``snapshot_builder._build_role_work_items_for_session`` — * keep these two in sync. * ────────────────────────────────────────────────────────────────────── */ const session = mapBackendSession({ task_id: 'task-1', channel_id: 'session:task-1', status: 'running', exec_mode: 'company', is_company_runtime: true, role_work_items: { engineer: { role_key: 'engineer', role_id: 'engineer', role_name: 'Engineer', runtime_status: 'tool_active', aggregated_status: 'active', work_items: [ { work_item_id: 'wi-1', work_item_projection_id: 'proj-engineer-1', phase: 'running', kanban_column: 'in-progress', title: 'Implement summary', kind: 'execute', is_review_target: false, executor_role_id: 'engineer', executor_role_name: 'Engineer', reviewer_role_id: 'cto', created_at: 100.0, updated_at: 200.0, execution_turn_id: 'runtime-task-1', activity_sections: [ { kind: 'execute', title: 'Execute', role_name: 'Engineer', runtime_task_id: 'runtime-task-1', entries: [ { timestamp: 151.0, type: 'thinking', summary: 'plan' }, ], }, { kind: 'report', title: 'Report', role_name: 'Engineer', runtime_task_id: 'runtime-report-1', entries: [ { timestamp: 175.0, type: 'tool_call', summary: 'write_report' }, ], }, ], progress_log: [ { timestamp: 150.0, type: 'tool_call', summary: 'edit_file' }, ], }, ], }, cto: { role_key: 'cto', role_id: 'cto', role_name: 'CTO', runtime_status: 'idle', aggregated_status: 'waiting', work_items: [ { work_item_id: 'wi-2', work_item_projection_id: 'proj-engineer-1', phase: 'awaiting_manager_review', kanban_column: 'in-review', title: 'Implement summary', kind: 'execute', is_review_target: true, executor_role_id: 'engineer', reviewer_role_id: 'cto', created_at: 50.0, updated_at: 250.0, execution_turn_id: 'runtime-task-1', progress_log: [], }, ], }, }, executor_role_work_items: { engineer: { role_key: 'engineer', role_id: 'engineer', role_name: 'Engineer', runtime_status: 'idle', aggregated_status: 'waiting', work_items: [ { work_item_id: 'wi-2', work_item_projection_id: 'proj-engineer-1', phase: 'awaiting_manager_review', kanban_column: 'in-review', title: 'Implement summary', kind: 'execute', is_review_target: true, executor_role_id: 'engineer', executor_role_name: 'Engineer', reviewer_role_id: 'cto', created_at: 50.0, updated_at: 250.0, execution_turn_id: 'runtime-task-1', progress_log: [], }, ], }, }, }) const roleWorkItems = session.roleWorkItems assert.ok(roleWorkItems, 'roleWorkItems should be parsed') assert.deepEqual(Object.keys(roleWorkItems!).sort(), ['cto', 'engineer']) const engineer = roleWorkItems!.engineer assert.equal(engineer.aggregatedStatus, 'active') assert.equal(engineer.runtimeStatus, 'tool_active') assert.equal(engineer.workItems.length, 1) assert.equal(engineer.workItems[0].phase, 'running') assert.equal(engineer.workItems[0].kanbanColumn, 'in-progress') assert.equal(engineer.workItems[0].executionTurnId, 'runtime-task-1') assert.equal(engineer.workItems[0].progressLog.length, 1) assert.equal(engineer.workItems[0].progressLog[0].summary, 'edit_file') assert.equal(engineer.workItems[0].activitySections?.length, 2) assert.equal(engineer.workItems[0].activitySections?.[0].roleName, 'Engineer') assert.equal(engineer.workItems[0].activitySections?.[1].runtimeTaskId, 'runtime-report-1') assert.equal(engineer.workItems[0].activitySections?.[1].entries[0].summary, 'write_report') const cto = roleWorkItems!.cto assert.equal(cto.aggregatedStatus, 'waiting') assert.equal(cto.runtimeStatus, 'idle') assert.equal(cto.workItems.length, 1) assert.equal(cto.workItems[0].isReviewTarget, true) assert.equal(cto.workItems[0].kanbanColumn, 'in-review') const executorRoleWorkItems = session.executorRoleWorkItems assert.ok(executorRoleWorkItems, 'executorRoleWorkItems should be parsed') assert.deepEqual(Object.keys(executorRoleWorkItems!).sort(), ['engineer']) assert.equal(executorRoleWorkItems!.engineer.workItems.length, 1) assert.equal(executorRoleWorkItems!.engineer.workItems[0].isReviewTarget, true) assert.equal(executorRoleWorkItems!.engineer.workItems[0].reviewerRoleId, 'cto') assert.equal(executorRoleWorkItems!.engineer.aggregatedStatus, 'waiting') // Sanity: an unknown phase falls back to ``queued`` (safest "not started" // label) rather than throwing — a backend with a temporarily stale enum // shouldn't crash the panel. const sessionWithBadPhase = mapBackendSession({ task_id: 'task-2', channel_id: 'session:task-2', status: 'running', role_work_items: { engineer: { role_key: 'engineer', role_id: 'engineer', role_name: 'Engineer', runtime_status: 'idle', aggregated_status: 'pending', work_items: [ { work_item_id: 'wi-bad', work_item_projection_id: 'proj-x', phase: 'totally_made_up', kanban_column: 'todo', title: 'Mystery item', created_at: 10.0, updated_at: 10.0, progress_log: [], }, ], }, }, }) const fallbackRow = sessionWithBadPhase.roleWorkItems!.engineer.workItems[0] assert.equal(fallbackRow.phase, 'queued') // Empty / missing payload normalizes to ``undefined`` so the consumer can // branch on ``.roleWorkItems != null`` without falsy-checking against {}. const sessionWithoutPayload = mapBackendSession({ task_id: 'task-3', channel_id: 'session:task-3', status: 'running', }) assert.equal(sessionWithoutPayload.roleWorkItems, undefined) assert.equal(sessionWithoutPayload.executorRoleWorkItems, undefined) const sessionWithEmptyPayload = mapBackendSession({ task_id: 'task-4', channel_id: 'session:task-4', status: 'running', role_work_items: {}, executor_role_work_items: {}, }) assert.equal(sessionWithEmptyPayload.roleWorkItems, undefined) assert.equal(sessionWithEmptyPayload.executorRoleWorkItems, undefined) console.log('roleWorkItems deserialization checks passed')