import { describe, it, expect, vi, beforeEach } from 'vitest' vi.mock('../../../server/models/member.js', () => { const mockSave = vi.fn().mockResolvedValue(undefined) function MockMember(data) { Object.assign(this, data) this._id = 'new-member-123' this.status = data.status || 'pending_payment' this.save = mockSave } MockMember.findOne = vi.fn() MockMember.findByIdAndUpdate = vi.fn() MockMember._mockSave = mockSave return { default: MockMember } }) vi.mock('../../../server/utils/mongoose.js', () => ({ connectDB: vi.fn() })) vi.mock('../../../server/utils/validateBody.js', () => ({ validateBody: vi.fn() })) vi.mock('../../../server/utils/schemas.js', () => ({ memberCreateSchema: {} })) vi.mock('../../../server/utils/slack.ts', () => ({ getSlackService: vi.fn().mockReturnValue(null) })) vi.mock('../../../server/utils/resend.js', () => ({ sendWelcomeEmail: vi.fn().mockResolvedValue(undefined) })) vi.mock('../../../server/utils/memberNumber.js', () => ({ assignMemberNumber: vi.fn().mockResolvedValue(1) })) import Member from '../../../server/models/member.js' import { validateBody } from '../../../server/utils/validateBody.js' import { sendWelcomeEmail } from '../../../server/utils/resend.js' import createHandler from '../../../server/api/members/create.post.js' import { createMockEvent } from '../helpers/createMockEvent.js' describe('members/create.post handler', () => { beforeEach(() => { vi.clearAllMocks() Member.findOne.mockResolvedValue(null) Member._mockSave.mockResolvedValue(undefined) validateBody.mockResolvedValue({ email: 'new@example.com', name: 'New Member', circle: 'community', contributionAmount: 0 }) }) it('validates request body via memberCreateSchema', async () => { const event = createMockEvent({ method: 'POST', path: '/api/members/create' }) await createHandler(event) expect(validateBody).toHaveBeenCalledOnce() expect(validateBody).toHaveBeenCalledWith(event, expect.any(Object)) }) it('rejects duplicate email with 409', async () => { Member.findOne.mockResolvedValue({ _id: 'existing-123', email: 'new@example.com' }) const event = createMockEvent({ method: 'POST', path: '/api/members/create' }) await expect(createHandler(event)).rejects.toMatchObject({ statusCode: 409, statusMessage: 'A member with this email already exists' }) }) it('does not expose helcimCustomerId or role in response', async () => { validateBody.mockResolvedValue({ email: 'new@example.com', name: 'New Member', circle: 'community', contributionAmount: 0, helcimCustomerId: 'cust-999', role: 'admin' }) const event = createMockEvent({ method: 'POST', path: '/api/members/create' }) const result = await createHandler(event) expect(result.member).not.toHaveProperty('helcimCustomerId') expect(result.member).not.toHaveProperty('role') expect(result.success).toBe(true) expect(result.member).toEqual({ id: 'new-member-123', email: 'new@example.com', name: 'New Member', circle: 'community', contributionAmount: 0, status: 'pending_payment' }) }) it('succeeds when Slack service is unavailable', async () => { const event = createMockEvent({ method: 'POST', path: '/api/members/create' }) const result = await createHandler(event) expect(result.success).toBe(true) expect(result.member.email).toBe('new@example.com') }) it('succeeds when welcome email fails', async () => { sendWelcomeEmail.mockRejectedValue(new Error('email service down')) const event = createMockEvent({ method: 'POST', path: '/api/members/create' }) const result = await createHandler(event) expect(result.success).toBe(true) expect(result.member.email).toBe('new@example.com') expect(sendWelcomeEmail).toHaveBeenCalledOnce() }) })