import { describe, it, expect, vi, beforeEach } from 'vitest' import { readFileSync } from 'node:fs' import { resolve } from 'node:path' vi.mock('../../../server/utils/adminAlerts.js', () => ({ computeAllAlerts: vi.fn(), ALERT_METADATA: { slack_invite_failed: { title: 'Slack invites failed', severity: 'critical' }, no_slack_handle_week: { title: 'Active members without a Slack handle', severity: 'attention' }, stuck_pending_payment: { title: 'Members stuck in pending payment', severity: 'attention' }, member_suspended: { title: 'Suspended members', severity: 'attention' }, preregistrant_selected_not_invited: { title: 'Pre-registrants selected but not invited', severity: 'attention' }, preregistrant_expired: { title: 'Expired pre-registrant invitations', severity: 'attention' }, event_draft_imminent: { title: 'Draft events with imminent start', severity: 'critical' }, event_near_capacity: { title: 'Events approaching capacity', severity: 'attention' }, tag_suggestions_pending: { title: 'Pending tag suggestions', severity: 'attention' } } })) vi.mock('../../../server/models/adminAlertDismissal.js', () => ({ default: { findOneAndUpdate: vi.fn(), find: vi.fn(), deleteMany: vi.fn() }, ADMIN_ALERT_TYPES: [ 'slack_invite_failed', 'no_slack_handle_week', 'stuck_pending_payment', 'member_suspended', 'preregistrant_selected_not_invited', 'preregistrant_expired', 'event_draft_imminent', 'event_near_capacity', 'tag_suggestions_pending' ] })) vi.mock('../../../server/utils/mongoose.js', () => ({ connectDB: vi.fn() })) vi.stubGlobal('adminAlertDismissSchema', {}) vi.stubGlobal('adminAlertRestoreSchema', {}) import getHandler from '../../../server/api/admin/alerts/index.get.js' import dismissHandler from '../../../server/api/admin/alerts/dismiss.post.js' import dismissedHandler from '../../../server/api/admin/alerts/dismissed.get.js' import restoreHandler from '../../../server/api/admin/alerts/restore.post.js' import { computeAllAlerts } from '../../../server/utils/adminAlerts.js' import AdminAlertDismissal from '../../../server/models/adminAlertDismissal.js' import { createMockEvent } from '../helpers/createMockEvent.js' describe('GET /api/admin/alerts', () => { beforeEach(() => { vi.clearAllMocks() requireAdmin.mockResolvedValue({ _id: { toString: () => 'admin-1' } }) }) describe('source inspection', () => { const source = readFileSync( resolve(import.meta.dirname, '../../../server/api/admin/alerts/index.get.js'), 'utf-8' ) it('calls requireAdmin before any business logic', () => { const adminIdx = source.indexOf('requireAdmin(event)') const computeIdx = source.indexOf('computeAllAlerts(') expect(adminIdx).toBeGreaterThan(-1) expect(computeIdx).toBeGreaterThan(-1) expect(adminIdx).toBeLessThan(computeIdx) }) }) it('returns the active alerts for the current admin', async () => { computeAllAlerts.mockResolvedValue([ { type: 'slack_invite_failed', severity: 'critical', title: 'Slack invites failed', count: 1, items: [{ id: 'm1', label: 'Alex', sublabel: 'alex@example.com', href: '/admin/members/m1' }], signature: 'abc' } ]) const event = createMockEvent({ method: 'GET', path: '/api/admin/alerts' }) const result = await getHandler(event) expect(result).toEqual({ alerts: [ { type: 'slack_invite_failed', severity: 'critical', title: 'Slack invites failed', count: 1, items: [{ id: 'm1', label: 'Alex', sublabel: 'alex@example.com', href: '/admin/members/m1' }], signature: 'abc' } ] }) expect(computeAllAlerts).toHaveBeenCalledWith('admin-1') }) it('rejects non-admin requests', async () => { requireAdmin.mockRejectedValue(createError({ statusCode: 403, statusMessage: 'Forbidden' })) const event = createMockEvent({ method: 'GET', path: '/api/admin/alerts' }) await expect(getHandler(event)).rejects.toMatchObject({ statusCode: 403 }) }) }) describe('POST /api/admin/alerts/dismiss', () => { beforeEach(() => { vi.clearAllMocks() requireAdmin.mockResolvedValue({ _id: { toString: () => 'admin-1' } }) validateBody.mockResolvedValue({ alertType: 'slack_invite_failed', signature: 'abc' }) }) describe('source inspection', () => { const source = readFileSync( resolve(import.meta.dirname, '../../../server/api/admin/alerts/dismiss.post.js'), 'utf-8' ) it('calls requireAdmin before validateBody', () => { const adminIdx = source.indexOf('requireAdmin(event)') const validateIdx = source.indexOf('validateBody(event') expect(adminIdx).toBeGreaterThan(-1) expect(validateIdx).toBeGreaterThan(-1) expect(adminIdx).toBeLessThan(validateIdx) }) }) it('upserts a dismissal with the current signature', async () => { AdminAlertDismissal.findOneAndUpdate.mockResolvedValue({}) const event = createMockEvent({ method: 'POST', path: '/api/admin/alerts/dismiss', body: { alertType: 'slack_invite_failed', signature: 'abc' } }) const result = await dismissHandler(event) expect(result).toEqual({ ok: true }) expect(AdminAlertDismissal.findOneAndUpdate).toHaveBeenCalledWith( { adminId: 'admin-1', alertType: 'slack_invite_failed' }, { $set: { signature: 'abc', dismissedAt: expect.any(Date) } }, { upsert: true, new: true } ) }) it('rejects non-admin requests', async () => { requireAdmin.mockRejectedValue(createError({ statusCode: 403, statusMessage: 'Forbidden' })) const event = createMockEvent({ method: 'POST', path: '/api/admin/alerts/dismiss', body: { alertType: 'slack_invite_failed', signature: 'abc' } }) await expect(dismissHandler(event)).rejects.toMatchObject({ statusCode: 403 }) }) it('rejects invalid body via validateBody', async () => { validateBody.mockRejectedValue(createError({ statusCode: 400, statusMessage: 'Validation failed' })) const event = createMockEvent({ method: 'POST', path: '/api/admin/alerts/dismiss', body: { alertType: 'unknown', signature: 'abc' } }) await expect(dismissHandler(event)).rejects.toMatchObject({ statusCode: 400 }) }) }) describe('GET /api/admin/alerts/dismissed', () => { beforeEach(() => { vi.clearAllMocks() requireAdmin.mockResolvedValue({ _id: { toString: () => 'admin-1' } }) }) describe('source inspection', () => { const source = readFileSync( resolve(import.meta.dirname, '../../../server/api/admin/alerts/dismissed.get.js'), 'utf-8' ) it('calls requireAdmin before querying dismissals', () => { const adminIdx = source.indexOf('requireAdmin(event)') const findIdx = source.indexOf('AdminAlertDismissal.find') expect(adminIdx).toBeGreaterThan(-1) expect(findIdx).toBeGreaterThan(-1) expect(adminIdx).toBeLessThan(findIdx) }) }) it('returns dismissed alerts with title and severity joined from metadata', async () => { AdminAlertDismissal.find.mockReturnValue({ sort: () => ({ lean: () => Promise.resolve([ { alertType: 'slack_invite_failed', signature: 'abc', dismissedAt: new Date('2026-04-08T10:00:00Z') }, { alertType: 'member_suspended', signature: 'def', dismissedAt: new Date('2026-04-07T10:00:00Z') } ]) }) }) const event = createMockEvent({ method: 'GET', path: '/api/admin/alerts/dismissed' }) const result = await dismissedHandler(event) expect(AdminAlertDismissal.find).toHaveBeenCalledWith({ adminId: 'admin-1' }) expect(result.dismissed).toEqual([ { alertType: 'slack_invite_failed', title: 'Slack invites failed', severity: 'critical', dismissedAt: new Date('2026-04-08T10:00:00Z') }, { alertType: 'member_suspended', title: 'Suspended members', severity: 'attention', dismissedAt: new Date('2026-04-07T10:00:00Z') } ]) }) it('returns empty list when no dismissals exist', async () => { AdminAlertDismissal.find.mockReturnValue({ sort: () => ({ lean: () => Promise.resolve([]) }) }) const event = createMockEvent({ method: 'GET', path: '/api/admin/alerts/dismissed' }) const result = await dismissedHandler(event) expect(result).toEqual({ dismissed: [] }) }) it('filters out orphaned slugs not present in ALERT_METADATA', async () => { AdminAlertDismissal.find.mockReturnValue({ sort: () => ({ lean: () => Promise.resolve([ { alertType: 'slack_invite_failed', signature: 'abc', dismissedAt: new Date() }, { alertType: 'legacy_removed_alert', signature: 'xyz', dismissedAt: new Date() } ]) }) }) const event = createMockEvent({ method: 'GET', path: '/api/admin/alerts/dismissed' }) const result = await dismissedHandler(event) expect(result.dismissed).toHaveLength(1) expect(result.dismissed[0].alertType).toBe('slack_invite_failed') }) it('rejects non-admin requests', async () => { requireAdmin.mockRejectedValue(createError({ statusCode: 403, statusMessage: 'Forbidden' })) const event = createMockEvent({ method: 'GET', path: '/api/admin/alerts/dismissed' }) await expect(dismissedHandler(event)).rejects.toMatchObject({ statusCode: 403 }) }) }) describe('POST /api/admin/alerts/restore', () => { beforeEach(() => { vi.clearAllMocks() requireAdmin.mockResolvedValue({ _id: { toString: () => 'admin-1' } }) validateBody.mockResolvedValue({ alertTypes: ['slack_invite_failed', 'member_suspended'] }) }) describe('source inspection', () => { const source = readFileSync( resolve(import.meta.dirname, '../../../server/api/admin/alerts/restore.post.js'), 'utf-8' ) it('calls requireAdmin before validateBody', () => { const adminIdx = source.indexOf('requireAdmin(event)') const validateIdx = source.indexOf('validateBody(event') expect(adminIdx).toBeGreaterThan(-1) expect(validateIdx).toBeGreaterThan(-1) expect(adminIdx).toBeLessThan(validateIdx) }) }) it('deletes dismissals matching the provided alertTypes and returns count', async () => { AdminAlertDismissal.deleteMany.mockResolvedValue({ deletedCount: 2 }) const event = createMockEvent({ method: 'POST', path: '/api/admin/alerts/restore', body: { alertTypes: ['slack_invite_failed', 'member_suspended'] } }) const result = await restoreHandler(event) expect(result).toEqual({ ok: true, restored: 2 }) expect(AdminAlertDismissal.deleteMany).toHaveBeenCalledWith({ adminId: 'admin-1', alertType: { $in: ['slack_invite_failed', 'member_suspended'] } }) }) it('returns restored: 0 when deleteMany returns no deletedCount', async () => { AdminAlertDismissal.deleteMany.mockResolvedValue({}) const event = createMockEvent({ method: 'POST', path: '/api/admin/alerts/restore', body: { alertTypes: ['slack_invite_failed'] } }) const result = await restoreHandler(event) expect(result).toEqual({ ok: true, restored: 0 }) }) it('rejects non-admin requests', async () => { requireAdmin.mockRejectedValue(createError({ statusCode: 403, statusMessage: 'Forbidden' })) const event = createMockEvent({ method: 'POST', path: '/api/admin/alerts/restore', body: { alertTypes: ['slack_invite_failed'] } }) await expect(restoreHandler(event)).rejects.toMatchObject({ statusCode: 403 }) }) it('rejects invalid body via validateBody', async () => { validateBody.mockRejectedValue(createError({ statusCode: 400, statusMessage: 'Validation failed' })) const event = createMockEvent({ method: 'POST', path: '/api/admin/alerts/restore', body: { alertTypes: [] } }) await expect(restoreHandler(event)).rejects.toMatchObject({ statusCode: 400 }) }) })