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>
This commit is contained in:
claude-code-best
2026-04-28 22:29:36 +08:00
parent 9e365f1ffa
commit a2cfaf9111
3 changed files with 76 additions and 34 deletions

View File

@@ -7,9 +7,14 @@ import {
setOriginalCwd,
setProjectRoot,
} from 'src/bootstrap/state.js'
import { logMock } from '../../../../../../tests/mocks/log'
import { debugMock } from '../../../../../../tests/mocks/debug'
let requestStatus = 200
mock.module('src/utils/log.ts', logMock)
mock.module('src/utils/debug.ts', debugMock)
mock.module('axios', () => ({
default: {
request: async () => ({
@@ -30,16 +35,41 @@ mock.module('src/services/oauth/client.js', () => ({
mock.module('src/constants/oauth.js', () => ({
getOauthConfig: () => ({ BASE_API_URL: 'https://example.test' }),
fileSuffixForOauthConfig: () => '',
}))
mock.module('src/services/analytics/growthbook.js', () => ({
getFeatureValue_CACHED_MAY_BE_STALE: () => true,
}))
mock.module('src/services/policyLimits/index.js', () => ({
isPolicyAllowed: () => true,
}))
mock.module('bun:bundle', () => ({
feature: () => false,
}))
let cwd = ''
let previousCwd = ''
let auditRecords: Array<Record<string, unknown>> = []
mock.module('src/utils/remoteTriggerAudit.js', () => ({
appendRemoteTriggerAuditRecord: async (record: Record<string, unknown>) => {
const full = { ...record, auditId: record.auditId ?? 'test-audit-id', createdAt: Date.now() }
auditRecords.push(full)
return full
},
resolveRemoteTriggerAuditPath: () => join(cwd, '.claude', 'remote-trigger-audit.jsonl'),
}))
beforeEach(async () => {
requestStatus = 200
auditRecords = []
previousCwd = process.cwd()
cwd = join(tmpdir(), `remote-trigger-tool-${Date.now()}-${Math.random().toString(16).slice(2)}`)
await mkdir(cwd, { recursive: true })
await mkdir(join(cwd, '.claude'), { recursive: true })
process.chdir(cwd)
resetStateForTests()
setOriginalCwd(cwd)
@@ -61,13 +91,10 @@ describe('RemoteTriggerTool audit', () => {
)
expect(result.data.audit_id).toBeString()
const raw = await readFile(
join(cwd, '.claude', 'remote-trigger-audit.jsonl'),
'utf-8',
)
expect(raw).toContain('"action":"run"')
expect(raw).toContain('"triggerId":"trigger-1"')
expect(raw).toContain('"ok":true')
expect(auditRecords).toHaveLength(1)
expect(auditRecords[0].action).toBe('run')
expect(auditRecords[0].triggerId).toBe('trigger-1')
expect(auditRecords[0].ok).toBe(true)
})
test('writes an audit record before rethrowing validation failures', async () => {
@@ -80,12 +107,9 @@ describe('RemoteTriggerTool audit', () => {
),
).rejects.toThrow('run requires trigger_id')
const raw = await readFile(
join(cwd, '.claude', 'remote-trigger-audit.jsonl'),
'utf-8',
)
expect(raw).toContain('"action":"run"')
expect(raw).toContain('"ok":false')
expect(raw).toContain('run requires trigger_id')
expect(auditRecords).toHaveLength(1)
expect(auditRecords[0].action).toBe('run')
expect(auditRecords[0].ok).toBe(false)
expect(auditRecords[0].error).toBe('run requires trigger_id')
})
})

View File

@@ -57,7 +57,7 @@ describe('autonomy CLI handler', () => {
sourceLabel: 'nightly',
})
const output = await getAutonomyStatusText()
const output = await getAutonomyStatusText({ rootDir: tempDir })
expect(output).toContain('Autonomy runs: 1')
expect(output).toContain('Queued: 1')
@@ -77,7 +77,7 @@ describe('autonomy CLI handler', () => {
})}\n`,
)
const output = await getAutonomyStatusText({ deep: true })
const output = await getAutonomyStatusText({ deep: true, rootDir: tempDir })
expect(output).toContain('# Autonomy Deep Status')
expect(output).toContain('## Workflow Runs')
@@ -87,8 +87,8 @@ describe('autonomy CLI handler', () => {
})
test('prints individual deep status sections for panel actions', async () => {
const pipes = await getAutonomyDeepSectionText('pipes')
const remoteControl = await getAutonomyDeepSectionText('remote-control')
const pipes = await getAutonomyDeepSectionText('pipes', { rootDir: tempDir })
const remoteControl = await getAutonomyDeepSectionText('remote-control', { rootDir: tempDir })
expect(pipes).toContain('# Pipes')
expect(pipes).toContain('Pipe registry:')
@@ -116,17 +116,17 @@ describe('autonomy CLI handler', () => {
})
const [waitingFlow] = await listAutonomyFlows(tempDir)
expect(await getAutonomyFlowsText()).toContain(waitingFlow!.flowId)
expect(await getAutonomyFlowText(waitingFlow!.flowId)).toContain(
expect(await getAutonomyFlowsText(undefined, { rootDir: tempDir })).toContain(waitingFlow!.flowId)
expect(await getAutonomyFlowText(waitingFlow!.flowId, { rootDir: tempDir })).toContain(
'Current step: wait',
)
const resumed = await resumeAutonomyFlowText(waitingFlow!.flowId)
const resumed = await resumeAutonomyFlowText(waitingFlow!.flowId, { rootDir: tempDir, currentDir: tempDir })
expect(resumed).toContain('Prepared the next managed step')
expect(resumed).toContain('Prompt:')
expect(resumed).toContain('Wait for manual signal')
const cancelled = await cancelAutonomyFlowText(waitingFlow!.flowId)
const cancelled = await cancelAutonomyFlowText(waitingFlow!.flowId, { rootDir: tempDir })
expect(cancelled).toContain('Cancelled flow')
})
})

View File

@@ -37,10 +37,12 @@ export function parseAutonomyLimit(raw?: string | number): number {
export async function getAutonomyStatusText(options?: {
deep?: boolean
rootDir?: string
}): Promise<string> {
const rootDir = options?.rootDir
const [runs, flows] = await Promise.all([
listAutonomyRuns(),
listAutonomyFlows(),
listAutonomyRuns(rootDir),
listAutonomyFlows(rootDir),
])
if (options?.deep) {
@@ -55,10 +57,11 @@ export async function getAutonomyStatusText(options?: {
export async function getAutonomyDeepSectionText(
sectionId: AutonomyDeepStatusSectionId,
options?: { rootDir?: string },
): Promise<string> {
const [runs, flows] = await Promise.all([
listAutonomyRuns(),
listAutonomyFlows(),
listAutonomyRuns(options?.rootDir),
listAutonomyFlows(options?.rootDir),
])
const sections = await formatAutonomyDeepStatusSections({ runs, flows })
const section = sections.find(item => item.id === sectionId)
@@ -76,9 +79,10 @@ export async function autonomyStatusHandler(options?: {
export async function getAutonomyRunsText(
limit?: string | number,
options?: { rootDir?: string },
): Promise<string> {
return formatAutonomyRunsList(
await listAutonomyRuns(),
await listAutonomyRuns(options?.rootDir),
parseAutonomyLimit(limit),
)
}
@@ -91,9 +95,10 @@ export async function autonomyRunsHandler(
export async function getAutonomyFlowsText(
limit?: string | number,
options?: { rootDir?: string },
): Promise<string> {
return formatAutonomyFlowsList(
await listAutonomyFlows(),
await listAutonomyFlows(options?.rootDir),
parseAutonomyLimit(limit),
)
}
@@ -104,8 +109,11 @@ export async function autonomyFlowsHandler(
process.stdout.write(`${await getAutonomyFlowsText(limit)}\n`)
}
export async function getAutonomyFlowText(flowId: string): Promise<string> {
return formatAutonomyFlowDetail(await getAutonomyFlowById(flowId))
export async function getAutonomyFlowText(
flowId: string,
options?: { rootDir?: string },
): Promise<string> {
return formatAutonomyFlowDetail(await getAutonomyFlowById(flowId, options?.rootDir))
}
export async function autonomyFlowHandler(flowId: string): Promise<void> {
@@ -116,9 +124,13 @@ export async function cancelAutonomyFlowText(
flowId: string,
options?: {
removeQueuedInMemory?: boolean
rootDir?: string
},
): Promise<string> {
const cancelled = await requestManagedAutonomyFlowCancel({ flowId })
const cancelled = await requestManagedAutonomyFlowCancel({
flowId,
rootDir: options?.rootDir,
})
if (!cancelled) {
return 'Autonomy flow not found.'
}
@@ -132,12 +144,12 @@ export async function cancelAutonomyFlowText(
removedCount = removed.length
for (const command of removed) {
if (command.autonomy?.runId) {
await markAutonomyRunCancelled(command.autonomy.runId)
await markAutonomyRunCancelled(command.autonomy.runId, options?.rootDir)
}
}
} else {
for (const runId of cancelled.queuedRunIds) {
await markAutonomyRunCancelled(runId)
await markAutonomyRunCancelled(runId, options?.rootDir)
}
removedCount = cancelled.queuedRunIds.length
}
@@ -155,9 +167,15 @@ export async function resumeAutonomyFlowText(
flowId: string,
options?: {
enqueueInMemory?: boolean
rootDir?: string
currentDir?: string
},
): Promise<string> {
const command = await resumeManagedAutonomyFlowPrompt({ flowId })
const command = await resumeManagedAutonomyFlowPrompt({
flowId,
rootDir: options?.rootDir,
currentDir: options?.currentDir,
})
if (!command) {
return 'Autonomy flow is not waiting or was not found.'
}