mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 13:55:50 +00:00
test: 添加 subagent 僵死场景相关测试用例
覆盖 subagent 生命周期关键模块的零覆盖函数: - messageQueueManager: 扩展队列操作测试(enqueue/dequeue/优先级排序) - queueProcessor: 测试 subagent 通知过滤和批量处理 - LocalAgentTask: 测试状态转换、通知防重、进度追踪 - task/framework: 测试 updateTaskState、registerTask、evictTerminalTask 共 66 个测试用例,135 个断言,全部通过。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
162
src/utils/__tests__/queueProcessor.test.ts
Normal file
162
src/utils/__tests__/queueProcessor.test.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
||||
|
||||
import {
|
||||
resetCommandQueue,
|
||||
enqueue,
|
||||
enqueuePendingNotification,
|
||||
} from '../messageQueueManager.js'
|
||||
import { hasQueuedCommands, processQueueIfReady } from '../queueProcessor.js'
|
||||
|
||||
beforeEach(() => {
|
||||
resetCommandQueue()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
resetCommandQueue()
|
||||
})
|
||||
|
||||
describe('processQueueIfReady', () => {
|
||||
test('returns processed:false when queue empty', () => {
|
||||
const result = processQueueIfReady({
|
||||
executeInput: async () => {},
|
||||
})
|
||||
expect(result.processed).toBe(false)
|
||||
})
|
||||
|
||||
test('processes single slash command individually', () => {
|
||||
const executed: string[][] = []
|
||||
enqueue({ value: '/help', mode: 'prompt' } as any)
|
||||
|
||||
const result = processQueueIfReady({
|
||||
executeInput: async cmds => {
|
||||
executed.push(cmds.map(c => c.value as string))
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.processed).toBe(true)
|
||||
expect(executed).toHaveLength(1)
|
||||
expect(executed[0]).toEqual(['/help'])
|
||||
})
|
||||
|
||||
test('processes bash mode command individually', () => {
|
||||
const executed: string[][] = []
|
||||
enqueue({ value: 'git status', mode: 'bash' } as any)
|
||||
|
||||
const result = processQueueIfReady({
|
||||
executeInput: async cmds => {
|
||||
executed.push(cmds.map(c => c.value as string))
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.processed).toBe(true)
|
||||
expect(executed).toHaveLength(1)
|
||||
expect(executed[0]).toEqual(['git status'])
|
||||
})
|
||||
|
||||
test('batches commands with same mode', () => {
|
||||
const executed: string[][] = []
|
||||
enqueuePendingNotification({ value: '<task1/>', mode: 'task-notification' } as any)
|
||||
enqueuePendingNotification({ value: '<task2/>', mode: 'task-notification' } as any)
|
||||
|
||||
const result = processQueueIfReady({
|
||||
executeInput: async cmds => {
|
||||
executed.push(cmds.map(c => c.value as string))
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.processed).toBe(true)
|
||||
expect(executed).toHaveLength(1)
|
||||
expect(executed[0]).toEqual(['<task1/>', '<task2/>'])
|
||||
})
|
||||
|
||||
test('does not mix different modes in same batch', () => {
|
||||
const executed: string[][] = []
|
||||
enqueue({ value: 'hello', mode: 'prompt' } as any)
|
||||
enqueuePendingNotification({ value: '<task/>', mode: 'task-notification' } as any)
|
||||
|
||||
const result = processQueueIfReady({
|
||||
executeInput: async cmds => {
|
||||
executed.push(cmds.map(c => c.value as string))
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.processed).toBe(true)
|
||||
// Only the 'prompt' mode command should be processed (higher priority than task-notification)
|
||||
expect(executed).toHaveLength(1)
|
||||
expect(executed[0]).toEqual(['hello'])
|
||||
|
||||
// The task-notification is still in queue
|
||||
expect(hasQueuedCommands()).toBe(true)
|
||||
})
|
||||
|
||||
test('skips commands with agentId set (subagent notifications)', () => {
|
||||
// This simulates the v2.1.119 fix: subagent task-notification with agentId
|
||||
// should not be processed by the main thread queue processor
|
||||
enqueuePendingNotification({
|
||||
value: '<task-notification>subagent result</task-notification>',
|
||||
mode: 'task-notification',
|
||||
agentId: 'agent-123',
|
||||
} as any)
|
||||
|
||||
const result = processQueueIfReady({
|
||||
executeInput: async () => {},
|
||||
})
|
||||
|
||||
// Should not process — it's a subagent notification
|
||||
expect(result.processed).toBe(false)
|
||||
})
|
||||
|
||||
test('returns processed:false when only subagent commands in queue', () => {
|
||||
enqueuePendingNotification({
|
||||
value: '<task-notification/>',
|
||||
mode: 'task-notification',
|
||||
agentId: 'agent-456',
|
||||
} as any)
|
||||
enqueuePendingNotification({
|
||||
value: '<task-notification/>',
|
||||
mode: 'task-notification',
|
||||
agentId: 'agent-789',
|
||||
} as any)
|
||||
|
||||
const result = processQueueIfReady({
|
||||
executeInput: async () => {},
|
||||
})
|
||||
|
||||
expect(result.processed).toBe(false)
|
||||
expect(hasQueuedCommands()).toBe(true)
|
||||
})
|
||||
|
||||
test('processes main-thread command but skips subagent command', () => {
|
||||
const executed: string[][] = []
|
||||
enqueuePendingNotification({ value: '<main-task/>', mode: 'task-notification' } as any)
|
||||
enqueuePendingNotification({
|
||||
value: '<sub-task/>',
|
||||
mode: 'task-notification',
|
||||
agentId: 'agent-123',
|
||||
} as any)
|
||||
|
||||
const result = processQueueIfReady({
|
||||
executeInput: async cmds => {
|
||||
executed.push(cmds.map(c => c.value as string))
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.processed).toBe(true)
|
||||
expect(executed).toHaveLength(1)
|
||||
expect(executed[0]).toEqual(['<main-task/>'])
|
||||
|
||||
// Subagent command still in queue
|
||||
expect(hasQueuedCommands()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('hasQueuedCommands', () => {
|
||||
test('returns false when queue empty', () => {
|
||||
expect(hasQueuedCommands()).toBe(false)
|
||||
})
|
||||
|
||||
test('returns true when commands in queue', () => {
|
||||
enqueue({ value: 'hello', mode: 'prompt' } as any)
|
||||
expect(hasQueuedCommands()).toBe(true)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user