This commit is contained in:
@@ -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')
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user