feat: add testing infrastructure — Vitest, Playwright, CI, git hooks
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:
parent
036af95e00
commit
1e30ba23cd
35 changed files with 3637 additions and 5 deletions
109
tests/server/api/members-create.test.js
Normal file
109
tests/server/api/members-create.test.js
Normal 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()
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue