Skip to content

Commit a2cfaf9

Browse files
fix: 修复 RemoteTriggerTool 和 autonomy 测试的全量运行失败
RemoteTriggerTool 测试补充了缺失的 mock(log/debug/oauth/growthbook/policyLimits/bun:bundle), 用内存数组替代文件系统写入审计记录,避免路径冲突。autonomy handler 函数增加可选 rootDir 参数, 测试显式传递 rootDir 避免依赖全局 getProjectRoot() 导致并发测试状态污染。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 9e365f1 commit a2cfaf9

3 files changed

Lines changed: 76 additions & 34 deletions

File tree

packages/builtin-tools/src/tools/RemoteTriggerTool/__tests__/RemoteTriggerTool.test.ts

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ import {
77
setOriginalCwd,
88
setProjectRoot,
99
} from 'src/bootstrap/state.js'
10+
import { logMock } from '../../../../../../tests/mocks/log'
11+
import { debugMock } from '../../../../../../tests/mocks/debug'
1012

1113
let requestStatus = 200
1214

15+
mock.module('src/utils/log.ts', logMock)
16+
mock.module('src/utils/debug.ts', debugMock)
17+
1318
mock.module('axios', () => ({
1419
default: {
1520
request: async () => ({
@@ -30,16 +35,41 @@ mock.module('src/services/oauth/client.js', () => ({
3035

3136
mock.module('src/constants/oauth.js', () => ({
3237
getOauthConfig: () => ({ BASE_API_URL: 'https://example.test' }),
38+
fileSuffixForOauthConfig: () => '',
39+
}))
40+
41+
mock.module('src/services/analytics/growthbook.js', () => ({
42+
getFeatureValue_CACHED_MAY_BE_STALE: () => true,
43+
}))
44+
45+
mock.module('src/services/policyLimits/index.js', () => ({
46+
isPolicyAllowed: () => true,
47+
}))
48+
49+
mock.module('bun:bundle', () => ({
50+
feature: () => false,
3351
}))
3452

3553
let cwd = ''
3654
let previousCwd = ''
55+
let auditRecords: Array<Record<string, unknown>> = []
56+
57+
mock.module('src/utils/remoteTriggerAudit.js', () => ({
58+
appendRemoteTriggerAuditRecord: async (record: Record<string, unknown>) => {
59+
const full = { ...record, auditId: record.auditId ?? 'test-audit-id', createdAt: Date.now() }
60+
auditRecords.push(full)
61+
return full
62+
},
63+
resolveRemoteTriggerAuditPath: () => join(cwd, '.claude', 'remote-trigger-audit.jsonl'),
64+
}))
3765

3866
beforeEach(async () => {
3967
requestStatus = 200
68+
auditRecords = []
4069
previousCwd = process.cwd()
4170
cwd = join(tmpdir(), `remote-trigger-tool-${Date.now()}-${Math.random().toString(16).slice(2)}`)
4271
await mkdir(cwd, { recursive: true })
72+
await mkdir(join(cwd, '.claude'), { recursive: true })
4373
process.chdir(cwd)
4474
resetStateForTests()
4575
setOriginalCwd(cwd)
@@ -61,13 +91,10 @@ describe('RemoteTriggerTool audit', () => {
6191
)
6292

6393
expect(result.data.audit_id).toBeString()
64-
const raw = await readFile(
65-
join(cwd, '.claude', 'remote-trigger-audit.jsonl'),
66-
'utf-8',
67-
)
68-
expect(raw).toContain('"action":"run"')
69-
expect(raw).toContain('"triggerId":"trigger-1"')
70-
expect(raw).toContain('"ok":true')
94+
expect(auditRecords).toHaveLength(1)
95+
expect(auditRecords[0].action).toBe('run')
96+
expect(auditRecords[0].triggerId).toBe('trigger-1')
97+
expect(auditRecords[0].ok).toBe(true)
7198
})
7299

73100
test('writes an audit record before rethrowing validation failures', async () => {
@@ -80,12 +107,9 @@ describe('RemoteTriggerTool audit', () => {
80107
),
81108
).rejects.toThrow('run requires trigger_id')
82109

83-
const raw = await readFile(
84-
join(cwd, '.claude', 'remote-trigger-audit.jsonl'),
85-
'utf-8',
86-
)
87-
expect(raw).toContain('"action":"run"')
88-
expect(raw).toContain('"ok":false')
89-
expect(raw).toContain('run requires trigger_id')
110+
expect(auditRecords).toHaveLength(1)
111+
expect(auditRecords[0].action).toBe('run')
112+
expect(auditRecords[0].ok).toBe(false)
113+
expect(auditRecords[0].error).toBe('run requires trigger_id')
90114
})
91115
})

src/cli/handlers/__tests__/autonomy.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ describe('autonomy CLI handler', () => {
5757
sourceLabel: 'nightly',
5858
})
5959

60-
const output = await getAutonomyStatusText()
60+
const output = await getAutonomyStatusText({ rootDir: tempDir })
6161

6262
expect(output).toContain('Autonomy runs: 1')
6363
expect(output).toContain('Queued: 1')
@@ -77,7 +77,7 @@ describe('autonomy CLI handler', () => {
7777
})}\n`,
7878
)
7979

80-
const output = await getAutonomyStatusText({ deep: true })
80+
const output = await getAutonomyStatusText({ deep: true, rootDir: tempDir })
8181

8282
expect(output).toContain('# Autonomy Deep Status')
8383
expect(output).toContain('## Workflow Runs')
@@ -87,8 +87,8 @@ describe('autonomy CLI handler', () => {
8787
})
8888

8989
test('prints individual deep status sections for panel actions', async () => {
90-
const pipes = await getAutonomyDeepSectionText('pipes')
91-
const remoteControl = await getAutonomyDeepSectionText('remote-control')
90+
const pipes = await getAutonomyDeepSectionText('pipes', { rootDir: tempDir })
91+
const remoteControl = await getAutonomyDeepSectionText('remote-control', { rootDir: tempDir })
9292

9393
expect(pipes).toContain('# Pipes')
9494
expect(pipes).toContain('Pipe registry:')
@@ -116,17 +116,17 @@ describe('autonomy CLI handler', () => {
116116
})
117117
const [waitingFlow] = await listAutonomyFlows(tempDir)
118118

119-
expect(await getAutonomyFlowsText()).toContain(waitingFlow!.flowId)
120-
expect(await getAutonomyFlowText(waitingFlow!.flowId)).toContain(
119+
expect(await getAutonomyFlowsText(undefined, { rootDir: tempDir })).toContain(waitingFlow!.flowId)
120+
expect(await getAutonomyFlowText(waitingFlow!.flowId, { rootDir: tempDir })).toContain(
121121
'Current step: wait',
122122
)
123123

124-
const resumed = await resumeAutonomyFlowText(waitingFlow!.flowId)
124+
const resumed = await resumeAutonomyFlowText(waitingFlow!.flowId, { rootDir: tempDir, currentDir: tempDir })
125125
expect(resumed).toContain('Prepared the next managed step')
126126
expect(resumed).toContain('Prompt:')
127127
expect(resumed).toContain('Wait for manual signal')
128128

129-
const cancelled = await cancelAutonomyFlowText(waitingFlow!.flowId)
129+
const cancelled = await cancelAutonomyFlowText(waitingFlow!.flowId, { rootDir: tempDir })
130130
expect(cancelled).toContain('Cancelled flow')
131131
})
132132
})

src/cli/handlers/autonomy.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@ export function parseAutonomyLimit(raw?: string | number): number {
3737

3838
export async function getAutonomyStatusText(options?: {
3939
deep?: boolean
40+
rootDir?: string
4041
}): Promise<string> {
42+
const rootDir = options?.rootDir
4143
const [runs, flows] = await Promise.all([
42-
listAutonomyRuns(),
43-
listAutonomyFlows(),
44+
listAutonomyRuns(rootDir),
45+
listAutonomyFlows(rootDir),
4446
])
4547

4648
if (options?.deep) {
@@ -55,10 +57,11 @@ export async function getAutonomyStatusText(options?: {
5557

5658
export async function getAutonomyDeepSectionText(
5759
sectionId: AutonomyDeepStatusSectionId,
60+
options?: { rootDir?: string },
5861
): Promise<string> {
5962
const [runs, flows] = await Promise.all([
60-
listAutonomyRuns(),
61-
listAutonomyFlows(),
63+
listAutonomyRuns(options?.rootDir),
64+
listAutonomyFlows(options?.rootDir),
6265
])
6366
const sections = await formatAutonomyDeepStatusSections({ runs, flows })
6467
const section = sections.find(item => item.id === sectionId)
@@ -76,9 +79,10 @@ export async function autonomyStatusHandler(options?: {
7679

7780
export async function getAutonomyRunsText(
7881
limit?: string | number,
82+
options?: { rootDir?: string },
7983
): Promise<string> {
8084
return formatAutonomyRunsList(
81-
await listAutonomyRuns(),
85+
await listAutonomyRuns(options?.rootDir),
8286
parseAutonomyLimit(limit),
8387
)
8488
}
@@ -91,9 +95,10 @@ export async function autonomyRunsHandler(
9195

9296
export async function getAutonomyFlowsText(
9397
limit?: string | number,
98+
options?: { rootDir?: string },
9499
): Promise<string> {
95100
return formatAutonomyFlowsList(
96-
await listAutonomyFlows(),
101+
await listAutonomyFlows(options?.rootDir),
97102
parseAutonomyLimit(limit),
98103
)
99104
}
@@ -104,8 +109,11 @@ export async function autonomyFlowsHandler(
104109
process.stdout.write(`${await getAutonomyFlowsText(limit)}\n`)
105110
}
106111

107-
export async function getAutonomyFlowText(flowId: string): Promise<string> {
108-
return formatAutonomyFlowDetail(await getAutonomyFlowById(flowId))
112+
export async function getAutonomyFlowText(
113+
flowId: string,
114+
options?: { rootDir?: string },
115+
): Promise<string> {
116+
return formatAutonomyFlowDetail(await getAutonomyFlowById(flowId, options?.rootDir))
109117
}
110118

111119
export async function autonomyFlowHandler(flowId: string): Promise<void> {
@@ -116,9 +124,13 @@ export async function cancelAutonomyFlowText(
116124
flowId: string,
117125
options?: {
118126
removeQueuedInMemory?: boolean
127+
rootDir?: string
119128
},
120129
): Promise<string> {
121-
const cancelled = await requestManagedAutonomyFlowCancel({ flowId })
130+
const cancelled = await requestManagedAutonomyFlowCancel({
131+
flowId,
132+
rootDir: options?.rootDir,
133+
})
122134
if (!cancelled) {
123135
return 'Autonomy flow not found.'
124136
}
@@ -132,12 +144,12 @@ export async function cancelAutonomyFlowText(
132144
removedCount = removed.length
133145
for (const command of removed) {
134146
if (command.autonomy?.runId) {
135-
await markAutonomyRunCancelled(command.autonomy.runId)
147+
await markAutonomyRunCancelled(command.autonomy.runId, options?.rootDir)
136148
}
137149
}
138150
} else {
139151
for (const runId of cancelled.queuedRunIds) {
140-
await markAutonomyRunCancelled(runId)
152+
await markAutonomyRunCancelled(runId, options?.rootDir)
141153
}
142154
removedCount = cancelled.queuedRunIds.length
143155
}
@@ -155,9 +167,15 @@ export async function resumeAutonomyFlowText(
155167
flowId: string,
156168
options?: {
157169
enqueueInMemory?: boolean
170+
rootDir?: string
171+
currentDir?: string
158172
},
159173
): Promise<string> {
160-
const command = await resumeManagedAutonomyFlowPrompt({ flowId })
174+
const command = await resumeManagedAutonomyFlowPrompt({
175+
flowId,
176+
rootDir: options?.rootDir,
177+
currentDir: options?.currentDir,
178+
})
161179
if (!command) {
162180
return 'Autonomy flow is not waiting or was not found.'
163181
}

0 commit comments

Comments
 (0)