From 21cf8d79b3782ecb03fab04164e76a9591d1c21f Mon Sep 17 00:00:00 2001 From: Jennie Robinson Faber Date: Wed, 8 Apr 2026 11:20:10 +0100 Subject: [PATCH] feat(admin): add POST /api/admin/alerts/dismiss endpoint --- server/api/admin/alerts/dismiss.post.js | 16 +++++++ tests/server/api/admin-alerts.test.js | 64 +++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 server/api/admin/alerts/dismiss.post.js diff --git a/server/api/admin/alerts/dismiss.post.js b/server/api/admin/alerts/dismiss.post.js new file mode 100644 index 0000000..b9f12b4 --- /dev/null +++ b/server/api/admin/alerts/dismiss.post.js @@ -0,0 +1,16 @@ +import AdminAlertDismissal from '../../../models/adminAlertDismissal.js' +import { connectDB } from '../../../utils/mongoose.js' + +export default defineEventHandler(async (event) => { + const admin = await requireAdmin(event) + const { alertType, signature } = await validateBody(event, adminAlertDismissSchema) + await connectDB() + + await AdminAlertDismissal.findOneAndUpdate( + { adminId: admin._id.toString(), alertType }, + { $set: { signature, dismissedAt: new Date() } }, + { upsert: true, new: true } + ) + + return { ok: true } +}) diff --git a/tests/server/api/admin-alerts.test.js b/tests/server/api/admin-alerts.test.js index 8ea6011..58d7d23 100644 --- a/tests/server/api/admin-alerts.test.js +++ b/tests/server/api/admin-alerts.test.js @@ -27,8 +27,12 @@ vi.mock('../../../server/utils/mongoose.js', () => ({ connectDB: vi.fn() })) +vi.stubGlobal('adminAlertDismissSchema', {}) + import getHandler from '../../../server/api/admin/alerts/index.get.js' +import dismissHandler from '../../../server/api/admin/alerts/dismiss.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', () => { @@ -87,3 +91,63 @@ describe('GET /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 }) + }) +})