import { afterEach, describe, expect, it, vi } from 'vitest' import { decideSignIn, SIGN_IN_ERRORS } from '@/lib/auth/signInPolicy' import type { GuildMember } from '@/lib/auth/discord' const mitglied: GuildMember = { userId: '42', roleIds: ['r-afis'], nickname: null } describe('Wer sich anmelden darf', () => { it('lehnt ab, wer nicht auf dem Discord-Server ist', () => { const entscheidung = decideSignIn({ member: null, isBootstrapAdmin: false, grantingRoleCount: 0, }) expect(entscheidung).toEqual({ ok: false, reason: SIGN_IN_ERRORS.notInGuild }) }) it('lehnt auch einen Notfall-Admin ab, der die Guild verlassen hat', () => { // Sonst bliebe die Hintertür nach einem Rauswurf offen. const entscheidung = decideSignIn({ member: null, isBootstrapAdmin: true, grantingRoleCount: 0, }) expect(entscheidung).toEqual({ ok: false, reason: SIGN_IN_ERRORS.notInGuild }) }) it('lehnt einen Gast ohne AfiS-Rolle ab', () => { const entscheidung = decideSignIn({ member: { ...mitglied, roleIds: ['r-gast'] }, isBootstrapAdmin: false, grantingRoleCount: 0, }) expect(entscheidung).toEqual({ ok: false, reason: SIGN_IN_ERRORS.notAfis }) }) it('laesst ein Mitglied mit AfiS-Rolle durch', () => { expect(decideSignIn({ member: mitglied, isBootstrapAdmin: false, grantingRoleCount: 1 })).toEqual( { ok: true }, ) }) it('laesst den Notfall-Admin ohne AfiS-Rolle durch', () => { expect(decideSignIn({ member: mitglied, isBootstrapAdmin: true, grantingRoleCount: 0 })).toEqual( { ok: true }, ) }) }) describe('Mitgliedschaftsabfrage beim Bot', () => { afterEach(() => { vi.unstubAllGlobals() vi.unstubAllEnvs() }) function stubEnv() { vi.stubEnv('DATABASE_URL', 'postgresql://x/y') vi.stubEnv('APP_URL', 'http://localhost:3000') vi.stubEnv('AUTH_SECRET', 'x'.repeat(40)) vi.stubEnv('DISCORD_CLIENT_ID', 'c') vi.stubEnv('DISCORD_CLIENT_SECRET', 's') vi.stubEnv('DISCORD_BOT_TOKEN', 'bot-token') vi.stubEnv('DISCORD_GUILD_ID', '99') } it('meldet null, wenn Discord 404 antwortet', async () => { stubEnv() vi.stubGlobal('fetch', vi.fn(async () => new Response(null, { status: 404 }))) const { resetEnvCache } = await import('@/lib/env') resetEnvCache() const { fetchGuildMember } = await import('@/lib/auth/discord') expect(await fetchGuildMember('42')).toBeNull() }) it('wirft bei einem Ausfall der Discord-API, statt Mitgliedschaft zu verneinen', async () => { // Ein Ausfall darf nicht aussehen wie "kein Mitglied": sonst sperrt eine // Störung bei Discord die halbe Abteilung aus und legt nebenbei Konten an. stubEnv() vi.stubGlobal('fetch', vi.fn(async () => new Response(null, { status: 500 }))) const { resetEnvCache } = await import('@/lib/env') resetEnvCache() const { fetchGuildMember } = await import('@/lib/auth/discord') await expect(fetchGuildMember('42')).rejects.toThrow(/500/) }) it('benutzt das Bot-Token, nicht das des Nutzers', async () => { stubEnv() const spy = vi.fn(async () => Response.json({ roles: ['r-afis'], nick: 'Vogel' })) vi.stubGlobal('fetch', spy) const { resetEnvCache } = await import('@/lib/env') resetEnvCache() const { fetchGuildMember } = await import('@/lib/auth/discord') const member = await fetchGuildMember('42') expect(member).toEqual({ userId: '42', roleIds: ['r-afis'], nickname: 'Vogel' }) const call = spy.mock.calls[0] as unknown as [string, RequestInit] const [url, init] = call expect(url).toContain('/guilds/99/members/42') expect((init.headers as Record).authorization).toBe('Bot bot-token') }) })