more functional design principles for tests
Some checks failed
ci/woodpecker/pr/pr Pipeline failed

This commit is contained in:
Madison Grubb
2026-02-17 11:21:02 -05:00
parent c8d37c98f4
commit 82bd51c3a4
3 changed files with 42 additions and 34 deletions

View File

@@ -59,7 +59,9 @@ async function waitForHealth(timeoutMs = 90000) {
} }
describe('Server and CoT integration', () => { describe('Server and CoT integration', () => {
let serverProcess = null const testState = {
serverProcess: null,
}
beforeAll(async () => { beforeAll(async () => {
ensureDevCerts() ensureDevCerts()
@@ -74,33 +76,35 @@ describe('Server and CoT integration', () => {
BOOTSTRAP_EMAIL: COT_AUTH_USER, BOOTSTRAP_EMAIL: COT_AUTH_USER,
BOOTSTRAP_PASSWORD: COT_AUTH_PASS, BOOTSTRAP_PASSWORD: COT_AUTH_PASS,
} }
serverProcess = spawn('node', ['.output/server/index.mjs'], { testState.serverProcess = spawn('node', ['.output/server/index.mjs'], {
cwd: projectRoot, cwd: projectRoot,
env, env,
stdio: ['ignore', 'pipe', 'pipe'], stdio: ['ignore', 'pipe', 'pipe'],
}) })
serverProcess.stdout?.on('data', d => process.stdout.write(d)) testState.serverProcess.stdout?.on('data', d => process.stdout.write(d))
serverProcess.stderr?.on('data', d => process.stderr.write(d)) testState.serverProcess.stderr?.on('data', d => process.stderr.write(d))
await waitForHealth(90000) await waitForHealth(90000)
}, 120000) }, 120000)
afterAll(() => { afterAll(() => {
if (serverProcess?.pid) { if (testState.serverProcess?.pid) {
serverProcess.kill('SIGTERM') testState.serverProcess.kill('SIGTERM')
} }
}) })
it('serves health on port 3000', async () => { it('serves health on port 3000', async () => {
let res const tryProtocols = async (protocols) => {
for (const protocol of ['https', 'http']) { if (protocols.length === 0) throw new Error('No protocol succeeded')
try { try {
res = await fetch(`${protocol}://localhost:${API_PORT}/health`, { method: 'GET', headers: { Accept: 'application/json' } }) const res = await fetch(`${protocols[0]}://localhost:${API_PORT}/health`, { method: 'GET', headers: { Accept: 'application/json' } })
if (res?.ok) break if (res?.ok) return res
return tryProtocols(protocols.slice(1))
} }
catch { catch {
// try next return tryProtocols(protocols.slice(1))
} }
} }
const res = await tryProtocols(['https', 'http'])
expect(res?.ok).toBe(true) expect(res?.ok).toBe(true)
const body = await res.json() const body = await res.json()
expect(body).toHaveProperty('status', 'ok') expect(body).toHaveProperty('status', 'ok')

View File

@@ -2,23 +2,25 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { registerCleanup, graceful, initShutdownHandlers, clearCleanup } from '../../server/utils/shutdown.js' import { registerCleanup, graceful, initShutdownHandlers, clearCleanup } from '../../server/utils/shutdown.js'
describe('shutdown integration', () => { describe('shutdown integration', () => {
let originalExit const testState = {
let exitCalls originalExit: null,
let originalOn exitCalls: [],
originalOn: null,
}
beforeEach(() => { beforeEach(() => {
clearCleanup() clearCleanup()
exitCalls = [] testState.exitCalls = []
originalExit = process.exit testState.originalExit = process.exit
process.exit = vi.fn((code) => { process.exit = vi.fn((code) => {
exitCalls.push(code) testState.exitCalls.push(code)
}) })
originalOn = process.on testState.originalOn = process.on
}) })
afterEach(() => { afterEach(() => {
process.exit = originalExit process.exit = testState.originalExit
process.on = originalOn process.on = testState.originalOn
clearCleanup() clearCleanup()
}) })
@@ -30,7 +32,7 @@ describe('shutdown integration', () => {
initShutdownHandlers() initShutdownHandlers()
expect(process.on).toHaveBeenCalledWith('SIGTERM', expect.any(Function)) expect(process.on).toHaveBeenCalledWith('SIGTERM', expect.any(Function))
expect(process.on).toHaveBeenCalledWith('SIGINT', expect.any(Function)) expect(process.on).toHaveBeenCalledWith('SIGINT', expect.any(Function))
process.on = originalOn process.on = testState.originalOn
}) })
it('signal handler calls graceful', async () => { it('signal handler calls graceful', async () => {
@@ -42,8 +44,8 @@ describe('shutdown integration', () => {
const sigtermHandler = handlers.SIGTERM const sigtermHandler = handlers.SIGTERM
expect(sigtermHandler).toBeDefined() expect(sigtermHandler).toBeDefined()
await sigtermHandler() await sigtermHandler()
expect(exitCalls.length).toBeGreaterThan(0) expect(testState.exitCalls.length).toBeGreaterThan(0)
process.on = originalOn process.on = testState.originalOn
}) })
it('signal handler handles graceful error', async () => { it('signal handler handles graceful error', async () => {
@@ -58,8 +60,8 @@ describe('shutdown integration', () => {
throw new Error('Force error') throw new Error('Force error')
}) })
await sigintHandler() await sigintHandler()
expect(exitCalls.length).toBeGreaterThan(0) expect(testState.exitCalls.length).toBeGreaterThan(0)
process.on = originalOn process.on = testState.originalOn
}) })
it('covers timeout path in graceful', async () => { it('covers timeout path in graceful', async () => {
@@ -68,7 +70,7 @@ describe('shutdown integration', () => {
}) })
graceful() graceful()
await new Promise(resolve => setTimeout(resolve, 100)) await new Promise(resolve => setTimeout(resolve, 100))
expect(exitCalls.length).toBeGreaterThan(0) expect(testState.exitCalls.length).toBeGreaterThan(0)
}) })
it('covers graceful catch block', async () => { it('covers graceful catch block', async () => {
@@ -76,6 +78,6 @@ describe('shutdown integration', () => {
throw new Error('Test error') throw new Error('Test error')
}) })
await graceful() await graceful()
expect(exitCalls.length).toBeGreaterThan(0) expect(testState.exitCalls.length).toBeGreaterThan(0)
}) })
}) })

View File

@@ -8,10 +8,12 @@ const wait = (ms = 10) => new Promise(resolve => setTimeout(resolve, ms))
describe('app/utils/logger', () => { describe('app/utils/logger', () => {
const consoleMocks = {} const consoleMocks = {}
const originalConsole = {} const originalConsole = {}
let serverCalls const testState = {
serverCalls: [],
}
beforeEach(() => { beforeEach(() => {
serverCalls = [] testState.serverCalls = []
const calls = { log: [], error: [], warn: [], debug: [] } const calls = { log: [], error: [], warn: [], debug: [] }
Object.keys(calls).forEach((key) => { Object.keys(calls).forEach((key) => {
@@ -22,7 +24,7 @@ describe('app/utils/logger', () => {
registerEndpoint('/api/log', async (event) => { registerEndpoint('/api/log', async (event) => {
const body = event.body || (await readBody(event).catch(() => ({}))) const body = event.body || (await readBody(event).catch(() => ({})))
serverCalls.push(body) testState.serverCalls.push(body)
return { ok: true } return { ok: true }
}, { method: 'POST' }) }, { method: 'POST' })
}) })
@@ -40,7 +42,7 @@ describe('app/utils/logger', () => {
logError('Test message') logError('Test message')
await wait() await wait()
expect(serverCalls[0]).toMatchObject({ expect(testState.serverCalls[0]).toMatchObject({
sessionId: 'session-123', sessionId: 'session-123',
userId: 'user-456', userId: 'user-456',
}) })
@@ -59,7 +61,7 @@ describe('app/utils/logger', () => {
await wait() await wait()
expect(consoleMocks[consoleKey]).toHaveBeenCalledWith(`[Test message]`, { key: 'value' }) expect(consoleMocks[consoleKey]).toHaveBeenCalledWith(`[Test message]`, { key: 'value' })
expect(serverCalls[0]).toMatchObject({ expect(testState.serverCalls[0]).toMatchObject({
level, level,
message: 'Test message', message: 'Test message',
data: { key: 'value' }, data: { key: 'value' },
@@ -84,7 +86,7 @@ describe('app/utils/logger', () => {
logError('Test message') logError('Test message')
await wait() await wait()
expect(serverCalls[0].timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/) expect(testState.serverCalls[0].timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
}) })
it('handles null sessionId and userId', async () => { it('handles null sessionId and userId', async () => {
@@ -92,7 +94,7 @@ describe('app/utils/logger', () => {
logError('Test message') logError('Test message')
await wait() await wait()
const { sessionId, userId } = serverCalls[0] const { sessionId, userId } = testState.serverCalls[0]
expect(sessionId === null || sessionId === undefined).toBe(true) expect(sessionId === null || sessionId === undefined).toBe(true)
expect(userId === null || userId === undefined).toBe(true) expect(userId === null || userId === undefined).toBe(true)
}) })