mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import {
|
|
classifyTruncatedHookRequest,
|
|
createHookTransportInterferenceTracker,
|
|
describeHookTransportInterference,
|
|
HookRequestTruncatedError,
|
|
isHookRequestTruncatedError
|
|
} from './agent-hook-transport-interference'
|
|
|
|
describe('classifyTruncatedHookRequest', () => {
|
|
it('reports truncation when fewer bytes arrived than Content-Length promised', () => {
|
|
const error = classifyTruncatedHookRequest('4096', 512)
|
|
expect(isHookRequestTruncatedError(error)).toBe(true)
|
|
expect(error).toMatchObject({ bytesRead: 512, contentLength: 4096 })
|
|
})
|
|
|
|
it('reports truncation when the connection died before any body byte', () => {
|
|
expect(classifyTruncatedHookRequest('4096', 0)).toBeInstanceOf(HookRequestTruncatedError)
|
|
})
|
|
|
|
it('stays silent for a complete body', () => {
|
|
expect(classifyTruncatedHookRequest('512', 512)).toBeNull()
|
|
})
|
|
|
|
it('stays silent without a usable Content-Length', () => {
|
|
// Why: chunked bodies and malformed headers prove nothing — reporting them would drown the signal.
|
|
expect(classifyTruncatedHookRequest(undefined, 10)).toBeNull()
|
|
expect(classifyTruncatedHookRequest('', 10)).toBeNull()
|
|
expect(classifyTruncatedHookRequest('not-a-number', 10)).toBeNull()
|
|
})
|
|
|
|
it('reads the first value when the header arrives duplicated', () => {
|
|
expect(classifyTruncatedHookRequest(['4096', '4096'], 12)).toBeInstanceOf(
|
|
HookRequestTruncatedError
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('createHookTransportInterferenceTracker', () => {
|
|
const truncation = { source: 'claude', error: new HookRequestTruncatedError(10, 900) }
|
|
|
|
it('stays quiet below the threshold so a single crashed writer is not an alarm', () => {
|
|
const onThreshold = vi.fn()
|
|
const tracker = createHookTransportInterferenceTracker(onThreshold, 3)
|
|
tracker.record(truncation)
|
|
tracker.record(truncation)
|
|
expect(onThreshold).not.toHaveBeenCalled()
|
|
expect(tracker.getCount()).toBe(2)
|
|
})
|
|
|
|
it('reports exactly once at the threshold and keeps counting after', () => {
|
|
const onThreshold = vi.fn()
|
|
const tracker = createHookTransportInterferenceTracker(onThreshold, 3)
|
|
for (let i = 0; i < 6; i++) {
|
|
tracker.record(truncation)
|
|
}
|
|
expect(onThreshold).toHaveBeenCalledTimes(1)
|
|
expect(onThreshold).toHaveBeenCalledWith({
|
|
count: 3,
|
|
source: 'claude',
|
|
bytesRead: 10,
|
|
contentLength: 900
|
|
})
|
|
expect(tracker.getCount()).toBe(6)
|
|
})
|
|
|
|
it('re-arms after reset', () => {
|
|
const onThreshold = vi.fn()
|
|
const tracker = createHookTransportInterferenceTracker(onThreshold, 1)
|
|
tracker.record(truncation)
|
|
tracker.reset()
|
|
tracker.record(truncation)
|
|
expect(onThreshold).toHaveBeenCalledTimes(2)
|
|
})
|
|
})
|
|
|
|
describe('describeHookTransportInterference', () => {
|
|
it('names the cause and the consequence so the log line is actionable', () => {
|
|
const message = describeHookTransportInterference({
|
|
count: 3,
|
|
source: 'codex',
|
|
bytesRead: 10,
|
|
contentLength: 900
|
|
})
|
|
expect(message).toContain('/hook/codex')
|
|
expect(message).toContain('10/900 bytes')
|
|
expect(message).toContain('security software')
|
|
// Why: the client's own --max-time can truncate too; a single-cause message would misdiagnose a stall.
|
|
expect(message).toContain('stalled past the hook client timeout')
|
|
})
|
|
|
|
it('omits the route when the truncation happened before it resolved', () => {
|
|
const message = describeHookTransportInterference({
|
|
count: 3,
|
|
source: null,
|
|
bytesRead: 0,
|
|
contentLength: 900
|
|
})
|
|
expect(message).not.toContain('/hook/')
|
|
})
|
|
})
|