Files
harheimertc/tests/news-endpoints.spec.ts
Torsten Schulz (local) 19d7aeefb0
Some checks failed
Code Analysis and Production Deploy / analyze (push) Failing after 15s
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Has been skipped
test: expand endpoint coverage and harden deploy gate
2026-05-21 08:03:59 +02:00

161 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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')
})
})
})