mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
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:
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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.'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user