test: expand endpoint coverage and harden deploy gate
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

This commit is contained in:
Torsten Schulz (local)
2026-05-21 08:03:59 +02:00
parent 5fce08ab75
commit 19d7aeefb0
10 changed files with 1013 additions and 14 deletions

View File

@@ -0,0 +1,160 @@
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')
})
})
})