From 82bd51c3a4a3bd8b1b6b8afd8c5a24a5f3bc05d7 Mon Sep 17 00:00:00 2001 From: Madison Grubb Date: Tue, 17 Feb 2026 11:21:02 -0500 Subject: [PATCH] more functional design principles for tests --- test/integration/server-and-cot.spec.js | 26 +++++++++++-------- test/integration/shutdown.spec.js | 34 +++++++++++++------------ test/nuxt/logger.spec.js | 16 +++++++----- 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/test/integration/server-and-cot.spec.js b/test/integration/server-and-cot.spec.js index 540b034..f2d9058 100644 --- a/test/integration/server-and-cot.spec.js +++ b/test/integration/server-and-cot.spec.js @@ -59,7 +59,9 @@ async function waitForHealth(timeoutMs = 90000) { } describe('Server and CoT integration', () => { - let serverProcess = null + const testState = { + serverProcess: null, + } beforeAll(async () => { ensureDevCerts() @@ -74,33 +76,35 @@ describe('Server and CoT integration', () => { BOOTSTRAP_EMAIL: COT_AUTH_USER, BOOTSTRAP_PASSWORD: COT_AUTH_PASS, } - serverProcess = spawn('node', ['.output/server/index.mjs'], { + testState.serverProcess = spawn('node', ['.output/server/index.mjs'], { cwd: projectRoot, env, stdio: ['ignore', 'pipe', 'pipe'], }) - serverProcess.stdout?.on('data', d => process.stdout.write(d)) - serverProcess.stderr?.on('data', d => process.stderr.write(d)) + testState.serverProcess.stdout?.on('data', d => process.stdout.write(d)) + testState.serverProcess.stderr?.on('data', d => process.stderr.write(d)) await waitForHealth(90000) }, 120000) afterAll(() => { - if (serverProcess?.pid) { - serverProcess.kill('SIGTERM') + if (testState.serverProcess?.pid) { + testState.serverProcess.kill('SIGTERM') } }) it('serves health on port 3000', async () => { - let res - for (const protocol of ['https', 'http']) { + const tryProtocols = async (protocols) => { + if (protocols.length === 0) throw new Error('No protocol succeeded') try { - res = await fetch(`${protocol}://localhost:${API_PORT}/health`, { method: 'GET', headers: { Accept: 'application/json' } }) - if (res?.ok) break + const res = await fetch(`${protocols[0]}://localhost:${API_PORT}/health`, { method: 'GET', headers: { Accept: 'application/json' } }) + if (res?.ok) return res + return tryProtocols(protocols.slice(1)) } catch { - // try next + return tryProtocols(protocols.slice(1)) } } + const res = await tryProtocols(['https', 'http']) expect(res?.ok).toBe(true) const body = await res.json() expect(body).toHaveProperty('status', 'ok') diff --git a/test/integration/shutdown.spec.js b/test/integration/shutdown.spec.js index 776af41..9a6b7e5 100644 --- a/test/integration/shutdown.spec.js +++ b/test/integration/shutdown.spec.js @@ -2,23 +2,25 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { registerCleanup, graceful, initShutdownHandlers, clearCleanup } from '../../server/utils/shutdown.js' describe('shutdown integration', () => { - let originalExit - let exitCalls - let originalOn + const testState = { + originalExit: null, + exitCalls: [], + originalOn: null, + } beforeEach(() => { clearCleanup() - exitCalls = [] - originalExit = process.exit + testState.exitCalls = [] + testState.originalExit = process.exit process.exit = vi.fn((code) => { - exitCalls.push(code) + testState.exitCalls.push(code) }) - originalOn = process.on + testState.originalOn = process.on }) afterEach(() => { - process.exit = originalExit - process.on = originalOn + process.exit = testState.originalExit + process.on = testState.originalOn clearCleanup() }) @@ -30,7 +32,7 @@ describe('shutdown integration', () => { initShutdownHandlers() expect(process.on).toHaveBeenCalledWith('SIGTERM', expect.any(Function)) expect(process.on).toHaveBeenCalledWith('SIGINT', expect.any(Function)) - process.on = originalOn + process.on = testState.originalOn }) it('signal handler calls graceful', async () => { @@ -42,8 +44,8 @@ describe('shutdown integration', () => { const sigtermHandler = handlers.SIGTERM expect(sigtermHandler).toBeDefined() await sigtermHandler() - expect(exitCalls.length).toBeGreaterThan(0) - process.on = originalOn + expect(testState.exitCalls.length).toBeGreaterThan(0) + process.on = testState.originalOn }) it('signal handler handles graceful error', async () => { @@ -58,8 +60,8 @@ describe('shutdown integration', () => { throw new Error('Force error') }) await sigintHandler() - expect(exitCalls.length).toBeGreaterThan(0) - process.on = originalOn + expect(testState.exitCalls.length).toBeGreaterThan(0) + process.on = testState.originalOn }) it('covers timeout path in graceful', async () => { @@ -68,7 +70,7 @@ describe('shutdown integration', () => { }) graceful() await new Promise(resolve => setTimeout(resolve, 100)) - expect(exitCalls.length).toBeGreaterThan(0) + expect(testState.exitCalls.length).toBeGreaterThan(0) }) it('covers graceful catch block', async () => { @@ -76,6 +78,6 @@ describe('shutdown integration', () => { throw new Error('Test error') }) await graceful() - expect(exitCalls.length).toBeGreaterThan(0) + expect(testState.exitCalls.length).toBeGreaterThan(0) }) }) diff --git a/test/nuxt/logger.spec.js b/test/nuxt/logger.spec.js index 33fcb05..289c747 100644 --- a/test/nuxt/logger.spec.js +++ b/test/nuxt/logger.spec.js @@ -8,10 +8,12 @@ const wait = (ms = 10) => new Promise(resolve => setTimeout(resolve, ms)) describe('app/utils/logger', () => { const consoleMocks = {} const originalConsole = {} - let serverCalls + const testState = { + serverCalls: [], + } beforeEach(() => { - serverCalls = [] + testState.serverCalls = [] const calls = { log: [], error: [], warn: [], debug: [] } Object.keys(calls).forEach((key) => { @@ -22,7 +24,7 @@ describe('app/utils/logger', () => { registerEndpoint('/api/log', async (event) => { const body = event.body || (await readBody(event).catch(() => ({}))) - serverCalls.push(body) + testState.serverCalls.push(body) return { ok: true } }, { method: 'POST' }) }) @@ -40,7 +42,7 @@ describe('app/utils/logger', () => { logError('Test message') await wait() - expect(serverCalls[0]).toMatchObject({ + expect(testState.serverCalls[0]).toMatchObject({ sessionId: 'session-123', userId: 'user-456', }) @@ -59,7 +61,7 @@ describe('app/utils/logger', () => { await wait() expect(consoleMocks[consoleKey]).toHaveBeenCalledWith(`[Test message]`, { key: 'value' }) - expect(serverCalls[0]).toMatchObject({ + expect(testState.serverCalls[0]).toMatchObject({ level, message: 'Test message', data: { key: 'value' }, @@ -84,7 +86,7 @@ describe('app/utils/logger', () => { logError('Test message') 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 () => { @@ -92,7 +94,7 @@ describe('app/utils/logger', () => { logError('Test message') await wait() - const { sessionId, userId } = serverCalls[0] + const { sessionId, userId } = testState.serverCalls[0] expect(sessionId === null || sessionId === undefined).toBe(true) expect(userId === null || userId === undefined).toBe(true) })