ghostguild-org/tests/server/api/events/members-only-visibility.test.js
2026-05-19 13:26:05 +01:00

172 lines
5.3 KiB
JavaScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
import { createMockEvent } from '../../helpers/createMockEvent.js'
const {
mockFind,
mockSort,
mockSelect,
mockLean,
mockGetOptionalMember,
mockLoadPublicEvent
} = vi.hoisted(() => ({
mockFind: vi.fn(),
mockSort: vi.fn(),
mockSelect: 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)
mockSelect.mockReturnValue({ lean: mockLean })
mockSort.mockReturnValue({ select: mockSelect })
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()
})
})