161 lines
5.7 KiB
TypeScript
161 lines
5.7 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||
import { createEvent, mockSuccessReadBody } from './setup'
|
||
|
||
vi.mock('../server/utils/auth.js', () => ({
|
||
verifyToken: vi.fn(),
|
||
getUserById: vi.fn(),
|
||
hasAnyRole: vi.fn((user, ...roles) => {
|
||
if (!user) return false
|
||
const userRoles = Array.isArray(user.roles) ? user.roles : (user.role ? [user.role] : [])
|
||
return roles.some(r => userRoles.includes(r))
|
||
})
|
||
}))
|
||
|
||
vi.mock('../server/utils/news.js', () => ({
|
||
readNews: vi.fn(),
|
||
saveNews: vi.fn(),
|
||
deleteNews: vi.fn()
|
||
}))
|
||
|
||
const authUtils = await import('../server/utils/auth.js')
|
||
const newsUtils = await import('../server/utils/news.js')
|
||
|
||
import newsGetHandler from '../server/api/news.get.js'
|
||
import newsPostHandler from '../server/api/news.post.js'
|
||
import newsDeleteHandler from '../server/api/news.delete.js'
|
||
|
||
describe('News API Endpoints', () => {
|
||
beforeEach(() => {
|
||
vi.clearAllMocks()
|
||
})
|
||
|
||
const adminEvent = () => {
|
||
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||
authUtils.verifyToken.mockReturnValue({ id: '1' })
|
||
authUtils.getUserById.mockResolvedValue({ id: '1', roles: ['admin'], name: 'Admin' })
|
||
authUtils.hasAnyRole.mockReturnValue(true)
|
||
return event
|
||
}
|
||
|
||
describe('GET /api/news', () => {
|
||
it('verlangt Authentifizierung', async () => {
|
||
const event = createEvent()
|
||
await expect(newsGetHandler(event)).rejects.toMatchObject({ statusCode: 401 })
|
||
})
|
||
|
||
it('lehnt ungültiges Token ab', async () => {
|
||
const event = createEvent({ cookies: { auth_token: 'bad' } })
|
||
authUtils.verifyToken.mockReturnValue(null)
|
||
await expect(newsGetHandler(event)).rejects.toMatchObject({ statusCode: 401 })
|
||
})
|
||
|
||
it('liefert News nach Datum sortiert (neueste zuerst)', async () => {
|
||
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||
authUtils.verifyToken.mockReturnValue({ id: '1' })
|
||
newsUtils.readNews.mockResolvedValue([
|
||
{ id: 'a', title: 'Alt', created: '2024-01-01' },
|
||
{ id: 'b', title: 'Neu', created: '2025-06-01' }
|
||
])
|
||
|
||
const result = await newsGetHandler(event)
|
||
|
||
expect(result.success).toBe(true)
|
||
expect(result.news[0].id).toBe('b')
|
||
expect(result.news[1].id).toBe('a')
|
||
})
|
||
})
|
||
|
||
describe('POST /api/news', () => {
|
||
it('verlangt Authentifizierung', async () => {
|
||
const event = createEvent()
|
||
mockSuccessReadBody({})
|
||
await expect(newsPostHandler(event)).rejects.toMatchObject({ statusCode: 401 })
|
||
})
|
||
|
||
it('lehnt ungültiges Token ab', async () => {
|
||
const event = createEvent({ cookies: { auth_token: 'bad' } })
|
||
authUtils.verifyToken.mockReturnValue(null)
|
||
mockSuccessReadBody({ title: 'T', content: 'C' })
|
||
await expect(newsPostHandler(event)).rejects.toMatchObject({ statusCode: 401 })
|
||
})
|
||
|
||
it('verlangt Admin- oder Vorstand-Rolle', async () => {
|
||
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||
authUtils.verifyToken.mockReturnValue({ id: '1' })
|
||
authUtils.getUserById.mockResolvedValue({ id: '1', roles: ['mitglied'] })
|
||
authUtils.hasAnyRole.mockReturnValue(false)
|
||
mockSuccessReadBody({ title: 'T', content: 'C' })
|
||
await expect(newsPostHandler(event)).rejects.toMatchObject({ statusCode: 403 })
|
||
})
|
||
|
||
it('validiert Pflichtfelder – kein Titel', async () => {
|
||
const event = adminEvent()
|
||
mockSuccessReadBody({ content: 'Nur Inhalt ohne Titel' })
|
||
await expect(newsPostHandler(event)).rejects.toMatchObject({ statusCode: 400 })
|
||
})
|
||
|
||
it('validiert Pflichtfelder – kein Inhalt', async () => {
|
||
const event = adminEvent()
|
||
mockSuccessReadBody({ title: 'Nur Titel' })
|
||
await expect(newsPostHandler(event)).rejects.toMatchObject({ statusCode: 400 })
|
||
})
|
||
|
||
it('speichert News erfolgreich', async () => {
|
||
const event = adminEvent()
|
||
newsUtils.saveNews.mockResolvedValue(undefined)
|
||
mockSuccessReadBody({ title: 'Neue Info', content: 'Inhalt hier', isPublic: true })
|
||
|
||
const result = await newsPostHandler(event)
|
||
|
||
expect(result.success).toBe(true)
|
||
expect(newsUtils.saveNews).toHaveBeenCalledWith(
|
||
expect.objectContaining({ title: 'Neue Info', content: 'Inhalt hier', isPublic: true })
|
||
)
|
||
})
|
||
|
||
it('setzt autor auf den angemeldeten Benutzer', async () => {
|
||
const event = adminEvent()
|
||
newsUtils.saveNews.mockResolvedValue(undefined)
|
||
mockSuccessReadBody({ title: 'Titel', content: 'Inhalt' })
|
||
|
||
await newsPostHandler(event)
|
||
|
||
expect(newsUtils.saveNews).toHaveBeenCalledWith(
|
||
expect.objectContaining({ author: 'Admin' })
|
||
)
|
||
})
|
||
})
|
||
|
||
describe('DELETE /api/news', () => {
|
||
it('verlangt Authentifizierung', async () => {
|
||
const event = createEvent()
|
||
await expect(newsDeleteHandler(event)).rejects.toMatchObject({ statusCode: 401 })
|
||
})
|
||
|
||
it('verlangt Admin- oder Vorstand-Rolle', async () => {
|
||
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||
authUtils.verifyToken.mockReturnValue({ id: '1' })
|
||
authUtils.getUserById.mockResolvedValue({ id: '1', roles: ['mitglied'] })
|
||
authUtils.hasAnyRole.mockReturnValue(false)
|
||
await expect(newsDeleteHandler(event)).rejects.toMatchObject({ statusCode: 403 })
|
||
})
|
||
|
||
it('validiert fehlende News-ID', async () => {
|
||
const event = adminEvent()
|
||
// query ist leer → kein id
|
||
await expect(newsDeleteHandler(event)).rejects.toMatchObject({ statusCode: 400 })
|
||
})
|
||
|
||
it('löscht News erfolgreich', async () => {
|
||
const event = adminEvent()
|
||
event.__query = { id: 'abc-123' }
|
||
newsUtils.deleteNews.mockResolvedValue(undefined)
|
||
|
||
const result = await newsDeleteHandler(event)
|
||
|
||
expect(result.success).toBe(true)
|
||
expect(newsUtils.deleteNews).toHaveBeenCalledWith('abc-123')
|
||
})
|
||
})
|
||
})
|