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.
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*\([^)]+\)/)?.[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')
|
|
})
|
|
})
|
|
})
|