import { describe, it, expect } from 'vitest'; import { buildSlackPayload } from './slack.js'; import type { NotificationEvent } from './types.js'; const base: NotificationEvent = { event: 'succeeded', taskId: 42, taskTitle: 'Ship the widget', pieceName: 'chat', spaceName: 'Acme Workspace', taskUrl: 'https://maestro.example.com/?page=spaces&space=s1&chat=42', includeDetails: true, }; describe('buildSlackPayload', () => { it('includeDetails=true: blocks carry title, piece, workspace, task id, and link', () => { const payload = buildSlackPayload(base); expect(payload.text).toContain('Ship the widget'); expect(payload.text).toContain('タスク完了'); const json = JSON.stringify(payload.blocks); expect(json).toContain('Ship the widget'); expect(json).toContain(base.taskUrl); expect(json).toContain('chat'); expect(json).toContain('Acme Workspace'); expect(json).toContain('42'); }); it('includeDetails=false: omits task title, piece name, and workspace name', () => { const payload = buildSlackPayload({ ...base, includeDetails: false }); expect(payload.text).not.toContain('Ship the widget'); expect(payload.text).toContain('#42'); // Serialized form must not leak the detailed fields either. const json = JSON.stringify(payload); expect(json).not.toContain('Ship the widget'); expect(json).not.toContain('Acme Workspace'); expect(json).not.toContain('*Piece:*'); }); it('uses a distinct emoji per event kind', () => { const succeeded = buildSlackPayload({ ...base, event: 'succeeded' }); const failed = buildSlackPayload({ ...base, event: 'failed' }); const waiting = buildSlackPayload({ ...base, event: 'waiting_human' }); expect(succeeded.text).toContain('✅'); expect(failed.text).toContain('❌'); expect(waiting.text).toContain('❓'); }); it('handles an empty taskUrl gracefully (no link markup)', () => { const payload = buildSlackPayload({ ...base, taskUrl: '' }); const json = JSON.stringify(payload); expect(json).not.toContain('<|'); expect(json).not.toContain('undefined'); }); });