feat(events): add tag validation to admin event create/edit routes

This commit is contained in:
Jennie Robinson Faber 2026-04-09 22:32:32 +01:00
parent 3797ff7925
commit 2166ee32ca
3 changed files with 185 additions and 0 deletions

View file

@ -0,0 +1,157 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
// --- Mocks (must be before imports) ---
const mockEventSave = vi.fn()
function MockEvent(data) {
Object.assign(this, data)
this._id = 'evt-123'
this.slug = '2026-04-01-test-event'
this.save = mockEventSave.mockResolvedValue(this)
}
MockEvent.findByIdAndUpdate = vi.fn()
vi.mock('../../../server/models/event.js', () => ({ default: MockEvent }))
vi.mock('../../../server/models/tag.js', () => ({
default: { find: vi.fn() }
}))
vi.mock('../../../server/utils/mongoose.js', () => ({ connectDB: vi.fn() }))
vi.mock('../../../server/utils/auth.js', () => ({
requireAdmin: vi.fn().mockResolvedValue({ email: 'admin@example.com', role: 'admin' })
}))
vi.mock('../../../server/utils/validateBody.js', () => ({ validateBody: vi.fn() }))
vi.mock('../../../server/utils/schemas.js', () => ({
adminEventCreateSchema: {},
adminEventUpdateSchema: {}
}))
import Tag from '../../../server/models/tag.js'
import { validateBody } from '../../../server/utils/validateBody.js'
import createHandler from '../../../server/api/admin/events.post.js'
import updateHandler from '../../../server/api/admin/events/[id].put.js'
import { createMockEvent } from '../helpers/createMockEvent.js'
// Stub the global adminEventUpdateSchema used by the update route (auto-imported by Nitro)
vi.stubGlobal('adminEventUpdateSchema', {})
// --- Helpers ---
const validEventBody = {
title: 'Test Event',
description: 'A test event',
startDate: '2026-04-01T10:00:00Z',
endDate: '2026-04-01T12:00:00Z',
location: 'https://meet.example.com'
}
// --- Tests ---
describe('Event tag validation', () => {
beforeEach(() => {
vi.clearAllMocks()
// Override the global validateBody stub (from setup.js) to use our mock
globalThis.validateBody = validateBody
})
describe('create route (events.post)', () => {
it('accepts valid tags', async () => {
validateBody.mockResolvedValue({
...validEventBody,
tags: ['game-design', 'cooperatives']
})
Tag.find.mockResolvedValue([
{ slug: 'game-design' },
{ slug: 'cooperatives' }
])
const event = createMockEvent({ method: 'POST', path: '/api/admin/events' })
const result = await createHandler(event)
expect(Tag.find).toHaveBeenCalledWith({ slug: { $in: ['game-design', 'cooperatives'] } })
expect(result.tags).toEqual(['game-design', 'cooperatives'])
})
it('rejects unknown tag slugs with 400', async () => {
validateBody.mockResolvedValue({
...validEventBody,
tags: ['game-design', 'nonexistent-tag']
})
Tag.find.mockResolvedValue([
{ slug: 'game-design' }
])
const event = createMockEvent({ method: 'POST', path: '/api/admin/events' })
await expect(createHandler(event)).rejects.toMatchObject({
statusCode: 400,
statusMessage: 'Unknown tag slugs: nonexistent-tag'
})
})
it('skips validation when tags are omitted', async () => {
validateBody.mockResolvedValue({ ...validEventBody })
const event = createMockEvent({ method: 'POST', path: '/api/admin/events' })
await createHandler(event)
expect(Tag.find).not.toHaveBeenCalled()
})
it('skips validation when tags array is empty', async () => {
validateBody.mockResolvedValue({ ...validEventBody, tags: [] })
const event = createMockEvent({ method: 'POST', path: '/api/admin/events' })
await createHandler(event)
expect(Tag.find).not.toHaveBeenCalled()
})
})
describe('update route (events/[id].put)', () => {
beforeEach(() => {
MockEvent.findByIdAndUpdate.mockResolvedValue({
_id: 'evt-123',
...validEventBody,
tags: ['game-design']
})
})
it('accepts valid tags', async () => {
validateBody.mockResolvedValue({
...validEventBody,
tags: ['game-design']
})
Tag.find.mockResolvedValue([
{ slug: 'game-design' }
])
const event = createMockEvent({ method: 'PUT', path: '/api/admin/events/evt-123' })
event.context = { ...event.context, params: { id: 'evt-123' } }
const result = await updateHandler(event)
expect(Tag.find).toHaveBeenCalledWith({ slug: { $in: ['game-design'] } })
expect(result.tags).toEqual(['game-design'])
})
it('rejects unknown tag slugs with 400', async () => {
validateBody.mockResolvedValue({
...validEventBody,
tags: ['valid-tag', 'bogus-tag', 'also-bogus']
})
Tag.find.mockResolvedValue([
{ slug: 'valid-tag' }
])
const event = createMockEvent({ method: 'PUT', path: '/api/admin/events/evt-123' })
event.context = { ...event.context, params: { id: 'evt-123' } }
await expect(updateHandler(event)).rejects.toMatchObject({
statusCode: 400,
statusMessage: 'Unknown tag slugs: bogus-tag, also-bogus'
})
})
})
})