major: kestrel is now a tak server #6
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user