const mockSupabaseFrom = jest.fn(); jest.mock('../../src/utils/supabase', () => ({ getSupabaseServiceClient: () => ({ from: mockSupabaseFrom }), })); const { getAlertsForUser, markAlertRead } = require('../../src/services/alertService'); beforeEach(() => { jest.clearAllMocks(); }); describe('alertService', () => { test('getAlertsForUser returns unread alerts', async () => { const alerts = [ { id: 'a1', alert_type: 'player_scratched', detail: 'Player X scratched', is_read: false }, ]; mockSupabaseFrom.mockImplementation(() => ({ select: (sel, opts) => { if (opts?.head) { return { eq: () => ({ eq: () => Promise.resolve({ count: 1 }) }), }; } return { eq: (col, val) => ({ eq: () => ({ order: () => Promise.resolve({ data: alerts, error: null }), }), }), }; }, })); const result = await getAlertsForUser('user-1'); expect(result.alerts).toHaveLength(1); expect(result.unread_count).toBe(1); }); test('markAlertRead returns updated alert', async () => { mockSupabaseFrom.mockReturnValue({ update: () => ({ eq: () => ({ eq: () => ({ select: () => ({ single: () => Promise.resolve({ data: { id: 'a1', is_read: true }, error: null }), }), }), }), }), }); const result = await markAlertRead('a1', 'user-1'); expect(result.id).toBe('a1'); expect(result.is_read).toBe(true); }); test('markAlertRead returns null for nonexistent alert', async () => { mockSupabaseFrom.mockReturnValue({ update: () => ({ eq: () => ({ eq: () => ({ select: () => ({ single: () => Promise.resolve({ data: null, error: { message: 'not found' } }), }), }), }), }), }); const result = await markAlertRead('nonexistent', 'user-1'); expect(result).toBeNull(); }); });