feat(admin): add POST /api/admin/alerts/dismiss endpoint
This commit is contained in:
parent
f0284c60b4
commit
21cf8d79b3
2 changed files with 80 additions and 0 deletions
16
server/api/admin/alerts/dismiss.post.js
Normal file
16
server/api/admin/alerts/dismiss.post.js
Normal file
|
|
@ -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 }
|
||||||
|
})
|
||||||
|
|
@ -27,8 +27,12 @@ vi.mock('../../../server/utils/mongoose.js', () => ({
|
||||||
connectDB: vi.fn()
|
connectDB: vi.fn()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.stubGlobal('adminAlertDismissSchema', {})
|
||||||
|
|
||||||
import getHandler from '../../../server/api/admin/alerts/index.get.js'
|
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 { computeAllAlerts } from '../../../server/utils/adminAlerts.js'
|
||||||
|
import AdminAlertDismissal from '../../../server/models/adminAlertDismissal.js'
|
||||||
import { createMockEvent } from '../helpers/createMockEvent.js'
|
import { createMockEvent } from '../helpers/createMockEvent.js'
|
||||||
|
|
||||||
describe('GET /api/admin/alerts', () => {
|
describe('GET /api/admin/alerts', () => {
|
||||||
|
|
@ -87,3 +91,63 @@ describe('GET /api/admin/alerts', () => {
|
||||||
await expect(getHandler(event)).rejects.toMatchObject({ statusCode: 403 })
|
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 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue