mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-19 06:45:50 +00:00
test: keep Codecov coverage on real agent communication paths
PR #369 was merged before the final Codecov coverage fix landed, so this follow-up carries only the incremental real-path tests needed on top of main. The tests exercise AgentSummary lifecycle branches, mailbox fail-closed behavior, UDS client connection failure through a real capability file, and UDS response-reader framing without mock.module, warning suppression, feature fallback, or production-code churn. Constraint: PR #369 is already merged; this branch must contain only the incremental Codecov repair on top of latest main Rejected: Reopen or keep pushing the merged PR branch | merged PR refs do not update and would leave Codecov stale Rejected: Mock bun:bundle or hide warnings | would reintroduce cross-test pollution and pseudo coverage Rejected: Keep unrelated SendMessageTool production diff | it created avoidable patch-coverage debt without improving the runtime path Confidence: high Scope-risk: narrow Directive: Keep these coverage tests on real paths; do not replace them with output suppression or feature-flag mocks Tested: bunx tsc --noEmit --pretty false Tested: bun run lint Tested: bun test src\utils\__tests__\teammateMailbox.test.ts Tested: bun test src\services\AgentSummary\__tests__\agentSummary.test.ts src\services\AgentSummary\__tests__\summaryContext.test.ts src\utils\__tests__\teammateMailbox.test.ts src\utils\__tests__\udsMessaging.test.ts src\utils\__tests__\udsResponseReader.test.ts packages\builtin-tools\src\tools\SendMessageTool\__tests__\udsRecipientSanitization.test.ts Tested: bun run test:all Tested: bun test --coverage --coverage-reporter lcov --coverage-dir coverage Tested: bun run build Tested: bun run build:vite Tested: bun audit Tested: git diff --check Tested: Claude simplify review GO (.omx/artifacts/claude-simplify-codecov-20260427-1521.md) Tested: Claude security review GO (.omx/artifacts/claude-security-codecov-20260427-1522.md) Not-tested: GitHub-hosted Codecov upload after this amended commit until PR checks rerun
This commit is contained in:
@@ -5,7 +5,10 @@ import type {
|
|||||||
CacheSafeParams,
|
CacheSafeParams,
|
||||||
ForkedAgentResult,
|
ForkedAgentResult,
|
||||||
} from '../../../utils/forkedAgent.js'
|
} from '../../../utils/forkedAgent.js'
|
||||||
import { startAgentSummarization } from '../agentSummary.js'
|
import {
|
||||||
|
type AgentSummaryDependencies,
|
||||||
|
startAgentSummarization,
|
||||||
|
} from '../agentSummary.js'
|
||||||
|
|
||||||
const transcriptMessages = [
|
const transcriptMessages = [
|
||||||
{ type: 'user', message: { content: 'start' }, uuid: 'u1' },
|
{ type: 'user', message: { content: 'start' }, uuid: 'u1' },
|
||||||
@@ -27,17 +30,14 @@ describe('startAgentSummarization', () => {
|
|||||||
let forkCalls: ForkCall[]
|
let forkCalls: ForkCall[]
|
||||||
let updateCalls: Array<{ taskId: string; summary: string }>
|
let updateCalls: Array<{ taskId: string; summary: string }>
|
||||||
let transcriptMessagesForTest: Message[]
|
let transcriptMessagesForTest: Message[]
|
||||||
|
let debugLogs: string[]
|
||||||
|
let loggedErrors: Error[]
|
||||||
|
let clearedHandles: unknown[]
|
||||||
|
|
||||||
beforeEach(() => {
|
function startTestSummarization(
|
||||||
forkCalls = []
|
dependencies: AgentSummaryDependencies = {},
|
||||||
updateCalls = []
|
): { stop: () => void } {
|
||||||
scheduled = undefined
|
return startAgentSummarization(
|
||||||
handle = undefined
|
|
||||||
transcriptMessagesForTest = transcriptMessages
|
|
||||||
})
|
|
||||||
|
|
||||||
test('summarizes bounded transcript once and skips unchanged fingerprints', async () => {
|
|
||||||
handle = startAgentSummarization(
|
|
||||||
'task-1',
|
'task-1',
|
||||||
asAgentId('a0000000000000000'),
|
asAgentId('a0000000000000000'),
|
||||||
{
|
{
|
||||||
@@ -48,14 +48,22 @@ describe('startAgentSummarization', () => {
|
|||||||
} as unknown as CacheSafeParams,
|
} as unknown as CacheSafeParams,
|
||||||
() => undefined,
|
() => undefined,
|
||||||
{
|
{
|
||||||
clearTimeout: () => undefined,
|
clearTimeout: ((timeoutId: unknown) => {
|
||||||
|
clearedHandles.push(timeoutId)
|
||||||
|
}) as typeof clearTimeout,
|
||||||
getAgentTranscript: async () => ({
|
getAgentTranscript: async () => ({
|
||||||
messages: transcriptMessagesForTest,
|
messages: transcriptMessagesForTest,
|
||||||
contentReplacements: [],
|
contentReplacements: [],
|
||||||
}),
|
}),
|
||||||
isPoorModeActive: () => false,
|
isPoorModeActive: () => false,
|
||||||
logError: () => undefined,
|
logError: error => {
|
||||||
logForDebugging: () => undefined,
|
loggedErrors.push(
|
||||||
|
error instanceof Error ? error : new Error(String(error)),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
logForDebugging: message => {
|
||||||
|
debugLogs.push(message)
|
||||||
|
},
|
||||||
runForkedAgent: async (args: ForkCall) => {
|
runForkedAgent: async (args: ForkCall) => {
|
||||||
forkCalls.push(args)
|
forkCalls.push(args)
|
||||||
return {
|
return {
|
||||||
@@ -79,8 +87,24 @@ describe('startAgentSummarization', () => {
|
|||||||
updateAgentSummary: (taskId: string, summary: string) => {
|
updateAgentSummary: (taskId: string, summary: string) => {
|
||||||
updateCalls.push({ taskId, summary })
|
updateCalls.push({ taskId, summary })
|
||||||
},
|
},
|
||||||
|
...dependencies,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
forkCalls = []
|
||||||
|
updateCalls = []
|
||||||
|
scheduled = undefined
|
||||||
|
handle = undefined
|
||||||
|
transcriptMessagesForTest = transcriptMessages
|
||||||
|
debugLogs = []
|
||||||
|
loggedErrors = []
|
||||||
|
clearedHandles = []
|
||||||
|
})
|
||||||
|
|
||||||
|
test('summarizes bounded transcript once and skips unchanged fingerprints', async () => {
|
||||||
|
handle = startTestSummarization()
|
||||||
|
|
||||||
expect(typeof scheduled).toBe('function')
|
expect(typeof scheduled).toBe('function')
|
||||||
await scheduled!()
|
await scheduled!()
|
||||||
@@ -106,47 +130,83 @@ describe('startAgentSummarization', () => {
|
|||||||
expect(updateCalls).toHaveLength(1)
|
expect(updateCalls).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('skips summarization when bounded context is too small', async () => {
|
test('skips summarization when filtering leaves too little bounded context', async () => {
|
||||||
transcriptMessagesForTest = transcriptMessages.slice(0, 2)
|
transcriptMessagesForTest = [
|
||||||
|
{ type: 'user', message: { content: 'start' }, uuid: 'u1' },
|
||||||
handle = startAgentSummarization(
|
|
||||||
'task-1',
|
|
||||||
asAgentId('a0000000000000000'),
|
|
||||||
{
|
{
|
||||||
forkContextMessages: transcriptMessages,
|
type: 'assistant',
|
||||||
model: 'claude-test',
|
uuid: 'a1',
|
||||||
} as unknown as CacheSafeParams,
|
message: {
|
||||||
() => undefined,
|
content: [{ type: 'tool_use', id: 'missing', name: 'Read' }],
|
||||||
{
|
|
||||||
clearTimeout: () => undefined,
|
|
||||||
getAgentTranscript: async () => ({
|
|
||||||
messages: transcriptMessagesForTest,
|
|
||||||
contentReplacements: [],
|
|
||||||
}),
|
|
||||||
isPoorModeActive: () => false,
|
|
||||||
logError: () => undefined,
|
|
||||||
logForDebugging: () => undefined,
|
|
||||||
runForkedAgent: async (args: ForkCall) => {
|
|
||||||
forkCalls.push(args)
|
|
||||||
return { messages: [] } as unknown as ForkedAgentResult
|
|
||||||
},
|
|
||||||
setTimeout: ((callback: TimerHandler) => {
|
|
||||||
if (typeof callback !== 'function') {
|
|
||||||
throw new Error('Expected timer callback')
|
|
||||||
}
|
|
||||||
scheduled = callback as () => void | Promise<void>
|
|
||||||
return 1 as unknown as ReturnType<typeof setTimeout>
|
|
||||||
}) as unknown as typeof setTimeout,
|
|
||||||
updateAgentSummary: (taskId: string, summary: string) => {
|
|
||||||
updateCalls.push({ taskId, summary })
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
{ type: 'user', message: { content: 'continue' }, uuid: 'u2' },
|
||||||
|
] as unknown as Message[]
|
||||||
|
|
||||||
|
handle = startTestSummarization()
|
||||||
|
|
||||||
expect(typeof scheduled).toBe('function')
|
expect(typeof scheduled).toBe('function')
|
||||||
await scheduled!()
|
await scheduled!()
|
||||||
|
|
||||||
expect(forkCalls).toEqual([])
|
expect(forkCalls).toEqual([])
|
||||||
expect(updateCalls).toEqual([])
|
expect(updateCalls).toEqual([])
|
||||||
|
expect(debugLogs).toContain(
|
||||||
|
'[AgentSummary] Skipping summary for task-1: no bounded context available',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('skips summarization before building context when transcript is too short', async () => {
|
||||||
|
transcriptMessagesForTest = transcriptMessages.slice(0, 2)
|
||||||
|
handle = startTestSummarization()
|
||||||
|
|
||||||
|
expect(typeof scheduled).toBe('function')
|
||||||
|
await scheduled!()
|
||||||
|
|
||||||
|
expect(forkCalls).toEqual([])
|
||||||
|
expect(updateCalls).toEqual([])
|
||||||
|
expect(debugLogs).toContain(
|
||||||
|
'[AgentSummary] Skipping summary for task-1: not enough messages (2)',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('skips and reschedules while poor mode is active', async () => {
|
||||||
|
handle = startTestSummarization({
|
||||||
|
isPoorModeActive: () => true,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(typeof scheduled).toBe('function')
|
||||||
|
await scheduled!()
|
||||||
|
|
||||||
|
expect(forkCalls).toEqual([])
|
||||||
|
expect(updateCalls).toEqual([])
|
||||||
|
expect(debugLogs).toContain(
|
||||||
|
'[AgentSummary] Skipping summary — poor mode active',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('logs summary errors and keeps the next timer owned by the summarizer', async () => {
|
||||||
|
const error = new Error('fork failed')
|
||||||
|
handle = startTestSummarization({
|
||||||
|
runForkedAgent: async () => {
|
||||||
|
throw error
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(typeof scheduled).toBe('function')
|
||||||
|
await scheduled!()
|
||||||
|
|
||||||
|
expect(loggedErrors).toEqual([error])
|
||||||
|
expect(updateCalls).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('stop clears the pending summary timer', () => {
|
||||||
|
handle = startTestSummarization()
|
||||||
|
|
||||||
|
handle.stop()
|
||||||
|
|
||||||
|
expect(debugLogs).toContain(
|
||||||
|
'[AgentSummary] Stopping summarization for task-1',
|
||||||
|
)
|
||||||
|
expect(clearedHandles).toEqual([1])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -141,6 +141,13 @@ describe('getSummaryContextFingerprint', () => {
|
|||||||
expect(estimateMessageChars(message)).toBeGreaterThan(0)
|
expect(estimateMessageChars(message)).toBeGreaterThan(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('treats unsupported top-level primitives as zero-size estimates', () => {
|
||||||
|
expect(
|
||||||
|
estimateMessageChars((() => undefined) as unknown as Message),
|
||||||
|
).toBe(0)
|
||||||
|
expect(estimateMessageChars(1n as unknown as Message)).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
test('returns null for an empty transcript', () => {
|
test('returns null for an empty transcript', () => {
|
||||||
expect(getSummaryContextFingerprint([])).toBeNull()
|
expect(getSummaryContextFingerprint([])).toBeNull()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -171,6 +171,17 @@ describe('compactMailboxMessages', () => {
|
|||||||
|
|
||||||
expect(compacted).toEqual([])
|
expect(compacted).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('returns an empty mailbox when all retention lanes are disabled', () => {
|
||||||
|
const compacted = compactMailboxMessages([message('unread', false)], {
|
||||||
|
maxMessages: 0,
|
||||||
|
maxReadMessages: 0,
|
||||||
|
maxUnreadProtocolMessages: 0,
|
||||||
|
maxRetainedBytes: 1_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(compacted).toEqual([])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('teammate mailbox retention', () => {
|
describe('teammate mailbox retention', () => {
|
||||||
@@ -331,6 +342,26 @@ describe('teammate mailbox retention', () => {
|
|||||||
expect(await readFile(inboxPath, 'utf-8')).toBe('{not-json')
|
expect(await readFile(inboxPath, 'utf-8')).toBe('{not-json')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('writeToMailbox rejects when the inbox path is already a directory', async () => {
|
||||||
|
const inboxPath = getInboxPath('worker', 'alpha')
|
||||||
|
await mkdir(inboxPath, { recursive: true })
|
||||||
|
|
||||||
|
const error = await writeToMailbox(
|
||||||
|
'worker',
|
||||||
|
{
|
||||||
|
from: 'team-lead',
|
||||||
|
text: 'new',
|
||||||
|
timestamp: new Date(5).toISOString(),
|
||||||
|
},
|
||||||
|
'alpha',
|
||||||
|
).then(
|
||||||
|
() => undefined,
|
||||||
|
error => error as NodeJS.ErrnoException,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(error?.code).toBe('EISDIR')
|
||||||
|
})
|
||||||
|
|
||||||
test('readMailbox fails closed on corrupt mailbox content', async () => {
|
test('readMailbox fails closed on corrupt mailbox content', async () => {
|
||||||
const inboxPath = getInboxPath('worker', 'alpha')
|
const inboxPath = getInboxPath('worker', 'alpha')
|
||||||
await mkdir(dirname(inboxPath), { recursive: true })
|
await mkdir(dirname(inboxPath), { recursive: true })
|
||||||
|
|||||||
@@ -217,6 +217,23 @@ describe('UDS inbox retention', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('udsClient send reports connection failures without leaking token state', async () => {
|
||||||
|
const path = socketPath('uds-client-connect-error')
|
||||||
|
const capabilityDir = join(tempConfigDir, 'messaging-capabilities')
|
||||||
|
const capabilityName = `${createHash('sha256').update(path).digest('hex')}.json`
|
||||||
|
await mkdir(capabilityDir, { recursive: true, mode: 0o700 })
|
||||||
|
await writeFile(
|
||||||
|
join(capabilityDir, capabilityName),
|
||||||
|
JSON.stringify({ socketPath: path, authToken: 'test-token' }),
|
||||||
|
'utf-8',
|
||||||
|
)
|
||||||
|
const { sendToUdsSocket } = await import('../udsClient.js')
|
||||||
|
|
||||||
|
await expect(sendToUdsSocket(path, 'hello')).rejects.toThrow(
|
||||||
|
'Failed to connect to peer',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test('sendUdsMessage fails closed before connecting without an auth token', async () => {
|
test('sendUdsMessage fails closed before connecting without an auth token', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
sendUdsMessage(socketPath('no-auth-token'), { type: 'text', data: 'x' }),
|
sendUdsMessage(socketPath('no-auth-token'), { type: 'text', data: 'x' }),
|
||||||
|
|||||||
@@ -97,6 +97,28 @@ describe('attachUdsResponseReader', () => {
|
|||||||
expect(socket.ended).toBe(true)
|
expect(socket.ended).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('continues scanning when blank and valid frames share one chunk', () => {
|
||||||
|
const socket = new FakeSocket()
|
||||||
|
let settled = false
|
||||||
|
let settledError: Error | undefined
|
||||||
|
|
||||||
|
attachUdsResponseReader(asSocket(socket), {
|
||||||
|
maxFrameBytes: 128,
|
||||||
|
onSettled: error => {
|
||||||
|
settled = true
|
||||||
|
settledError = error
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.emitData(
|
||||||
|
Buffer.from(`\n${JSON.stringify({ type: 'response' })}\n`),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(settled).toBe(true)
|
||||||
|
expect(settledError).toBeUndefined()
|
||||||
|
expect(socket.ended).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
test('rejects receiver error frames', () => {
|
test('rejects receiver error frames', () => {
|
||||||
const socket = new FakeSocket()
|
const socket = new FakeSocket()
|
||||||
let settledError: Error | undefined
|
let settledError: Error | undefined
|
||||||
@@ -116,6 +138,31 @@ describe('attachUdsResponseReader', () => {
|
|||||||
expect(socket.destroyed).toBe(true)
|
expect(socket.destroyed).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('ignores unrelated receiver frames until a terminal response arrives', () => {
|
||||||
|
const socket = new FakeSocket()
|
||||||
|
let settled = false
|
||||||
|
let settledError: Error | undefined
|
||||||
|
|
||||||
|
attachUdsResponseReader(asSocket(socket), {
|
||||||
|
maxFrameBytes: 128,
|
||||||
|
onSettled: error => {
|
||||||
|
settled = true
|
||||||
|
settledError = error
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.emitData(
|
||||||
|
Buffer.from(
|
||||||
|
`${JSON.stringify({ type: 'notification', data: 'queued' })}\n`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
expect(settled).toBe(false)
|
||||||
|
|
||||||
|
socket.emitData(Buffer.from(`${JSON.stringify({ type: 'response' })}\n`))
|
||||||
|
expect(settled).toBe(true)
|
||||||
|
expect(settledError).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
test('uses custom socket error formatting', () => {
|
test('uses custom socket error formatting', () => {
|
||||||
const socket = new FakeSocket()
|
const socket = new FakeSocket()
|
||||||
let settledError: Error | undefined
|
let settledError: Error | undefined
|
||||||
|
|||||||
Reference in New Issue
Block a user