// @ts-nocheck 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(), getUserFromToken: vi.fn(), readUsers: vi.fn(), readMembers: vi.fn(), writeUsers: vi.fn(), readSessions: vi.fn(), writeSessions: vi.fn(), migrateUserRoles: vi.fn((user) => { if (!user) return user if (Array.isArray(user.roles)) return user if (user.role) { user.roles = [user.role] delete user.role } else { user.roles = ['mitglied'] } return user }), 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)) }), hasRole: vi.fn((user, role) => { if (!user) return false const userRoles = Array.isArray(user.roles) ? user.roles : (user.role ? [user.role] : []) return userRoles.includes(role) }), normalizeUserEmail: vi.fn(email => String(email || '').trim().toLowerCase()), isHiddenUser: vi.fn(user => user?.hidden === true || user?.invisible === true || user?.isHidden === true || user?.systemAccount === true || user?.accountType === 'playstore_review') })) vi.mock('../server/utils/members.js', () => ({ readMembers: vi.fn(), writeMembers: vi.fn(), saveMember: vi.fn(), deleteMember: vi.fn(), normalizeDate: vi.fn((value) => value) })) const authUtils = await import('../server/utils/auth.js') const memberUtils = await import('../server/utils/members.js') import membersGetHandler from '../server/api/members.get.js' import membersPostHandler from '../server/api/members.post.js' import membersDeleteHandler from '../server/api/members.delete.js' import membersBulkHandler from '../server/api/members/bulk.post.js' import toggleMannschaftsspielerHandler from '../server/api/members/toggle-mannschaftsspieler.post.js' describe('Members API Endpoints', () => { beforeEach(() => { vi.clearAllMocks() authUtils.readUsers.mockResolvedValue([]) memberUtils.readMembers.mockResolvedValue([]) }) describe('GET /api/members', () => { it('verlangt Authentifizierung', async () => { const event = createEvent() await expect(membersGetHandler(event)).rejects.toMatchObject({ statusCode: 401 }) }) it('gibt zusammengeführte Mitglieder zurück', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.verifyToken.mockReturnValue({ id: '1' }) memberUtils.readMembers.mockResolvedValue([ { id: 'm1', firstName: 'Anna', lastName: 'Muster', email: 'anna@club.de', hasHallKey: true } ]) authUtils.readUsers.mockResolvedValue([ { id: 'u1', name: 'Ben Nutzer', email: 'ben@club.de', role: 'mitglied', active: true } ]) authUtils.getUserFromToken.mockResolvedValue({ id: '1', role: 'mitglied' }) const response = await membersGetHandler(event) expect(response.success).toBe(true) expect(response.members).toHaveLength(2) expect(response.members[0]).toHaveProperty('hasHallKey', true) }) it('zeigt Legacy-Mitglieder ohne active-Flag weiterhin an', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.verifyToken.mockReturnValue({ id: '1' }) memberUtils.readMembers.mockResolvedValue([ { id: 'm1', firstName: 'Anna', lastName: 'Muster', geburtsdatum: '2000-01-01' }, { id: 'm2', firstName: 'Offen', lastName: 'Antrag', geburtsdatum: '2001-01-01', status: 'pending' } ]) authUtils.readUsers.mockResolvedValue([]) authUtils.getUserFromToken.mockResolvedValue({ id: '1', role: 'mitglied' }) const response = await membersGetHandler(event) expect(response.success).toBe(true) expect(response.members).toHaveLength(1) expect(response.members[0].name).toBe('Anna Muster') }) it('liefert Geburtstags-Sichtbarkeit für Admin/Vorstand-Bearbeitung', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.verifyToken.mockReturnValue({ id: '1' }) memberUtils.readMembers.mockResolvedValue([ { id: 'm1', firstName: 'Anna', lastName: 'Muster', geburtsdatum: '2000-01-01', visibility: { showBirthday: false } } ]) authUtils.readUsers.mockResolvedValue([]) authUtils.getUserFromToken.mockResolvedValue({ id: '1', role: 'admin' }) const response = await membersGetHandler(event) expect(response.members).toHaveLength(1) expect(response.members[0].showBirthday).toBe(false) }) it('uebernimmt Geburtstags-Sichtbarkeit vom Login-Benutzer beim Merge', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.verifyToken.mockReturnValue({ id: '1' }) memberUtils.readMembers.mockResolvedValue([ { id: 'm1', firstName: 'Anna', lastName: 'Muster', email: 'anna@club.de', geburtsdatum: '2000-01-01', visibility: { showBirthday: true } } ]) authUtils.readUsers.mockResolvedValue([ { id: 'u1', name: 'Anna Muster', email: 'anna@club.de', active: true, visibility: { showBirthday: false } } ]) authUtils.getUserFromToken.mockResolvedValue({ id: '1', role: 'admin' }) const response = await membersGetHandler(event) expect(response.members).toHaveLength(1) expect(response.members[0].showBirthday).toBe(false) }) it('blendet unsichtbare Playstore-Benutzer und passende manuelle Einträge aus', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.verifyToken.mockReturnValue({ id: '1' }) memberUtils.readMembers.mockResolvedValue([ { id: 'm1', firstName: 'Anna', lastName: 'Muster', email: 'anna@club.de' }, { id: 'm2', firstName: 'Play', lastName: 'Store', email: 'review@club.de' } ]) authUtils.readUsers.mockResolvedValue([ { id: 'u1', name: 'Ben Nutzer', email: 'ben@club.de', role: 'mitglied', active: true }, { id: 'u2', name: 'Playstore Review', email: 'review@club.de', roles: ['mitglied'], active: true, accountType: 'playstore_review' } ]) authUtils.getUserFromToken.mockResolvedValue({ id: '1', role: 'mitglied' }) const response = await membersGetHandler(event) expect(response.members.map(member => member.email || member.name)).not.toContain('review@club.de') expect(response.members.map(member => member.name)).not.toContain('Play Store') expect(response.members.map(member => member.name)).not.toContain('Playstore Review') expect(response.members).toHaveLength(2) }) }) describe('POST /api/members', () => { const baseBody = { firstName: 'Lisa', lastName: 'Beispiel', geburtsdatum: '2000-01-01', email: 'lisa@example.com', hasHallKey: true } it('verweigert Zugriff ohne Token', async () => { const event = createEvent() mockSuccessReadBody(baseBody) await expect(membersPostHandler(event)).rejects.toMatchObject({ statusCode: 401 }) }) it('verlangt Admin- oder Vorstand-Rolle', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) mockSuccessReadBody(baseBody) authUtils.getUserFromToken.mockResolvedValue({ id: '2', role: 'mitglied' }) await expect(membersPostHandler(event)).rejects.toMatchObject({ statusCode: 403 }) }) it('gibt 409 bei Duplikaten zurück', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) mockSuccessReadBody(baseBody) authUtils.getUserFromToken.mockResolvedValue({ id: '2', role: 'admin' }) memberUtils.saveMember.mockRejectedValue(new Error('existiert bereits')) await expect(membersPostHandler(event)).rejects.toMatchObject({ statusCode: 409 }) }) it('speichert Mitglied erfolgreich', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) mockSuccessReadBody(baseBody) authUtils.getUserFromToken.mockResolvedValue({ id: '2', role: 'admin' }) authUtils.readUsers.mockResolvedValue([]) memberUtils.readMembers.mockResolvedValue([]) memberUtils.saveMember.mockResolvedValue(true) const response = await membersPostHandler(event) expect(response.success).toBe(true) expect(memberUtils.saveMember).toHaveBeenCalledWith(expect.objectContaining({ hasHallKey: true })) }) it('speichert Geburtstags-Sichtbarkeit für manuelle Mitglieder', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) mockSuccessReadBody({ ...baseBody, showBirthday: false }) authUtils.getUserFromToken.mockResolvedValue({ id: '2', role: 'admin' }) authUtils.readUsers.mockResolvedValue([]) memberUtils.readMembers.mockResolvedValue([ { id: 'manual-1', firstName: 'Lisa', lastName: 'Beispiel', email: 'lisa@example.com', visibility: { showBirthday: true } } ]) memberUtils.saveMember.mockResolvedValue(true) const response = await membersPostHandler(event) expect(response.success).toBe(true) expect(memberUtils.saveMember).toHaveBeenCalledWith(expect.objectContaining({ visibility: expect.objectContaining({ showBirthday: false }) })) expect(authUtils.writeUsers).not.toHaveBeenCalled() }) it('kann Geburtstags-Sichtbarkeit auch am Login-Benutzer ausschalten', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) mockSuccessReadBody({ id: 'user-1', ...baseBody, email: 'lisa@example.com', visibility: { showBirthday: false } }) authUtils.getUserFromToken.mockResolvedValue({ id: '2', role: 'vorstand' }) memberUtils.readMembers.mockResolvedValue([]) authUtils.readUsers.mockResolvedValue([ { id: 'user-1', email: 'lisa@example.com', visibility: { showBirthday: true, showEmail: true } } ]) authUtils.writeUsers.mockResolvedValue(undefined) memberUtils.saveMember.mockResolvedValue(true) const response = await membersPostHandler(event) expect(response.success).toBe(true) expect(authUtils.writeUsers).toHaveBeenCalledWith([ expect.objectContaining({ id: 'user-1', visibility: expect.objectContaining({ showBirthday: false, showEmail: true }) }) ]) }) it('darf Geburtstags-Sichtbarkeit nicht für Login-Benutzer einschalten', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) mockSuccessReadBody({ id: 'user-1', ...baseBody, email: 'lisa@example.com', visibility: { showBirthday: true } }) authUtils.getUserFromToken.mockResolvedValue({ id: '2', role: 'vorstand' }) memberUtils.readMembers.mockResolvedValue([]) authUtils.readUsers.mockResolvedValue([ { id: 'user-1', email: 'lisa@example.com', visibility: { showBirthday: false, showEmail: true } } ]) memberUtils.saveMember.mockResolvedValue(true) const response = await membersPostHandler(event) expect(response.success).toBe(true) expect(memberUtils.saveMember).toHaveBeenCalledWith(expect.objectContaining({ visibility: expect.objectContaining({ showBirthday: false }) })) expect(authUtils.writeUsers).not.toHaveBeenCalled() }) it('erlaubt vorstand beim Speichern', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) mockSuccessReadBody(baseBody) authUtils.getUserFromToken.mockResolvedValue({ id: '3', role: 'vorstand' }) memberUtils.saveMember.mockResolvedValue(true) const response = await membersPostHandler(event) expect(response.success).toBe(true) }) it('erlaubt Updates von Altdaten ohne Geburtsdatum', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) mockSuccessReadBody({ id: 'legacy-1', firstName: 'Lisa', lastName: 'Beispiel', email: 'lisa@example.com' }) authUtils.getUserFromToken.mockResolvedValue({ id: '3', role: 'vorstand' }) authUtils.readUsers.mockResolvedValue([]) memberUtils.saveMember.mockResolvedValue(true) const response = await membersPostHandler(event) expect(response.success).toBe(true) expect(memberUtils.saveMember).toHaveBeenCalledWith(expect.objectContaining({ id: 'legacy-1', geburtsdatum: '' })) }) }) describe('DELETE /api/members', () => { it('validiert die ID', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) mockSuccessReadBody({}) authUtils.verifyToken.mockReturnValue({ id: '1' }) authUtils.getUserById.mockResolvedValue({ id: '1', role: 'admin' }) await expect(membersDeleteHandler(event)).rejects.toMatchObject({ statusCode: 400 }) }) it('löscht Mitglied erfolgreich', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) mockSuccessReadBody({ id: 'm1' }) authUtils.verifyToken.mockReturnValue({ id: '1' }) authUtils.getUserById.mockResolvedValue({ id: '1', role: 'vorstand' }) memberUtils.deleteMember.mockResolvedValue(true) const response = await membersDeleteHandler(event) expect(response.success).toBe(true) expect(memberUtils.deleteMember).toHaveBeenCalledWith('m1') }) }) describe('POST /api/members/bulk', () => { const importBody = { members: [ { firstName: 'Paul', lastName: 'Team', geburtsdatum: '2001-02-03' } ] } it('validiert members-Array', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) mockSuccessReadBody({ members: [] }) authUtils.verifyToken.mockReturnValue({ id: '1' }) authUtils.getUserById.mockResolvedValue({ id: '1', role: 'admin' }) await expect(membersBulkHandler(event)).rejects.toMatchObject({ statusCode: 400 }) }) it('importiert Mitglieder und schreibt Datei', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) mockSuccessReadBody(importBody) authUtils.verifyToken.mockReturnValue({ id: '1' }) authUtils.getUserById.mockResolvedValue({ id: '1', role: 'admin' }) memberUtils.readMembers.mockResolvedValue([]) memberUtils.writeMembers.mockResolvedValue(true) const response = await membersBulkHandler(event) expect(response.success).toBe(true) expect(response.summary.imported).toBe(1) expect(memberUtils.writeMembers).toHaveBeenCalled() }) }) describe('POST /api/members/toggle-mannschaftsspieler', () => { it('verlangt Authentifizierung', async () => { const event = createEvent() mockSuccessReadBody({ memberId: '1' }) await expect(toggleMannschaftsspielerHandler(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({ memberId: '2' }) await expect(toggleMannschaftsspielerHandler(event)).rejects.toMatchObject({ statusCode: 403 }) }) it('gibt 400 wenn memberId fehlt', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.verifyToken.mockReturnValue({ id: '1' }) authUtils.getUserById.mockResolvedValue({ id: '1', roles: ['admin'] }) authUtils.hasAnyRole.mockReturnValue(true) mockSuccessReadBody({}) await expect(toggleMannschaftsspielerHandler(event)).rejects.toMatchObject({ statusCode: 400 }) }) it('gibt 404 wenn Mitglied nicht gefunden', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.verifyToken.mockReturnValue({ id: '1' }) authUtils.getUserById.mockResolvedValue({ id: '1', roles: ['admin'] }) authUtils.hasAnyRole.mockReturnValue(true) authUtils.readUsers.mockResolvedValue([]) memberUtils.readMembers.mockResolvedValue([]) mockSuccessReadBody({ memberId: 'unbekannt' }) await expect(toggleMannschaftsspielerHandler(event)).rejects.toMatchObject({ statusCode: 404 }) }) it('toggled isMannschaftsspieler für eingeloggten User', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.verifyToken.mockReturnValue({ id: '1' }) authUtils.getUserById.mockResolvedValue({ id: '1', roles: ['admin'] }) authUtils.hasAnyRole.mockReturnValue(true) authUtils.readUsers.mockResolvedValue([ { id: 'user-5', name: 'Max Muster', isMannschaftsspieler: false, roles: ['mitglied'] } ]) authUtils.writeUsers.mockResolvedValue(undefined) memberUtils.readMembers.mockResolvedValue([]) mockSuccessReadBody({ memberId: 'user-5' }) const result = await toggleMannschaftsspielerHandler(event) expect(result.success).toBe(true) expect(result.isMannschaftsspieler).toBe(true) expect(authUtils.writeUsers).toHaveBeenCalled() }) it('toggled isMannschaftsspieler für manuelles Mitglied', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.verifyToken.mockReturnValue({ id: '1' }) authUtils.getUserById.mockResolvedValue({ id: '1', roles: ['admin'] }) authUtils.hasAnyRole.mockReturnValue(true) authUtils.readUsers.mockResolvedValue([]) memberUtils.readMembers.mockResolvedValue([ { id: 'manual-1', firstName: 'Paul', lastName: 'Team', isMannschaftsspieler: true } ]) memberUtils.writeMembers.mockResolvedValue(undefined) mockSuccessReadBody({ memberId: 'manual-1' }) const result = await toggleMannschaftsspielerHandler(event) expect(result.success).toBe(true) expect(result.isMannschaftsspieler).toBe(false) expect(memberUtils.writeMembers).toHaveBeenCalled() }) }) })