import main from '../morning-report'; describe('morning-report: stalled-state and report composition', () => { const OLD_ENV = process.env; let logs: string[] = []; beforeEach(() => { logs = []; // @ts-ignore global.console = { log: (msg: string) => logs.push(msg) }; jest.resetModules(); jest.clearAllMocks(); }); afterEach(() => { process.env = OLD_ENV }); it('detects stalled workflow and formats report', async () => { const now = new Date('2026-03-26T18:00:00.000Z'); const fakeStatus = { currentPhase: 'parse', overallStatus: 'running', lastUpdated: '2026-03-26T16:00:00.000Z', lastFailureReason: null, nextAction: '', completedPhases: ['a'] }; const fakeCommits = [ { hash: 'abc123', msg: 'test commit', date: '2026-03-26T10:00:00.000Z' } ]; const fakePending = [ { id: 'foo', eventType: 'phase_started', phase: 'parse', timestamp: '2026-03-26T16:00:00.000Z', summary: 'Phase parse started', relayStatus: 'pending' } ]; // Mock deps jest.mock('../../src/backend/services/WorkflowStatusManager', () => ({ WorkflowStatusManager: jest.fn().mockImplementation(() => ({ read: async () => fakeStatus })) })); jest.mock('../../src/backend/services/PhaseUpdateQueue', () => ({ getPendingPhaseUpdates: async () => fakePending, getAllPhaseUpdates: async () => fakePending })); jest.spyOn(require('../morning-report'), 'getRecentCommits').mockResolvedValue(fakeCommits); // Main await main({ commitWindowHours: 24, stalledThresholdMinutes: 60, now }); expect(logs.join('\n')).toContain('⚠️ Workflow Stalled'); expect(logs.join('\n')).toContain('abc123'); expect(logs.join('\n')).toContain('phase_started'); expect(logs.join('\n')).toContain('pending'); }); it('shows no blockers, no stalled, no pending, no commits', async () => { const now = new Date('2026-03-26T11:00:00.000Z'); // No status, no events, no commits jest.mock('../../src/backend/services/WorkflowStatusManager', () => ({ WorkflowStatusManager: jest.fn().mockImplementation(() => ({ read: async () => null })) })); jest.mock('../../src/backend/services/PhaseUpdateQueue', () => ({ getPendingPhaseUpdates: async () => [], getAllPhaseUpdates: async () => [] })); jest.spyOn(require('../morning-report'), 'getRecentCommits').mockResolvedValue([]); await main({ commitWindowHours: 24, stalledThresholdMinutes: 60, now }); expect(logs.join('\n')).toContain('(No commits in window)'); expect(logs.join('\n')).toContain('No workflow status available'); expect(logs.join('\n')).toContain('All phase updates relayed'); }); });