- Implemented CmsViewModel to manage CMS data loading and state. - Created MemberAreaDetailScreens for displaying member information and news. - Added MembersViewModel and MemberNewsViewModel for managing member data and news. - Developed MemberAreaScreen to provide navigation and display member-related options. - Introduced ProfileScreen and ProfileViewModel for user profile management. - Implemented state management for loading, error handling, and form updates across screens.
269 lines
9.9 KiB
TypeScript
269 lines
9.9 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { createEvent } from './setup'
|
|
import fs from 'fs/promises'
|
|
|
|
vi.mock('../server/utils/spielplan-data.js', () => ({
|
|
listSpielplanSeasons: vi.fn().mockResolvedValue(['25--26']),
|
|
readSpielplanData: vi.fn(),
|
|
validateSeasonSlug: vi.fn().mockReturnValue(true),
|
|
getCurrentSeasonSlug: vi.fn().mockReturnValue('25--26')
|
|
}))
|
|
|
|
vi.mock('../server/utils/logger.js', () => ({
|
|
error: vi.fn(),
|
|
info: vi.fn(),
|
|
warn: vi.fn()
|
|
}))
|
|
|
|
vi.mock('../server/utils/auth.js', () => ({
|
|
verifyToken: vi.fn(),
|
|
getUserFromToken: vi.fn(),
|
|
readUsers: vi.fn().mockResolvedValue([]),
|
|
migrateUserRoles: vi.fn(user => user)
|
|
}))
|
|
|
|
vi.mock('../server/utils/members.js', () => ({
|
|
readMembers: vi.fn().mockResolvedValue([]),
|
|
normalizeDate: vi.fn(v => v)
|
|
}))
|
|
|
|
const spielplanData = await import('../server/utils/spielplan-data.js')
|
|
const authUtils = await import('../server/utils/auth.js')
|
|
const memberUtils = await import('../server/utils/members.js')
|
|
|
|
import spielplanHandler from '../server/api/spielplan.get.js'
|
|
import mannschaftenHandler from '../server/api/mannschaften.get.js'
|
|
import vereinsmeisterschaftenHandler from '../server/api/vereinsmeisterschaften.get.js'
|
|
import birthdaysHandler from '../server/api/birthdays.get.js'
|
|
|
|
describe('Spielplan, Mannschaften & öffentliche Endpoints', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
spielplanData.validateSeasonSlug.mockReturnValue(true)
|
|
spielplanData.getCurrentSeasonSlug.mockReturnValue('25--26')
|
|
spielplanData.listSpielplanSeasons.mockResolvedValue(['25--26'])
|
|
authUtils.readUsers.mockResolvedValue([])
|
|
memberUtils.readMembers.mockResolvedValue([])
|
|
vi.spyOn(fs, 'readFile').mockResolvedValue('csv,data\nrow1,row2')
|
|
vi.spyOn(fs, 'access').mockResolvedValue(undefined)
|
|
})
|
|
|
|
describe('GET /api/spielplan', () => {
|
|
it('gibt Fehler bei ungültigem Saison-Slug zurück', async () => {
|
|
const event = createEvent({ query: { season: 'ungueltig' } })
|
|
spielplanData.validateSeasonSlug.mockReturnValue(false)
|
|
|
|
const result = await spielplanHandler(event)
|
|
|
|
expect(result.success).toBe(false)
|
|
})
|
|
|
|
it('gibt Fehler zurück wenn Spielplan leer ist', async () => {
|
|
const event = createEvent()
|
|
spielplanData.readSpielplanData.mockResolvedValue({ data: [], headers: [] })
|
|
|
|
const result = await spielplanHandler(event)
|
|
|
|
expect(result.success).toBe(false)
|
|
expect(result.data).toHaveLength(0)
|
|
})
|
|
|
|
it('liefert Spielplandaten mit Saisons', async () => {
|
|
const event = createEvent()
|
|
spielplanData.readSpielplanData.mockResolvedValue({
|
|
data: [['Harheimer TC 1', 'Gegner', '3:1']],
|
|
headers: ['Heimteam', 'Gastteam', 'Ergebnis'],
|
|
source: 'file',
|
|
filePath: '/some/path',
|
|
season: '25--26'
|
|
})
|
|
|
|
const result = await spielplanHandler(event)
|
|
|
|
expect(result.success).toBe(true)
|
|
expect(result.data).toHaveLength(1)
|
|
expect(result.seasons).toContain('25--26')
|
|
})
|
|
|
|
it('liefert Spielplandaten für angeforderte Saison', async () => {
|
|
const event = createEvent({ query: { season: '24--25' } })
|
|
spielplanData.readSpielplanData.mockResolvedValue({
|
|
data: [['Team A', 'Team B', '2:3']],
|
|
headers: ['Heim', 'Gast', 'Ergebnis'],
|
|
source: 'file',
|
|
filePath: '/path',
|
|
season: '24--25'
|
|
})
|
|
|
|
const result = await spielplanHandler(event)
|
|
|
|
expect(result.success).toBe(true)
|
|
expect(result.season).toBe('24--25')
|
|
})
|
|
})
|
|
|
|
describe('GET /api/mannschaften', () => {
|
|
it('lehnt ungültigen Saison-Slug ab', async () => {
|
|
const event = createEvent({ query: { season: 'invalid' } })
|
|
spielplanData.validateSeasonSlug.mockReturnValue(false)
|
|
|
|
await expect(mannschaftenHandler(event)).rejects.toMatchObject({ statusCode: 400 })
|
|
})
|
|
|
|
it('gibt 404 wenn keine Mannschaftsdatei gefunden', async () => {
|
|
const event = createEvent()
|
|
vi.spyOn(fs, 'access').mockRejectedValue(
|
|
Object.assign(new Error('ENOENT'), { code: 'ENOENT' })
|
|
)
|
|
|
|
await expect(mannschaftenHandler(event)).rejects.toMatchObject({ statusCode: 404 })
|
|
})
|
|
|
|
it('liefert CSV-Inhalt der Mannschaftsdaten', async () => {
|
|
const event = createEvent()
|
|
vi.spyOn(fs, 'access').mockResolvedValue(undefined)
|
|
vi.spyOn(fs, 'readFile').mockResolvedValue('Name,Liga\nHarheimer TC 1,Kreisliga')
|
|
|
|
const result = await mannschaftenHandler(event)
|
|
|
|
expect(result).toContain('Harheimer TC 1')
|
|
expect(result).toContain('Kreisliga')
|
|
})
|
|
|
|
it('liefert CSV für angeforderte Saison', async () => {
|
|
const event = createEvent({ query: { season: '24--25' } })
|
|
vi.spyOn(fs, 'access').mockResolvedValue(undefined)
|
|
vi.spyOn(fs, 'readFile').mockResolvedValue('Name,Liga\nHarheimer TC 2,Bezirksliga')
|
|
|
|
const result = await mannschaftenHandler(event)
|
|
|
|
expect(result).toContain('Harheimer TC 2')
|
|
})
|
|
})
|
|
|
|
describe('GET /api/vereinsmeisterschaften', () => {
|
|
it('gibt 404 wenn Datei nicht gefunden', async () => {
|
|
vi.spyOn(fs, 'access').mockRejectedValue(
|
|
Object.assign(new Error('ENOENT'), { code: 'ENOENT' })
|
|
)
|
|
vi.spyOn(fs, 'readFile').mockRejectedValue(
|
|
Object.assign(new Error('ENOENT'), { code: 'ENOENT' })
|
|
)
|
|
const event = createEvent()
|
|
|
|
await expect(vereinsmeisterschaftenHandler(event)).rejects.toMatchObject({ statusCode: 404 })
|
|
})
|
|
|
|
it('liefert CSV-Inhalt der Vereinsmeisterschaften', async () => {
|
|
vi.spyOn(fs, 'access').mockResolvedValue(undefined)
|
|
vi.spyOn(fs, 'readFile').mockResolvedValue('Jahr,Sieger\n2024,Max Muster')
|
|
const event = createEvent()
|
|
|
|
const result = await vereinsmeisterschaftenHandler(event)
|
|
|
|
expect(result).toContain('Max Muster')
|
|
expect(result).toContain('2024')
|
|
})
|
|
})
|
|
|
|
describe('GET /api/birthdays', () => {
|
|
it('gibt leere Liste zurück wenn keine Mitglieder', async () => {
|
|
const event = createEvent()
|
|
memberUtils.readMembers.mockResolvedValue([])
|
|
authUtils.readUsers.mockResolvedValue([])
|
|
|
|
const result = await birthdaysHandler(event)
|
|
|
|
expect(result.success).toBe(true)
|
|
expect(result.birthdays).toHaveLength(0)
|
|
})
|
|
|
|
it('zeigt kommende Geburtstage der nächsten 28 Tage', async () => {
|
|
const event = createEvent()
|
|
const inDays = 7
|
|
const targetDate = new Date()
|
|
targetDate.setDate(targetDate.getDate() + inDays)
|
|
const geburtsdatum = `${targetDate.getFullYear() - 30}-${String(targetDate.getMonth() + 1).padStart(2, '0')}-${String(targetDate.getDate()).padStart(2, '0')}`
|
|
|
|
memberUtils.readMembers.mockResolvedValue([
|
|
{ firstName: 'Max', lastName: 'Muster', geburtsdatum, visibility: { showBirthday: true } }
|
|
])
|
|
authUtils.readUsers.mockResolvedValue([])
|
|
|
|
const result = await birthdaysHandler(event)
|
|
|
|
expect(result.birthdays).toHaveLength(1)
|
|
expect(result.birthdays[0].name).toBe('Max Muster')
|
|
})
|
|
|
|
it('versteckt Geburtstage wenn showBirthday=false', async () => {
|
|
const event = createEvent()
|
|
const inDays = 7
|
|
const targetDate = new Date()
|
|
targetDate.setDate(targetDate.getDate() + inDays)
|
|
const geburtsdatum = `${targetDate.getFullYear() - 30}-${String(targetDate.getMonth() + 1).padStart(2, '0')}-${String(targetDate.getDate()).padStart(2, '0')}`
|
|
|
|
memberUtils.readMembers.mockResolvedValue([
|
|
{ firstName: 'Privat', lastName: 'Person', geburtsdatum, visibility: { showBirthday: false } }
|
|
])
|
|
authUtils.readUsers.mockResolvedValue([])
|
|
|
|
const result = await birthdaysHandler(event)
|
|
|
|
expect(result.birthdays).toHaveLength(0)
|
|
})
|
|
|
|
it('zeigt Geburtstage mit deaktivierter Sichtbarkeit für Vorstand', async () => {
|
|
const event = createEvent({ cookies: { auth_token: 'token' } })
|
|
const inDays = 7
|
|
const targetDate = new Date()
|
|
targetDate.setDate(targetDate.getDate() + inDays)
|
|
const geburtsdatum = `${targetDate.getFullYear() - 30}-${String(targetDate.getMonth() + 1).padStart(2, '0')}-${String(targetDate.getDate()).padStart(2, '0')}`
|
|
|
|
authUtils.verifyToken.mockReturnValue({ id: 'v1' })
|
|
authUtils.getUserFromToken.mockResolvedValue({ id: 'v1', roles: ['vorstand'], active: true })
|
|
memberUtils.readMembers.mockResolvedValue([
|
|
{ firstName: 'Privat', lastName: 'Person', geburtsdatum, visibility: { showBirthday: false } }
|
|
])
|
|
authUtils.readUsers.mockResolvedValue([])
|
|
|
|
const result = await birthdaysHandler(event)
|
|
|
|
// Vorstand sieht alle, auch private
|
|
expect(result.birthdays).toHaveLength(1)
|
|
})
|
|
|
|
it('akzeptiert Bearer-Token für Android-Clients', async () => {
|
|
const event = createEvent({ headers: { authorization: 'Bearer android-token' } })
|
|
const inDays = 7
|
|
const targetDate = new Date()
|
|
targetDate.setDate(targetDate.getDate() + inDays)
|
|
const geburtsdatum = `${targetDate.getFullYear() - 30}-${String(targetDate.getMonth() + 1).padStart(2, '0')}-${String(targetDate.getDate()).padStart(2, '0')}`
|
|
|
|
authUtils.verifyToken.mockReturnValue({ id: 'v1' })
|
|
authUtils.getUserFromToken.mockResolvedValue({ id: 'v1', roles: ['vorstand'], active: true })
|
|
memberUtils.readMembers.mockResolvedValue([
|
|
{ firstName: 'Android', lastName: 'Privat', geburtsdatum, visibility: { showBirthday: false } }
|
|
])
|
|
authUtils.readUsers.mockResolvedValue([])
|
|
|
|
const result = await birthdaysHandler(event)
|
|
|
|
expect(authUtils.verifyToken).toHaveBeenCalledWith('android-token')
|
|
expect(result.birthdays).toHaveLength(1)
|
|
})
|
|
|
|
it('ignoriert Mitglieder ohne Geburtsdatum', async () => {
|
|
const event = createEvent()
|
|
memberUtils.readMembers.mockResolvedValue([
|
|
{ firstName: 'Kein', lastName: 'Datum', geburtsdatum: null }
|
|
])
|
|
authUtils.readUsers.mockResolvedValue([])
|
|
|
|
const result = await birthdaysHandler(event)
|
|
|
|
expect(result.birthdays).toHaveLength(0)
|
|
})
|
|
})
|
|
})
|