Files
harheimertc/tests/news-endpoints.spec.ts
Torsten Schulz (local) 5da11d2e4d
Some checks failed
Code Analysis and Production Deploy / analyze (push) Failing after 7m50s
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Has been skipped
Fix in news, first android notification service
2026-06-10 13:47:33 +02:00

189 lines
6.8 KiB
TypeScript
Raw Permalink 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()
}))
vi.mock('../server/utils/push-notifications.js', () => ({
sendNewNewsPush: vi.fn().mockResolvedValue({ sent: 1, skipped: false })
}))
const authUtils = await import('../server/utils/auth.js')
const newsUtils = await import('../server/utils/news.js')
const pushUtils = await import('../server/utils/push-notifications.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 })
)
expect(pushUtils.sendNewNewsPush).toHaveBeenCalledWith(
expect.objectContaining({ title: 'Neue Info', content: 'Inhalt hier', isPublic: true })
)
})
it('sendet keinen Push bei News-Update', async () => {
const event = adminEvent()
newsUtils.saveNews.mockResolvedValue(undefined)
mockSuccessReadBody({ id: 'existing-news', title: 'Update', content: 'Inhalt' })
await newsPostHandler(event)
expect(pushUtils.sendNewNewsPush).not.toHaveBeenCalled()
})
it('sendet keinen Push bei versteckten News', async () => {
const event = adminEvent()
newsUtils.saveNews.mockResolvedValue(undefined)
mockSuccessReadBody({ title: 'Intern', content: 'Inhalt', isHidden: true })
await newsPostHandler(event)
expect(pushUtils.sendNewNewsPush).not.toHaveBeenCalled()
})
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')
})
})
})