import { beforeEach, describe, expect, it, vi } from 'vitest' import fs from 'fs/promises' vi.mock('../server/utils/auth.js', () => ({ readUsers: vi.fn(), isHiddenUser: vi.fn(user => user?.hidden === true || user?.invisible === true || user?.isHidden === true || user?.systemAccount === true) })) vi.mock('../server/utils/members.js', () => ({ readMembers: vi.fn() })) vi.mock('../server/utils/termine.js', () => ({ readTermine: vi.fn().mockResolvedValue([]) })) vi.mock('../server/utils/news.js', () => ({ readNews: vi.fn().mockResolvedValue([]) })) vi.mock('../server/utils/spielplan-data.js', () => ({ getDefaultSpielplanSeason: vi.fn().mockResolvedValue('25--26'), readSpielplanData: vi.fn().mockResolvedValue({ data: [] }) })) vi.mock('../server/utils/push-notifications.js', () => ({ sendPushToUsers: vi.fn().mockResolvedValue({ sent: 1, failed: 0, removed: 0, recipients: 1, tokenCount: 1, skipped: false }) })) vi.mock('../server/utils/logger.js', () => ({ error: vi.fn(), info: vi.fn(), warn: vi.fn() })) const authUtils = await import('../server/utils/auth.js') const memberUtils = await import('../server/utils/members.js') const pushUtils = await import('../server/utils/push-notifications.js') const spielplanUtils = await import('../server/utils/spielplan-data.js') const { runNotificationSchedulerTick } = await import('../server/utils/notification-scheduler.js') const schedulerNow = new Date('2026-06-14T07:00:00.000Z') const recipient = { id: 'recipient', name: 'Push Empfaenger', active: true, notificationSettings: { birthdays: true, notificationTime: '09:00' } } describe('Notification Scheduler', () => { beforeEach(() => { vi.clearAllMocks() vi.spyOn(fs, 'readFile').mockImplementation(async (filePath) => { if (String(filePath).includes('mannschaften_25--26.csv')) { return [ 'Mannschaft,Liga,Staffelleiter,Telefon,Heimspieltag,Spielsystem,Mannschaftsführer,Spieler,Weitere Informationen Link,Letzte Aktualisierung', 'Erwachsene 1,,,,,,Mannschaftsfuehrer,Max Spieler,,', 'Erwachsene 2,,,,,,Andere Person,Andere Spieler,,' ].join('\n') } throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }) }) vi.spyOn(fs, 'mkdir').mockResolvedValue(undefined) vi.spyOn(fs, 'writeFile').mockResolvedValue(undefined) memberUtils.readMembers.mockResolvedValue([]) authUtils.readUsers.mockResolvedValue([recipient]) spielplanUtils.getDefaultSpielplanSeason.mockResolvedValue('25--26') spielplanUtils.readSpielplanData.mockResolvedValue({ data: [] }) }) it('sendet Geburtstags-Push nur fuer Mitglieder mit expliziter Geburtstagsfreigabe', async () => { memberUtils.readMembers.mockResolvedValue([ { firstName: 'Erlaubt', lastName: 'Person', active: true, geburtsdatum: '1990-06-14', visibility: { showBirthday: true } }, { firstName: 'Privat', lastName: 'Person', active: true, geburtsdatum: '1990-06-14', visibility: { showBirthday: false } }, { firstName: 'Unklar', lastName: 'Person', active: true, geburtsdatum: '1990-06-14' } ]) await runNotificationSchedulerTick(schedulerNow) expect(pushUtils.sendPushToUsers).toHaveBeenCalledTimes(1) expect(pushUtils.sendPushToUsers).toHaveBeenCalledWith(expect.objectContaining({ title: 'Geburtstage heute', body: 'Erlaubt Person hat heute Geburtstag.', data: { type: 'birthdays', date: '2026-06-14' } })) }) it('nennt bei mehreren Geburtstags-Pushes nur Namen und kein Alter', async () => { memberUtils.readMembers.mockResolvedValue([ { firstName: 'Anna', lastName: 'Beispiel', active: true, geburtsdatum: '1980-06-14', visibility: { showBirthday: true } }, { firstName: 'Bert', lastName: 'Beispiel', active: true, geburtsdatum: '2010-06-14', visibility: { showBirthday: true } } ]) await runNotificationSchedulerTick(schedulerNow) const payload = pushUtils.sendPushToUsers.mock.calls[0][0] expect(payload.body).toBe('Geburtstage heute: Anna Beispiel, Bert Beispiel.') expect(payload.body).not.toMatch(/\b\d+\b/) expect(payload.body).not.toContain('Jahre') }) it('sendet Punktspiel-Push nur einmal, wenn alle, eigene und ausgewaehlte Mannschaft dasselbe Spiel treffen', async () => { const matchUser = { id: 'match-user', name: 'Max Spieler', active: true, notificationSettings: { allTeamMatches: true, ownTeamMatches: true, selectedTeamSlugs: ['erwachsene-1'], selectedTeamSeason: '25--26', notificationTime: '09:00' } } authUtils.readUsers.mockResolvedValue([matchUser]) spielplanUtils.readSpielplanData.mockResolvedValue({ data: [{ Termin: '14.06.2026 20:15', BegegnungNr: 'spiel-1', Altersklasse: 'Erwachsene', HeimVereinName: 'Harheimer TC', HeimMannschaftAltersklasse: 'Erwachsene', HeimMannschaftNr: '1', HeimMannschaft: 'Harheimer TC', GastVereinName: 'Gastverein', GastMannschaftAltersklasse: 'Erwachsene', GastMannschaftNr: '1', GastMannschaft: 'Gastverein' }] }) await runNotificationSchedulerTick(schedulerNow) expect(pushUtils.sendPushToUsers).toHaveBeenCalledTimes(1) const payload = pushUtils.sendPushToUsers.mock.calls[0][0] expect(payload.title).toBe('Punktspiele') expect(payload.predicate(matchUser, matchUser.notificationSettings)).toBe(true) expect(payload.bodyForUser(matchUser, matchUser.notificationSettings)).toContain('Harheimer TC - Gastverein') }) it('fasst eigene und ausgewaehlte Punktspiele in einer Benachrichtigung zusammen', async () => { const matchUser = { id: 'match-user', name: 'Max Spieler', active: true, notificationSettings: { allTeamMatches: false, ownTeamMatches: true, selectedTeamSlugs: ['erwachsene-2'], selectedTeamSeason: '25--26', notificationTime: '09:00' } } authUtils.readUsers.mockResolvedValue([matchUser]) spielplanUtils.readSpielplanData.mockResolvedValue({ data: [ { Termin: '14.06.2026 20:15', BegegnungNr: 'spiel-1', Altersklasse: 'Erwachsene', HeimVereinName: 'Harheimer TC', HeimMannschaftAltersklasse: 'Erwachsene', HeimMannschaftNr: '1', HeimMannschaft: 'Harheimer TC', GastMannschaft: 'Gastverein' }, { Termin: '14.06.2026 20:30', BegegnungNr: 'spiel-2', Altersklasse: 'Erwachsene', HeimMannschaft: 'Gastverein II', GastVereinName: 'Harheimer TC', GastMannschaftAltersklasse: 'Erwachsene', GastMannschaftNr: '2', GastMannschaft: 'Harheimer TC II' } ] }) await runNotificationSchedulerTick(schedulerNow) expect(pushUtils.sendPushToUsers).toHaveBeenCalledTimes(1) const payload = pushUtils.sendPushToUsers.mock.calls[0][0] expect(payload.predicate(matchUser, matchUser.notificationSettings)).toBe(true) expect(payload.bodyForUser(matchUser, matchUser.notificationSettings)).toBe('2 Punktspiele am 14.06.2026') }) })