import { describe, expect, it, vi } from 'vitest'; import { taskIdSchema } from '@contracts'; import { afterSave } from '../save-wake.ts'; const TASK = taskIdSchema.parse('11111111-1111-4111-8111-111111111111'); /** 缺省有一张卡在等人 —— 「保存即铺格」的场景恰恰都是这一种 */ function spy(pending: ReadonlyArray = [{ id: 'card-1' }]) { const calls: string[] = []; return { calls, deps: { views: { repush: () => { calls.push('repush'); return Promise.resolve(true); } }, task: { wake: () => { calls.push('wake'); return Promise.resolve(true); } }, humanTool: { pendingCards: () => { calls.push('pending'); return Promise.resolve(pending); } }, }, }; } describe('件上保存之后', () => { it('真落下了行、且有卡在等人:先重推件,再叫醒任务 —— 配方这才有机会按最新的草稿铺格', async () => { const { calls, deps } = spy(); await afterSave(deps, TASK, 3); expect(calls).toEqual(['repush', 'pending', 'wake']); }); it('一行都没落下:什么也不做 —— 没变的东西不值得叫醒谁', async () => { const { calls, deps } = spy(); await afterSave(deps, TASK, 0); expect(calls).toEqual([]); }); /* * **没有卡在等人就别叫醒**(2026-08-29 终审 F1):`wake` 对 awaiting-event / awaiting-human * 一律拨成 running,推进器随即推一拍并记一步预算 —— 而现有九个活动包一个 auto 都没有, * 人每按一次保存就白烧一步、界面闪一次「AI 正在推进这单」;任务本来 awaiting-human * (等人裁决)时被叫醒,那一拍结束还会把它降级成 awaiting-event:等的明明还是人。 */ /* * **这条用例测的是 `pendingCards` 现读为空的那一种局面 —— 不是验收态**(2026-08-29 复审 * A 项二审:这里原话写着「验收态就是这一种」,是错的)。`pendingCards` * (`human-tool-service.ts:374`)把**未办结工单**也算在内,而验收态恰恰有一张开着的 * 验收工单 —— 真在验收态时 `pendingCards` 不空,`wake` 照样会发,不落进这条分支。 */ it('没有卡在等人(pendingCards 现读为空):件照旧重推,**不叫醒**', async () => { const { calls, deps } = spy([]); await afterSave(deps, TASK, 3); expect(calls).toEqual(['repush', 'pending']); }); it('叫醒失败不抬手,但**不再静默** —— 它是「保存即铺格」的唯一触发器', async () => { const calls: string[] = []; const said: string[] = []; const write = vi.spyOn(globalThis.process.stderr, 'write').mockImplementation((chunk) => { said.push(String(chunk)); return true; }); const deps = { views: { repush: () => { calls.push('repush'); return Promise.resolve(true); } }, task: { wake: () => Promise.reject(new Error('库连不上')) }, humanTool: { pendingCards: () => Promise.resolve([{ id: 'card-1' }]) }, }; await expect(afterSave(deps, TASK, 1)).resolves.toBeUndefined(); write.mockRestore(); expect(calls).toEqual(['repush']); expect(said.join('')).toContain('库连不上'); }); it('问不出有没有卡在等人:也不叫醒,照样说一声', async () => { const said: string[] = []; const write = vi.spyOn(globalThis.process.stderr, 'write').mockImplementation((chunk) => { said.push(String(chunk)); return true; }); const calls: string[] = []; const deps = { views: { repush: () => Promise.resolve(true) }, task: { wake: () => { calls.push('wake'); return Promise.resolve(true); } }, humanTool: { pendingCards: () => Promise.reject(new Error('库连不上')) }, }; await expect(afterSave(deps, TASK, 1)).resolves.toBeUndefined(); write.mockRestore(); expect(calls).toEqual([]); expect(said.join('')).toContain('库连不上'); }); });