Accessibility fixes (aria-labels, color contrast, html lang, inline link underlines), atomic dev login endpoints, and E2E test hardening.
172 lines
5.7 KiB
JavaScript
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*\([\s\S]*?\)\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*\([\s\S]*?\)\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')
|
|
})
|
|
})
|
|
})
|