feat: add testing infrastructure — Vitest, Playwright, CI, git hooks
Some checks are pending
Test / vitest (push) Waiting to run
Test / playwright (push) Blocked by required conditions
Test / visual (push) Blocked by required conditions

Add comprehensive testing covering 420 unit/handler tests across 24 Vitest
files, 9 Playwright E2E specs, accessibility scans, and visual regression.
Includes GitHub Actions CI, Husky pre-push hook, and TESTING.md docs.
This commit is contained in:
Jennie Robinson Faber 2026-04-04 16:07:21 +01:00
parent 036af95e00
commit 1e30ba23cd
35 changed files with 3637 additions and 5 deletions

View file

@ -0,0 +1,109 @@
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)
}))
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',
contributionTier: '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',
contributionTier: '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',
contributionTier: '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()
})
})