ghostguild-org/tests/server/api/dev-endpoints.test.js
Jennie Robinson Faber c40f2c7c63 fix: accessibility improvements and test infrastructure hardening
Add aria-labels to form controls (selects, checkboxes, switches), set
html lang attribute and page title, fix color contrast for --candle-dim
and --text-faint tokens, underline inline links, remove opacity hack.
Harden dev login endpoints with atomic findOneAndUpdate and tokenVersion
in JWT. Update Playwright timeouts and E2E test helpers.
2026-04-05 21:59:02 +01:00

172 lines
5.7 KiB
JavaScript

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
vi.mock('../../../server/models/member.js', () => ({
default: { findOne: vi.fn(), create: vi.fn(), findOneAndUpdate: vi.fn() }
}))
vi.mock('../../../server/utils/mongoose.js', () => ({
connectDB: vi.fn()
}))
vi.mock('jsonwebtoken', () => ({
default: { sign: vi.fn().mockReturnValue('mock-token') }
}))
import Member from '../../../server/models/member.js'
import testLoginHandler from '../../../server/api/dev/test-login.get.js'
import memberLoginHandler from '../../../server/api/dev/member-login.get.js'
import { createMockEvent } from '../helpers/createMockEvent.js'
const mockMember = {
_id: 'member-123',
email: 'test-admin@ghostguild.dev',
name: 'Test Admin',
circle: 'founder',
role: 'admin',
status: 'active'
}
describe('dev endpoints', () => {
let originalNodeEnv
beforeEach(() => {
originalNodeEnv = process.env.NODE_ENV
process.env.NODE_ENV = 'development'
vi.clearAllMocks()
})
afterEach(() => {
process.env.NODE_ENV = originalNodeEnv
})
// ── Source inspection ──────────────────────────────────────────
describe('source inspection', () => {
const devDir = resolve(import.meta.dirname, '../../../server/api/dev')
it('test-login.get.js first conditional is NODE_ENV production check', () => {
const source = readFileSync(resolve(devDir, 'test-login.get.js'), 'utf-8')
const handlerBody = source.slice(source.indexOf('defineEventHandler'))
const firstIf = handlerBody.match(/if\s*\([^)]+\)/)?.[0]
expect(firstIf).toContain("process.env.NODE_ENV === 'production'")
})
it('member-login.get.js first conditional is NODE_ENV production check', () => {
const source = readFileSync(resolve(devDir, 'member-login.get.js'), 'utf-8')
const handlerBody = source.slice(source.indexOf('defineEventHandler'))
const firstIf = handlerBody.match(/if\s*\([^)]+\)/)?.[0]
expect(firstIf).toContain("process.env.NODE_ENV === 'production'")
})
})
// ── test-login.get.js ──────────────────────────────────────────
describe('test-login.get.js', () => {
it('returns 404 in production', async () => {
process.env.NODE_ENV = 'production'
const event = createMockEvent({ method: 'GET', path: '/api/dev/test-login' })
await expect(testLoginHandler(event)).rejects.toMatchObject({
statusCode: 404
})
})
it('creates admin user when none exists', async () => {
Member.findOneAndUpdate.mockResolvedValue(mockMember)
const event = createMockEvent({ method: 'GET', path: '/api/dev/test-login' })
await testLoginHandler(event)
expect(Member.findOneAndUpdate).toHaveBeenCalledWith(
{ email: 'test-admin@ghostguild.dev' },
expect.objectContaining({
$setOnInsert: expect.objectContaining({
role: 'admin',
circle: 'founder'
})
}),
{ upsert: true, new: true }
)
})
it('uses existing admin when found', async () => {
Member.findOneAndUpdate.mockResolvedValue(mockMember)
const event = createMockEvent({ method: 'GET', path: '/api/dev/test-login' })
await testLoginHandler(event)
expect(Member.findOneAndUpdate).toHaveBeenCalledWith(
{ email: 'test-admin@ghostguild.dev' },
expect.any(Object),
{ upsert: true, new: true }
)
})
it('sets auth cookie', async () => {
Member.findOneAndUpdate.mockResolvedValue(mockMember)
const event = createMockEvent({ method: 'GET', path: '/api/dev/test-login' })
await testLoginHandler(event)
const cookieHeader = event._testSetHeaders['set-cookie']
expect(cookieHeader).toBeDefined()
expect(cookieHeader).toContain('auth-token=mock-token')
})
})
// ── member-login.get.js ────────────────────────────────────────
describe('member-login.get.js', () => {
it('returns 404 in production', async () => {
process.env.NODE_ENV = 'production'
const event = createMockEvent({ method: 'GET', path: '/api/dev/member-login' })
await expect(memberLoginHandler(event)).rejects.toMatchObject({
statusCode: 404
})
})
it('returns 400 when email query param is missing', async () => {
const event = createMockEvent({ method: 'GET', path: '/api/dev/member-login' })
await expect(memberLoginHandler(event)).rejects.toMatchObject({
statusCode: 400
})
})
it('returns 404 when member not found', async () => {
Member.findOne.mockResolvedValue(null)
const event = createMockEvent({
method: 'GET',
path: '/api/dev/member-login?email=nobody@example.com'
})
await expect(memberLoginHandler(event)).rejects.toMatchObject({
statusCode: 404
})
})
it('sets auth cookie for found member', async () => {
const foundMember = {
_id: 'member-456',
email: 'test@example.com',
name: 'Test User',
status: 'active'
}
Member.findOne.mockResolvedValue(foundMember)
const event = createMockEvent({
method: 'GET',
path: '/api/dev/member-login?email=test@example.com'
})
await memberLoginHandler(event)
const cookieHeader = event._testSetHeaders['set-cookie']
expect(cookieHeader).toBeDefined()
expect(cookieHeader).toContain('auth-token=mock-token')
})
})
})