import { beforeEach, describe, expect, it, mock, vi } from 'bun:test'; import { Hono } from 'hono'; const db = { group: { findUnique: mock(), }, attendee: { findFirst: mock(), create: mock(), count: mock(), }, groupWaitlistEntry: { findFirst: mock(), create: mock(), }, }; mock.module('@/lib/prisma', () => ({ default: db, getPrismaClient: mock(), UnitOfWork: class {}, })); import prisma from '@/lib/prisma'; import { invitationsRoutes } from '@/modules/invitations'; const app = new Hono(); app.route('/invitations', invitationsRoutes); const mockGroup = { id: 'group-1', name: 'Taller de Cerámica', description: 'Clases de cerámica los sábados', days: ['SATURDAY'], time: '10:00', capacity: 15, price: 500, billingType: 'MONTHLY', createdById: 'teacher-1', owner: { name: 'Prof. Mario Rossi' }, }; describe('public invitations routes', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('GET /invitations/:token', () => { it('returns group details for a valid invite token', async () => { prisma.group.findUnique.mockResolvedValue(mockGroup as never); const res = await app.request('/invitations/valid-token-123'); expect(res.status).toBe(200); const data = await res.json(); expect(data).toEqual({ id: 'group-1', name: 'Taller de Cerámica', description: 'Clases de cerámica los sábados', teacherName: 'Prof. Mario Rossi', days: ['SATURDAY'], time: '10:00', capacity: 15, price: 500, billingType: 'MONTHLY', }); expect(prisma.group.findUnique).toHaveBeenCalledWith({ where: { inviteToken: 'valid-token-123' }, include: { owner: { select: { name: true } } }, }); }); it('returns 404 when the invite token is invalid', async () => { prisma.group.findUnique.mockResolvedValue(null); const res = await app.request('/invitations/invalid-token'); expect(res.status).toBe(404); const body = await res.json(); expect(body.title).toBe('Not Found'); }); }); describe('POST /invitations/:token/join', () => { it('registers a new attendee successfully', async () => { prisma.group.findUnique.mockResolvedValue(mockGroup as never); prisma.attendee.findFirst.mockResolvedValue(null); prisma.attendee.create.mockResolvedValue({ id: 'att-1', groupId: 'group-1', fullName: 'Lucía Méndez', email: 'lucia@test.com', phone: '+5491122334455', guardianName: null, guardianPhone: null, notes: null, createdAt: new Date('2026-09-18T10:00:00.000Z'), updatedAt: new Date('2026-09-18T10:00:00.000Z'), } as never); const res = await app.request('/invitations/valid-token-123/join', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ firstName: 'Lucía', lastName: 'Méndez', phone: '+54 9 11 2233-4455', email: 'lucia@test.com', }), }); expect(res.status).toBe(201); const body = await res.json(); expect(body.message).toBe('Inscripción realizada con éxito'); expect(body.attendee.fullName).toBe('Lucía Méndez'); expect(prisma.attendee.create).toHaveBeenCalledWith({ data: { groupId: 'group-1', fullName: 'Lucía Méndez', phone: '+5491122334455', email: 'lucia@test.com', }, }); }); it('rejects registration when phone already exists in the group', async () => { prisma.group.findUnique.mockResolvedValue(mockGroup as never); prisma.attendee.findFirst.mockResolvedValue({ id: 'existing-att' } as never); const res = await app.request('/invitations/valid-token-123/join', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ firstName: 'Lucía', lastName: 'Méndez', phone: '1122334455', }), }); expect(res.status).toBe(409); const body = await res.json(); expect(body.code).toBe('attendee_already_registered'); expect(prisma.attendee.create).not.toHaveBeenCalled(); }); it('rejects invalid inputs (missing required name/phone)', async () => { const res = await app.request('/invitations/valid-token-123/join', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ firstName: '', phone: '12', }), }); expect(res.status).toBe(400); }); it('adds to the waitlist when the group is full', async () => { prisma.group.findUnique.mockResolvedValue(mockGroup as never); prisma.attendee.findFirst.mockResolvedValue(null); prisma.attendee.count.mockResolvedValue(15); // capacity full prisma.groupWaitlistEntry.findFirst.mockResolvedValue(null); prisma.groupWaitlistEntry.create.mockResolvedValue({ id: 'wl-1', groupId: 'group-1', fullName: 'Lucía Méndez', phone: '+5491122334455', email: 'lucia@test.com', notes: null, status: 'PENDING', createdAt: new Date('2026-09-18T10:00:00.000Z'), updatedAt: new Date('2026-09-18T10:00:00.000Z'), } as never); const res = await app.request('/invitations/valid-token-123/join', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ firstName: 'Lucía', lastName: 'Méndez', phone: '+54 9 11 2233-4455', email: 'lucia@test.com', }), }); expect(res.status).toBe(201); const body = await res.json(); expect(body.status).toBe('waitlisted'); expect(body.attendee).toBeNull(); expect(prisma.attendee.create).not.toHaveBeenCalled(); expect(prisma.groupWaitlistEntry.create).toHaveBeenCalledWith({ data: { groupId: 'group-1', fullName: 'Lucía Méndez', phone: '+5491122334455', email: 'lucia@test.com', }, }); }); }); });