test(background-agent): add unit tests for extracted modules

Add 104 new tests across 4 test files:
- error-classifier.test.ts (80 tests): isRecord, isAbortedSessionError, getErrorText, extractErrorName, extractErrorMessage, getSessionErrorMessage
- fallback-retry-handler.test.ts (19 tests): retry logic, fallback chain, concurrency release, session abort, queue management
- process-cleanup.test.ts (7 tests): signal registration, multi-manager shutdown, cleanup on unregister
- compaction-aware-message-resolver.test.ts (13 tests): compaction agent detection, message resolution with temp dirs (pre-existing, verified)

Total background-agent tests: 161 -> 265 (104 new, 0 regressions)
This commit is contained in:
YeonGyu-Kim
2026-02-22 11:59:06 +09:00
parent d53bcfbced
commit 8d66d5641a
4 changed files with 967 additions and 0 deletions
@@ -0,0 +1,156 @@
import { describe, test, expect, beforeEach, afterEach, mock } from "bun:test"
import {
registerManagerForCleanup,
unregisterManagerForCleanup,
_resetForTesting,
} from "./process-cleanup"
describe("process-cleanup", () => {
const registeredManagers: Array<{ shutdown: () => void }> = []
const mockShutdown = mock(() => {})
const processOnCalls: Array<[string, Function]> = []
const processOffCalls: Array<[string, Function]> = []
const originalProcessOn = process.on.bind(process)
const originalProcessOff = process.off.bind(process)
beforeEach(() => {
mockShutdown.mockClear()
processOnCalls.length = 0
processOffCalls.length = 0
registeredManagers.length = 0
process.on = originalProcessOn as any
process.off = originalProcessOff as any
_resetForTesting()
process.on = ((event: string, listener: Function) => {
processOnCalls.push([event, listener])
return process
}) as any
process.off = ((event: string, listener: Function) => {
processOffCalls.push([event, listener])
return process
}) as any
})
afterEach(() => {
process.on = originalProcessOn as any
process.off = originalProcessOff as any
for (const manager of [...registeredManagers]) {
unregisterManagerForCleanup(manager)
}
})
describe("registerManagerForCleanup", () => {
test("registers signal handlers on first manager", () => {
const manager = { shutdown: mockShutdown }
registeredManagers.push(manager)
registerManagerForCleanup(manager)
const signals = processOnCalls.map(([signal]) => signal)
expect(signals).toContain("SIGINT")
expect(signals).toContain("SIGTERM")
expect(signals).toContain("beforeExit")
expect(signals).toContain("exit")
})
test("signal listener calls shutdown on registered manager", () => {
const manager = { shutdown: mockShutdown }
registeredManagers.push(manager)
registerManagerForCleanup(manager)
const [, listener] = processOnCalls[0]
listener()
expect(mockShutdown).toHaveBeenCalled()
})
test("multiple managers all get shutdown when signal fires", () => {
const shutdown1 = mock(() => {})
const shutdown2 = mock(() => {})
const shutdown3 = mock(() => {})
const manager1 = { shutdown: shutdown1 }
const manager2 = { shutdown: shutdown2 }
const manager3 = { shutdown: shutdown3 }
registeredManagers.push(manager1, manager2, manager3)
registerManagerForCleanup(manager1)
registerManagerForCleanup(manager2)
registerManagerForCleanup(manager3)
const [, listener] = processOnCalls[0]
listener()
expect(shutdown1).toHaveBeenCalledTimes(1)
expect(shutdown2).toHaveBeenCalledTimes(1)
expect(shutdown3).toHaveBeenCalledTimes(1)
})
test("does not re-register signal handlers for subsequent managers", () => {
const manager1 = { shutdown: mockShutdown }
const manager2 = { shutdown: mockShutdown }
registeredManagers.push(manager1, manager2)
registerManagerForCleanup(manager1)
const callsAfterFirst = processOnCalls.length
registerManagerForCleanup(manager2)
expect(processOnCalls.length).toBe(callsAfterFirst)
})
})
describe("unregisterManagerForCleanup", () => {
test("removes signal handlers when last manager unregisters", () => {
const manager = { shutdown: mockShutdown }
registeredManagers.push(manager)
registerManagerForCleanup(manager)
unregisterManagerForCleanup(manager)
registeredManagers.length = 0
const offSignals = processOffCalls.map(([signal]) => signal)
expect(offSignals).toContain("SIGINT")
expect(offSignals).toContain("SIGTERM")
expect(offSignals).toContain("beforeExit")
expect(offSignals).toContain("exit")
})
test("keeps signal handlers when other managers remain", () => {
const manager1 = { shutdown: mockShutdown }
const manager2 = { shutdown: mockShutdown }
registeredManagers.push(manager1, manager2)
registerManagerForCleanup(manager1)
registerManagerForCleanup(manager2)
unregisterManagerForCleanup(manager2)
expect(processOffCalls.length).toBe(0)
})
test("remaining managers still get shutdown after partial unregister", () => {
const shutdown1 = mock(() => {})
const shutdown2 = mock(() => {})
const manager1 = { shutdown: shutdown1 }
const manager2 = { shutdown: shutdown2 }
registeredManagers.push(manager1, manager2)
registerManagerForCleanup(manager1)
registerManagerForCleanup(manager2)
const [, listener] = processOnCalls[0]
unregisterManagerForCleanup(manager2)
listener()
expect(shutdown1).toHaveBeenCalledTimes(1)
expect(shutdown2).not.toHaveBeenCalled()
})
})
})