import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import Member from '../../../server/models/member.js' import { requireAuth } from '../../../server/utils/auth.js' import { requiresPayment, getHelcimPlanId, getContributionTierByValue } from '../../../server/config/contributions.js' import subscriptionHandler from '../../../server/api/helcim/subscription.post.js' import { createMockEvent } from '../helpers/createMockEvent.js' vi.mock('../../../server/models/member.js', () => ({ default: { findOneAndUpdate: vi.fn(), findOne: vi.fn() } })) vi.mock('../../../server/utils/mongoose.js', () => ({ connectDB: vi.fn() })) vi.mock('../../../server/utils/auth.js', () => ({ requireAuth: vi.fn() })) vi.mock('../../../server/utils/slack.ts', () => ({ getSlackService: vi.fn().mockReturnValue(null) })) vi.mock('../../../server/config/contributions.js', () => ({ requiresPayment: vi.fn(), getHelcimPlanId: vi.fn(), getContributionTierByValue: vi.fn() })) vi.mock('../../../server/utils/resend.js', () => ({ sendWelcomeEmail: vi.fn().mockResolvedValue({ success: true }) })) // helcimSubscriptionSchema is a Nitro auto-import used by validateBody vi.stubGlobal('helcimSubscriptionSchema', {}) describe('helcim subscription endpoint', () => { const savedFetch = globalThis.fetch beforeEach(() => { vi.clearAllMocks() }) afterEach(() => { // Restore fetch in case a test stubbed it globalThis.fetch = savedFetch }) it('requires auth', async () => { requireAuth.mockRejectedValue( createError({ statusCode: 401, statusMessage: 'Unauthorized' }) ) const event = createMockEvent({ method: 'POST', path: '/api/helcim/subscription', body: { customerId: 'cust-1', contributionTier: '0', customerCode: 'code-1' } }) await expect(subscriptionHandler(event)).rejects.toMatchObject({ statusCode: 401, statusMessage: 'Unauthorized' }) expect(requireAuth).toHaveBeenCalledWith(event) }) it('free tier skips Helcim and activates member', async () => { requireAuth.mockResolvedValue(undefined) requiresPayment.mockReturnValue(false) const mockMember = { _id: 'member-1', email: 'test@example.com', name: 'Test', circle: 'community', contributionTier: '0', status: 'active', save: vi.fn() } Member.findOneAndUpdate.mockResolvedValue(mockMember) const event = createMockEvent({ method: 'POST', path: '/api/helcim/subscription', body: { customerId: 'cust-1', contributionTier: '0', customerCode: 'code-1' } }) const result = await subscriptionHandler(event) expect(result.success).toBe(true) expect(result.subscription).toBeNull() expect(result.member).toEqual({ id: 'member-1', email: 'test@example.com', name: 'Test', circle: 'community', contributionTier: '0', status: 'active' }) expect(Member.findOneAndUpdate).toHaveBeenCalledWith( { helcimCustomerId: 'cust-1' }, expect.objectContaining({ status: 'active', contributionTier: '0' }), { new: true } ) }) it('paid tier without cardToken returns 400', async () => { requireAuth.mockResolvedValue(undefined) requiresPayment.mockReturnValue(true) getHelcimPlanId.mockReturnValue('plan-123') const event = createMockEvent({ method: 'POST', path: '/api/helcim/subscription', body: { customerId: 'cust-1', contributionTier: '15', customerCode: 'code-1' } }) await expect(subscriptionHandler(event)).rejects.toMatchObject({ statusCode: 400, statusMessage: 'Payment information is required for this contribution tier' }) }) it('Helcim API failure still activates member', async () => { requireAuth.mockResolvedValue(undefined) requiresPayment.mockReturnValue(true) getHelcimPlanId.mockReturnValue('plan-123') getContributionTierByValue.mockReturnValue({ amount: '15' }) const mockMember = { _id: 'member-2', email: 'paid@example.com', name: 'Paid User', circle: 'founder', contributionTier: '15', status: 'active', save: vi.fn() } Member.findOneAndUpdate.mockResolvedValue(mockMember) vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('Network error'))) const event = createMockEvent({ method: 'POST', path: '/api/helcim/subscription', body: { customerId: 'cust-1', contributionTier: '15', customerCode: 'code-1', cardToken: 'tok-123' } }) const result = await subscriptionHandler(event) expect(result.success).toBe(true) expect(result.warning).toBeTruthy() expect(result.member.status).toBe('active') expect(Member.findOneAndUpdate).toHaveBeenCalledWith( { helcimCustomerId: 'cust-1' }, expect.objectContaining({ status: 'active', contributionTier: '15' }), { new: true } ) vi.unstubAllGlobals() // Re-stub the schema global after unstubAllGlobals vi.stubGlobal('helcimSubscriptionSchema', {}) }) })