import { describe, it, expect, vi, beforeEach } from 'vitest' import { createMockEvent } from '../../helpers/createMockEvent.js' const { mockFind, mockSort, mockLean, mockGetOptionalMember, mockLoadPublicEvent } = vi.hoisted(() => ({ mockFind: vi.fn(), mockSort: vi.fn(), mockLean: vi.fn(), mockGetOptionalMember: vi.fn(), mockLoadPublicEvent: vi.fn() })) vi.mock('../../../../server/models/event.js', () => ({ default: { find: mockFind } })) vi.mock('../../../../server/utils/mongoose.js', () => ({ connectDB: vi.fn() })) vi.mock('../../../../server/utils/auth.js', () => ({ getOptionalMember: mockGetOptionalMember })) vi.mock('../../../../server/utils/loadEvent.js', () => ({ loadPublicEvent: mockLoadPublicEvent })) function setupFindChain(result = []) { mockLean.mockResolvedValue(result) mockSort.mockReturnValue({ lean: mockLean }) mockFind.mockReturnValue({ sort: mockSort }) } function mkReq({ method = 'GET', path = '/', id } = {}) { const req = createMockEvent({ method, path }) if (id) req.context = { params: { id } } return req } describe('GET /api/events — membersOnly visibility', () => { let listHandler beforeEach(async () => { vi.clearAllMocks() vi.resetModules() setupFindChain([]) listHandler = (await import('../../../../server/api/events/index.get.js')).default }) it('hides membersOnly events from anonymous callers', async () => { mockGetOptionalMember.mockResolvedValue(null) await listHandler(mkReq({ path: '/api/events' })) const filter = mockFind.mock.calls[0][0] expect(filter.membersOnly).toEqual({ $ne: true }) }) it('hides membersOnly events from guest/cancelled/suspended members', async () => { mockGetOptionalMember.mockResolvedValue({ _id: 'm1', role: 'member', status: 'guest' }) await listHandler(mkReq({ path: '/api/events' })) const filter = mockFind.mock.calls[0][0] expect(filter.membersOnly).toEqual({ $ne: true }) }) it('shows membersOnly events to active members', async () => { mockGetOptionalMember.mockResolvedValue({ _id: 'm1', role: 'member', status: 'active' }) await listHandler(mkReq({ path: '/api/events' })) const filter = mockFind.mock.calls[0][0] expect(filter.membersOnly).toBeUndefined() }) it('shows membersOnly events to pending_payment members', async () => { mockGetOptionalMember.mockResolvedValue({ _id: 'm1', role: 'member', status: 'pending_payment' }) await listHandler(mkReq({ path: '/api/events' })) const filter = mockFind.mock.calls[0][0] expect(filter.membersOnly).toBeUndefined() }) it('shows membersOnly events to admins regardless of status', async () => { mockGetOptionalMember.mockResolvedValue({ _id: 'm1', role: 'admin', status: 'cancelled' }) await listHandler(mkReq({ path: '/api/events' })) const filter = mockFind.mock.calls[0][0] expect(filter.membersOnly).toBeUndefined() }) }) describe('GET /api/events/[id] — membersOnly visibility', () => { let detailHandler beforeEach(async () => { vi.clearAllMocks() vi.resetModules() detailHandler = (await import('../../../../server/api/events/[id].get.js')).default }) const baseEvent = (overrides = {}) => ({ _id: 'e1', slug: 'members-only-event', title: 'Members Only', isVisible: true, membersOnly: true, registrations: [], ...overrides }) it('404s a membersOnly event for anonymous callers', async () => { mockLoadPublicEvent.mockResolvedValue(baseEvent()) mockGetOptionalMember.mockResolvedValue(null) await expect( detailHandler(mkReq({ path: '/api/events/members-only-event', id: 'members-only-event' })) ).rejects.toMatchObject({ statusCode: 404 }) }) it('404s a membersOnly event for guest members', async () => { mockLoadPublicEvent.mockResolvedValue(baseEvent()) mockGetOptionalMember.mockResolvedValue({ _id: 'm1', role: 'member', status: 'guest' }) await expect( detailHandler(mkReq({ path: '/api/events/members-only-event', id: 'members-only-event' })) ).rejects.toMatchObject({ statusCode: 404 }) }) it('returns the event to active members', async () => { mockLoadPublicEvent.mockResolvedValue(baseEvent()) mockGetOptionalMember.mockResolvedValue({ _id: 'm1', role: 'member', status: 'active' }) const result = await detailHandler( mkReq({ path: '/api/events/members-only-event', id: 'members-only-event' }) ) expect(result.slug).toBe('members-only-event') }) it('returns the event to admins', async () => { mockLoadPublicEvent.mockResolvedValue(baseEvent()) mockGetOptionalMember.mockResolvedValue({ _id: 'm1', role: 'admin', status: 'active' }) const result = await detailHandler( mkReq({ path: '/api/events/members-only-event', id: 'members-only-event' }) ) expect(result.slug).toBe('members-only-event') }) it('does not gate non-membersOnly events (auth check skipped)', async () => { mockLoadPublicEvent.mockResolvedValue(baseEvent({ membersOnly: false })) const result = await detailHandler( mkReq({ path: '/api/events/members-only-event', id: 'members-only-event' }) ) expect(result.slug).toBe('members-only-event') expect(mockGetOptionalMember).not.toHaveBeenCalled() }) })