/** 人类工具三规矩的机检 —— domain 测试零 mock(OS 05 / AM 06) */ import { describe, expect, it } from 'vitest'; import type { AcceptanceWorkOrder, ChatCard, DeliverableId, ExecutionWorkOrder, WorkOrderId, WorkOrderReply, } from '@contracts'; import { allCriteriaGreen, arrivalOf, checkChatCard, checkChatCardReply, checkWorkOrder, checkWorkOrderReply, producesCompletion, } from '../internal/domain/card-rules.ts'; const OID = 'wo-1' as WorkOrderId; const execution = (over: Partial = {}): ExecutionWorkOrder => ({ id: OID, at: '2026-07-27T10:00:00.000Z', mode: 'execution', title: '补齐 RFQ 关键字段', basis: [{ k: '缺口', v: 'PN / EAU / 交期' }], ask: '请向客户追问缺失字段', fields: [{ key: 'pn', label: 'PN', kind: 'text', required: true }], ...over, }); const acceptance = (over: Partial = {}): AcceptanceWorkOrder => ({ id: OID, at: '2026-07-27T10:00:00.000Z', mode: 'acceptance', title: '验收统一受理记录', basis: [{ k: '条目数', v: '12' }], deliverable: 'd-1' as DeliverableId, criteria: [{ label: '八字段齐', level: 'L1', pass: true }], fields: [], ...over, }); describe('调用必附上下文 —— 裸调用基座拒发', () => { it('basis 为空即拒', () => { expect(checkWorkOrder(execution({ basis: [] })).map((v) => v.code)).toContain('bare-call'); }); it('决策卡 basis 为空即拒', () => { const card: ChatCard = { id: 'c1' as never, at: '2026-07-27T10:00:00.000Z', k: 'decision', ask: '选一个', basis: [], options: [ { value: 'a', label: 'A' }, { value: 'b', label: 'B' }, ], allowCustom: false, }; expect(checkChatCard(card).map((v) => v.code)).toContain('bare-call'); }); }); describe('工单形态机检', () => { it('规范的执行工单通过', () => { expect(checkWorkOrder(execution())).toEqual([]); }); it('执行工单 = 指导书 + 空表单,表单不得为空', () => { expect(checkWorkOrder(execution({ fields: [] })).map((v) => v.code)).toContain('fields-missing'); }); it('radio 字段缺选项即拒', () => { const v = checkWorkOrder(execution({ fields: [{ key: 'x', label: 'X', kind: 'radio' }] })); expect(v.map((i) => i.code)).toContain('options-missing'); }); it('字段 key 重复即拒', () => { const v = checkWorkOrder( execution({ fields: [ { key: 'x', label: 'X', kind: 'text' }, { key: 'x', label: 'Y', kind: 'text' }, ], }), ); expect(v.map((i) => i.code)).toContain('field-key-duplicate'); }); it('验收工单必须列判据', () => { expect(checkWorkOrder(acceptance({ criteria: [] })).map((v) => v.code)).toContain('criteria-missing'); }); }); describe('到达面两路', () => { it('负责人的卡进任务对话流', () => { expect(arrivalOf('s1', 's1')).toBe('task-chat'); }); it('别人的卡走系统消息 —— 跨人卡不是责任转移', () => { expect(arrivalOf('s2', 's1')).toBe('system-message'); }); }); describe('回传校验', () => { it('必填未回填即拒', () => { const reply: WorkOrderReply = { mode: 'execution', workOrderId: OID, values: {} }; expect(checkWorkOrderReply(execution(), reply).map((v) => v.code)).toContain('required-missing'); }); it('必填已填即过', () => { const reply: WorkOrderReply = { mode: 'execution', workOrderId: OID, values: { pn: 'LM358' } }; expect(checkWorkOrderReply(execution(), reply)).toEqual([]); }); it('驳回必附意见', () => { const reply: WorkOrderReply = { mode: 'acceptance', workOrderId: OID, verdict: 'revise', values: {}, selectedPorts: {}, }; expect(checkWorkOrderReply(acceptance(), reply).map((v) => v.code)).toContain('comment-required'); }); it('放行通过无需意见', () => { const reply: WorkOrderReply = { mode: 'acceptance', workOrderId: OID, verdict: 'pass', values: {}, selectedPorts: {}, }; expect(checkWorkOrderReply(acceptance(), reply)).toEqual([]); }); it('已回传的工单不得再答 —— 只增不改', () => { const done = acceptance({ done: { at: 'x', by: 's1' as never, result: 'pass' } }); const reply: WorkOrderReply = { mode: 'acceptance', workOrderId: OID, verdict: 'pass', values: {}, selectedPorts: {}, }; expect(checkWorkOrderReply(done, reply).map((v) => v.code)).toEqual(['already-answered']); }); it('模式不符即拒', () => { const reply: WorkOrderReply = { mode: 'execution', workOrderId: OID, values: { pn: 'x' } }; expect(checkWorkOrderReply(acceptance(), reply).map((v) => v.code)).toEqual(['mode-mismatch']); }); }); describe('对话卡回传', () => { const decision: ChatCard = { id: 'c1' as never, at: '2026-07-27T10:00:00.000Z', k: 'decision', ask: '降不降价', basis: [{ k: '底价', v: '1.80' }], options: [ { value: 'hold', label: '守价' }, { value: 'cut', label: '让价' }, ], allowCustom: false, }; it('选项内的分支通过', () => { expect(checkChatCardReply(decision, { k: 'decision', cardId: 'c1' as never, value: 'cut' })).toEqual([]); }); it('选项外的分支在不允许自定义时即拒', () => { const v = checkChatCardReply(decision, { k: 'decision', cardId: 'c1' as never, value: 'other' }); expect(v.map((i) => i.code)).toEqual(['unknown-option']); }); it('自定义收尾项必附理由', () => { const open = { ...decision, allowCustom: true }; expect( checkChatCardReply(open, { k: 'decision', cardId: 'c1' as never, value: 'other' }).map((i) => i.code), ).toEqual(['note-required']); expect( checkChatCardReply(open, { k: 'decision', cardId: 'c1' as never, value: 'other', note: '客户口头承诺追单' }), ).toEqual([]); }); it('补全卡两条路必走其一', () => { const card: ChatCard = { id: 'c2' as never, at: '2026-07-27T10:00:00.000Z', k: 'elicitation', ask: '补交期', fields: [{ key: 'lead', label: '交期' }], }; expect( checkChatCardReply(card, { k: 'elicitation', cardId: 'c2' as never, values: {} }).map((i) => i.code), ).toEqual(['answer-required']); expect(checkChatCardReply(card, { k: 'elicitation', cardId: 'c2' as never, values: { lead: '8周' } })).toEqual([]); expect( checkChatCardReply(card, { k: 'elicitation', cardId: 'c2' as never, values: {}, reason: '客户未答复' }), ).toEqual([]); }); }); describe('完成只出自放行通过', () => { it('放行通过才产生完成', () => { expect( producesCompletion({ mode: 'acceptance', workOrderId: OID, verdict: 'pass', values: {}, selectedPorts: {} }), ).toBe(true); }); it('驳回不产生完成 —— 环内回转,任务继续', () => { expect( producesCompletion({ mode: 'acceptance', workOrderId: OID, verdict: 'redo', values: {}, selectedPorts: {}, comment: '重做', }), ).toBe(false); }); it('执行工单回传不产生完成', () => { expect(producesCompletion({ mode: 'execution', workOrderId: OID, values: {} })).toBe(false); }); it('判据全绿只是可提交,不等于完成', () => { expect(allCriteriaGreen(acceptance())).toBe(true); expect(allCriteriaGreen(acceptance({ criteria: [{ label: 'x', level: 'L2', pass: false }] }))).toBe(false); }); });