Update project config and documentation, add admin invite script,
implement membersOnly event visibility
This commit is contained in:
parent
96470a604a
commit
9e18560ebf
9 changed files with 387 additions and 50 deletions
172
tests/server/api/events/members-only-visibility.test.js
Normal file
172
tests/server/api/events/members-only-visibility.test.js
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
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()
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue