import { describe, it, expect, vi, beforeEach } from 'vitest' const { mockGetSession } = vi.hoisted(() => ({ mockGetSession: vi.fn(), })) vi.mock('@/lib/auth', () => ({ getSession: mockGetSession, })) vi.mock('@/lib/prisma', () => ({ prisma: { loginPairing: { findUnique: vi.fn(), update: vi.fn(), }, user: { findUnique: vi.fn(), }, }, })) import { prisma } from '@/lib/prisma' import { hashToken } from '@/lib/auth/pairing' import { getPairingForApproval, approvePairing, cancelPairing, } from '@/actions/pairing' const mockPrisma = prisma as unknown as { loginPairing: { findUnique: ReturnType update: ReturnType } user: { findUnique: ReturnType } } const VALID_PAIRING_ID = 'cmohmk0a' + 'g008bs417mzik8x9w'.padEnd(17, 'a').slice(0, 17) const VALID_SECRET = 'A'.repeat(43) // ≥40 chars voor Zod min(40) const SESSION_USER = { userId: 'user-1', isDemo: false } const SESSION_DEMO = { userId: 'demo-1', isDemo: true } beforeEach(() => { vi.clearAllMocks() mockPrisma.user.findUnique.mockResolvedValue({ username: 'lars' }) }) function pendingPairing(secret = VALID_SECRET) { return { status: 'pending' as const, expires_at: new Date(Date.now() + 60_000), secret_hash: hashToken(secret), desktop_ua: 'TestUA/1.0', desktop_ip: '198.51.100.1', } } describe('actions/pairing', () => { describe('getPairingForApproval', () => { it('ok-pad: pending + correct secret → desktop-info + username', async () => { mockGetSession.mockResolvedValue(SESSION_USER) mockPrisma.loginPairing.findUnique.mockResolvedValue(pendingPairing()) const res = await getPairingForApproval(VALID_PAIRING_ID, VALID_SECRET) expect(res).toEqual({ ok: true, desktop_ua: 'TestUA/1.0', desktop_ip: '198.51.100.1', username: 'lars', }) }) it('faalt zonder sessie', async () => { mockGetSession.mockResolvedValue({ userId: undefined, isDemo: false }) const res = await getPairingForApproval(VALID_PAIRING_ID, VALID_SECRET) expect(res).toEqual({ ok: false, error: 'Niet ingelogd' }) }) it('faalt op al-approved pairing', async () => { mockGetSession.mockResolvedValue(SESSION_USER) mockPrisma.loginPairing.findUnique.mockResolvedValue({ ...pendingPairing(), status: 'approved', }) const res = await getPairingForApproval(VALID_PAIRING_ID, VALID_SECRET) expect(res).toEqual({ ok: false, error: 'Pairing al afgehandeld' }) }) it('faalt op verlopen pairing', async () => { mockGetSession.mockResolvedValue(SESSION_USER) mockPrisma.loginPairing.findUnique.mockResolvedValue({ ...pendingPairing(), expires_at: new Date(Date.now() - 1000), }) const res = await getPairingForApproval(VALID_PAIRING_ID, VALID_SECRET) expect(res).toEqual({ ok: false, error: 'Pairing verlopen' }) }) it('faalt op verkeerd secret', async () => { mockGetSession.mockResolvedValue(SESSION_USER) mockPrisma.loginPairing.findUnique.mockResolvedValue(pendingPairing('echt')) const res = await getPairingForApproval(VALID_PAIRING_ID, VALID_SECRET) expect(res).toEqual({ ok: false, error: 'Ongeldig pairing-geheim' }) }) it('faalt op ongeldige cuid (Zod)', async () => { mockGetSession.mockResolvedValue(SESSION_USER) const res = await getPairingForApproval('niet-cuid', VALID_SECRET) expect(res).toEqual({ ok: false, error: 'Ongeldige invoer' }) expect(mockPrisma.loginPairing.findUnique).not.toHaveBeenCalled() }) }) describe('approvePairing', () => { it('happy-pad: status pending→approved, user_id gezet, expires_at +5min', async () => { mockGetSession.mockResolvedValue(SESSION_USER) mockPrisma.loginPairing.findUnique.mockResolvedValue(pendingPairing()) mockPrisma.loginPairing.update.mockResolvedValue({}) const res = await approvePairing(VALID_PAIRING_ID, VALID_SECRET) expect(res).toEqual({ ok: true }) expect(mockPrisma.loginPairing.update).toHaveBeenCalledTimes(1) const arg = mockPrisma.loginPairing.update.mock.calls[0][0] expect(arg.where).toEqual({ id: VALID_PAIRING_ID }) expect(arg.data.status).toBe('approved') expect(arg.data.user_id).toBe('user-1') const dt = new Date(arg.data.expires_at).getTime() - Date.now() expect(dt).toBeGreaterThan(295_000) expect(dt).toBeLessThan(305_000) }) it('demo-user wordt geblokkeerd, geen DB-write', async () => { mockGetSession.mockResolvedValue(SESSION_DEMO) const res = await approvePairing(VALID_PAIRING_ID, VALID_SECRET) expect(res).toEqual({ ok: false, error: 'Niet beschikbaar in demo-modus' }) expect(mockPrisma.loginPairing.findUnique).not.toHaveBeenCalled() expect(mockPrisma.loginPairing.update).not.toHaveBeenCalled() }) it('faalt op verkeerd secret zonder DB-write', async () => { mockGetSession.mockResolvedValue(SESSION_USER) mockPrisma.loginPairing.findUnique.mockResolvedValue(pendingPairing('echt')) const res = await approvePairing(VALID_PAIRING_ID, VALID_SECRET) expect(res).toEqual({ ok: false, error: 'Ongeldig pairing-geheim' }) expect(mockPrisma.loginPairing.update).not.toHaveBeenCalled() }) }) describe('cancelPairing', () => { it('happy-pad: status pending→cancelled', async () => { mockGetSession.mockResolvedValue(SESSION_USER) mockPrisma.loginPairing.findUnique.mockResolvedValue(pendingPairing()) mockPrisma.loginPairing.update.mockResolvedValue({}) const res = await cancelPairing(VALID_PAIRING_ID, VALID_SECRET) expect(res).toEqual({ ok: true }) const arg = mockPrisma.loginPairing.update.mock.calls[0][0] expect(arg.data.status).toBe('cancelled') }) it('demo-user wordt geblokkeerd, geen DB-write', async () => { mockGetSession.mockResolvedValue(SESSION_DEMO) const res = await cancelPairing(VALID_PAIRING_ID, VALID_SECRET) expect(res).toEqual({ ok: false, error: 'Niet beschikbaar in demo-modus' }) expect(mockPrisma.loginPairing.findUnique).not.toHaveBeenCalled() expect(mockPrisma.loginPairing.update).not.toHaveBeenCalled() }) }) })