import test from 'node:test'; import assert from 'node:assert/strict'; import { resolvePostTaskActiveDocId, shouldSwitchTemplateSlotToServer, pollUntilPostTaskServerDoc, } from './postTaskDeliverableSync'; import type { DeliverableItem } from '../types/workbench'; import type { WorkflowDocumentSummary } from '../api/projectDocuments'; const base = (over: Partial): DeliverableItem => ({ docId: 'doc-x', projectId: 6089, taskKey: 'intake', title: '用户初始需求收集表', stage: 'V0.1', category: 'workflow', creatorId: 1, creatorName: 'A', isFixedWorkflow: true, isTemplateOnly: true, lifecycleState: 'drafting', isClaimed: false, author: '', reviewer: '', approver: '', updatedAt: '', governanceMode: 'controlled', ...over, }); test('resolvePostTaskActiveDocId prefers server doc_proj for taskKey', () => { const docs = [ base({ docId: 'doc-p6089-intake', isTemplateOnly: true }), base({ docId: 'doc_proj6089_intake', isTemplateOnly: false, title: '用户初始需求收集表', }), ]; assert.equal( resolvePostTaskActiveDocId({ deliverables: docs, projectId: 6089, taskKey: 'intake' }), 'doc_proj6089_intake', ); }); test('resolvePostTaskActiveDocId honors preferred server doc id', () => { const docs = [ base({ docId: 'doc_proj6089_intake', isTemplateOnly: false }), base({ docId: 'doc_proj6089_user_requirements', taskKey: 'user_requirements', isTemplateOnly: false, }), ]; assert.equal( resolvePostTaskActiveDocId({ deliverables: docs, projectId: 6089, taskKey: 'intake', preferredDocId: 'doc_proj6089_intake', }), 'doc_proj6089_intake', ); }); test('shouldSwitchTemplateSlotToServer when active is local template slot', () => { const docs = [ base({ docId: 'doc-p6089-intake', isTemplateOnly: true }), base({ docId: 'doc_proj6089_intake', isTemplateOnly: false }), ]; assert.equal( shouldSwitchTemplateSlotToServer({ activeDocId: 'doc-p6089-intake', deliverables: docs, projectId: 6089, taskKey: 'intake', serverDocId: 'doc_proj6089_intake', }), true, ); assert.equal( shouldSwitchTemplateSlotToServer({ activeDocId: 'doc_proj6089_intake', deliverables: docs, projectId: 6089, taskKey: 'intake', serverDocId: 'doc_proj6089_intake', }), false, ); }); test('pollUntilPostTaskServerDoc retries until same taskKey server doc appears', async () => { const local = [base({ docId: 'doc-p6089-intake', isTemplateOnly: true })]; let calls = 0; const snaps: DeliverableItem[][] = []; const result = await pollUntilPostTaskServerDoc({ listDocs: async () => { calls += 1; if (calls < 3) return []; return [ { doc_id: 'doc_proj6089_intake', project_id: 6089, task_key: 'intake', title: '用户初始需求收集表', lifecycle_state: 'drafting', is_template_only: false, } satisfies WorkflowDocumentSummary, ]; }, getDeliverables: () => local, merge: (prev, docs) => { if (docs.length === 0) return prev; return [ ...prev.filter((d) => d.docId !== 'doc_proj6089_intake'), base({ docId: docs[0].doc_id, isTemplateOnly: false, taskKey: docs[0].task_key, }), ]; }, projectId: 6089, taskKey: 'intake', maxAttempts: 5, delayMs: 1, sleep: async () => undefined, onMerged: (merged) => { snaps.push(merged); }, }); assert.equal(result.serverDocId, 'doc_proj6089_intake'); assert.equal(result.attempts, 3); assert.ok(snaps.length >= 3); assert.ok(snaps[snaps.length - 1].some((d) => d.docId === 'doc_proj6089_intake')); }); test('pollUntilPostTaskServerDoc returns null after maxAttempts without relying on setState', async () => { const local = [base({ docId: 'doc-p6089-intake', isTemplateOnly: true })]; const result = await pollUntilPostTaskServerDoc({ listDocs: async () => [], getDeliverables: () => local, merge: (prev) => prev, projectId: 6089, taskKey: 'intake', maxAttempts: 3, delayMs: 1, sleep: async () => undefined, }); assert.equal(result.serverDocId, null); assert.equal(result.attempts, 3); assert.equal(result.merged[0]?.docId, 'doc-p6089-intake'); });