/** 投递五诺的纯判定部分(OS 6.3 / 编码规范 §7.1) */ import { describe, expect, it } from 'vitest'; import { consumptionKey, decideRetry, fanOutTargets, planConsumption } from '../internal/domain/delivery.ts'; import { deadlineMillis } from '../internal/domain/listener-match.ts'; describe('decideRetry —— 处理有回执', () => { it('未超限即重投,退避递增', () => { expect(decideRetry(1)).toEqual({ kind: 'retry', delayMs: 1_000, attempt: 2 }); expect(decideRetry(2)).toEqual({ kind: 'retry', delayMs: 5_000, attempt: 3 }); expect(decideRetry(4)).toEqual({ kind: 'retry', delayMs: 120_000, attempt: 5 }); }); it('超限入死信', () => { expect(decideRetry(5)).toEqual({ kind: 'dead-letter', attempts: 5 }); }); }); describe('fanOutTargets —— 扇出与幂等', () => { it('同一任务多条监听命中只产生一份投递', () => { expect(fanOutTargets([{ taskId: 'a' }, { taskId: 'a' }, { taskId: 'b' }])).toEqual(['a', 'b']); }); }); describe('planConsumption —— 三级消费', () => { it('监听与触发独立判定:可既推进旧单又起新单', () => { const plan = planConsumption({ listenerHits: [{ taskId: 't1' }], triggerHits: ['tr1'], ownedTaskId: 't1', hasOwner: true, }); expect(plan.taskTargets).toEqual(['t1']); expect(plan.triggerTargets).toEqual(['tr1']); expect(plan.messageFallback).toBe(false); }); it('皆未命中且无主 → 消息兜底', () => { const plan = planConsumption({ listenerHits: [], triggerHits: [], ownedTaskId: undefined, hasOwner: false }); expect(plan.messageFallback).toBe(true); }); it('有主但零监听命中 → 仅记录,不兜底、不降级为无主', () => { const plan = planConsumption({ listenerHits: [], triggerHits: [], ownedTaskId: 't7', hasOwner: true }); expect(plan.messageFallback).toBe(false); expect(plan.recordOnlyTask).toBe('t7'); expect(plan.taskTargets).toEqual([]); }); it('主不是任务也算有主 —— 引擎发的工作流事件不得被当成无主件塞进收件箱', () => { // 起单 / 解析负责人 / 起任务这些事件的 target 是 workflow,不是 task。 // 只认 ownedTaskId 的话,每起一单都会给人推一条「无主入站待认领」。 const plan = planConsumption({ listenerHits: [], triggerHits: [], ownedTaskId: undefined, hasOwner: true, }); expect(plan.messageFallback).toBe(false); expect(plan.recordOnlyTask).toBeUndefined(); }); it('有主且监听命中 → 不是仅记录', () => { const plan = planConsumption({ listenerHits: [{ taskId: 't7' }], triggerHits: [], ownedTaskId: 't7', hasOwner: true, }); expect(plan.recordOnlyTask).toBeUndefined(); }); it('无主但触发命中 → 不兜底', () => { const plan = planConsumption({ listenerHits: [], triggerHits: ['tr1'], ownedTaskId: undefined, hasOwner: false }); expect(plan.messageFallback).toBe(false); }); }); describe('consumptionKey —— 幂等键', () => { it('事件 ID × 消费者', () => { expect(consumptionKey('e1', 'task:t1')).toBe('e1::task:t1'); }); }); describe('deadlineMillis —— 监听时限必配', () => { it('解析 ISO-8601 duration', () => { expect(deadlineMillis('P3D')).toBe(3 * 86_400_000); expect(deadlineMillis('PT30M')).toBe(30 * 60_000); expect(deadlineMillis('P1DT2H30M')).toBe(86_400_000 + 2 * 3_600_000 + 30 * 60_000); }); it('非法表达式抛错', () => { expect(() => deadlineMillis('3天')).toThrow(/非法时限/); }); it('零时限抛错 —— 无死等', () => { expect(() => deadlineMillis('PT0S')).toThrow(/大于零/); }); });