import { describe, it, expect, vi, beforeEach } from 'vitest' import { getEscalationThreshold, escalateOverdueCapa } from '@/lib/notifications/capa-escalation' vi.mock('resend', () => { const sendMock = vi.fn().mockResolvedValue({ data: { id: 'email-id' }, error: null }) function ResendMock() { return { emails: { send: sendMock } } } return { Resend: ResendMock } }) vi.mock('@/lib/notifications/whatsapp', () => ({ sendWhatsAppMessage: vi.fn().mockResolvedValue(undefined), })) vi.mock('@/lib/settings', () => ({ getApiKey: vi.fn().mockResolvedValue('test-cred'), })) describe('getEscalationThreshold', () => { function daysFromNow(n: number): string { const d = new Date() d.setDate(d.getDate() + n) return d.toISOString().split('T')[0] } it('returns warning_3d when due in 3 days', () => { expect(getEscalationThreshold(daysFromNow(3))).toBe('warning_3d') }) it('returns due_today when due today', () => { expect(getEscalationThreshold(daysFromNow(0))).toBe('due_today') }) it('returns overdue_3d when 3 days past due', () => { expect(getEscalationThreshold(daysFromNow(-3))).toBe('overdue_3d') }) it('returns overdue_7d when 7 days past due', () => { expect(getEscalationThreshold(daysFromNow(-7))).toBe('overdue_7d') }) it('returns null for 2 days before due (no threshold)', () => { expect(getEscalationThreshold(daysFromNow(2))).toBeNull() }) it('returns null for 4 days before due', () => { expect(getEscalationThreshold(daysFromNow(4))).toBeNull() }) }) describe('escalateOverdueCapa — WhatsApp', () => { function daysFromNow(n: number): string { const d = new Date() d.setDate(d.getDate() + n) return d.toISOString().split('T')[0] } function makeSupabaseMock(overrides: { capas?: unknown[] alreadySent?: unknown hseUsers?: unknown[] }) { const { capas = [], alreadySent = null, hseUsers = [] } = overrides // Each .from() call returns a fresh chainable builder // We track call order: first from('capa_actions'), then from('notifications_log'), then from('users'), then from('notifications_log').insert let fromCallIndex = 0 const makeChain = (resolvedValue: unknown) => { const chain: Record = {} const methods = ['select', 'not', 'eq', 'in', 'limit', 'maybeSingle', 'insert'] for (const m of methods) { chain[m] = vi.fn(() => chain) } // Terminal: awaiting the chain resolves to resolvedValue Object.defineProperty(chain, 'then', { get() { return (resolve: (v: unknown) => unknown) => Promise.resolve(resolvedValue).then(resolve) }, }) return chain } const supabase = { from: vi.fn(() => { const index = fromCallIndex++ if (index === 0) return makeChain({ data: capas, error: null }) // capa_actions if (index === 1) return makeChain({ data: alreadySent, error: null }) // notifications_log check if (index === 2) return makeChain({ data: hseUsers, error: null }) // hse users for CC return makeChain({ data: null, error: null }) // notifications_log insert }), } return supabase as unknown as import('@supabase/supabase-js').SupabaseClient } beforeEach(() => { vi.clearAllMocks() }) it('sends WhatsApp when threshold is overdue_7d and owner has phone', async () => { const { sendWhatsAppMessage: mockWA } = await import('@/lib/notifications/whatsapp') const supabase = makeSupabaseMock({ capas: [ { id: 'capa-1', description: 'Fix safety barrier', due_date: daysFromNow(-7), incident_id: 'inc-1', incidents: { reference_no: 'KL-202501-0001', site_id: 'site-1' }, owner: { email: 'owner@test.com', name: 'Alice', phone: '60123456789' }, }, ], alreadySent: null, hseUsers: [], }) await escalateOverdueCapa(supabase) expect(mockWA).toHaveBeenCalledWith( expect.stringMatching(/^\d+$/), 'ims_capa_overdue', expect.arrayContaining([expect.any(String)]), 'test-cred', 'test-cred', ) }) it('does NOT send WhatsApp when threshold is warning_3d', async () => { const { sendWhatsAppMessage: mockWA } = await import('@/lib/notifications/whatsapp') vi.clearAllMocks() const supabase = makeSupabaseMock({ capas: [ { id: 'capa-2', description: 'Inspect equipment', due_date: daysFromNow(3), incident_id: 'inc-2', incidents: { reference_no: 'KL-202501-0002', site_id: 'site-1' }, owner: { email: 'owner@test.com', name: 'Bob', phone: '60129876543' }, }, ], alreadySent: null, hseUsers: [], }) await escalateOverdueCapa(supabase) expect(mockWA).not.toHaveBeenCalled() }) })