Files
claude-code/tests/integration/dependency-overrides.test.ts
unraid 452a7e6a15 fixup: address CodeRabbit review on PR #386
Twelve actionable items (7 Major + 5 Minor) from the CodeRabbit review on
claude-code-best/claude-code#386:

- docs/internals/autonomy-jira.md: typo "due input close" → "due to input close".
- src/utils/autonomyRuns.ts:
  - selectPersistedAutonomyRuns no longer evicts active (queued/running) runs
    when the combined list exceeds AUTONOMY_RUNS_MAX. Active runs are kept in
    full and the inactive history is capped to the remaining budget so
    persisted ownership for live work survives.
  - isValidOwnerProcessId now allows pid <= 4_194_304 so a live run owned by
    the maximum Linux PID is not treated as stale.
- src/utils/autonomyAuthority.ts: maskCodeFencedLines tracks the active fence
  length and only closes the fence when a same-character run of equal-or-
  greater length appears with no trailing content, so a nested ```yaml inside
  an outer ```` block no longer leaks fake `tasks:` entries into the parser.
- src/cli/print.ts: late-shutdown branches in the cron and scheduled-task
  paths now call cancelQueuedAutonomyCommands({ commands: [command] }) instead
  of markAutonomyRunCancelled(...). Updating run state alone left the
  queue-side record orphaned for resume/recovery.
- src/utils/processUserInput/processSlashCommand.tsx: scheduled-task-result
  notification is enqueued before finalizeAutonomyRunCompleted (which queues
  follow-up autonomy commands) so both at priority: 'later' land in order and
  the next autonomy step can not run before the worker's output is observed.
- src/screens/REPL.tsx + src/utils/handlePromptSubmit.ts:
  - onQuery now returns Promise<boolean>: false from the concurrent-guard
    skip path, true otherwise. Other call sites use `void onQuery(...)` and
    are unaffected. handlePromptSubmit's onQuery prop type matches.
  - The autonomy-prompt callsite captures the executed flag, finalizes
    claim.claimedCommands as { type: 'completed' } only when onQuery actually
    ran, and runs the completed-finalize in its own try/catch so a failure
    there does not propagate into the outer catch and trigger a second
    finalize as { type: 'failed' } for the same commands.
  - Removed the unsafe `command.value as string` cast; createUserMessage
    already accepts `string | ContentBlockParam[]`.
  - createUserMessage mock in src/__tests__/handlePromptSubmit.test.ts now
    matches the new Promise<boolean> shape.
- packages/builtin-tools/src/tools/RemoteTriggerTool/__tests__/
  RemoteTriggerTool.test.ts:
  - Inline auth mock replaced with the shared tests/mocks/auth (added).
  - The full mock of src/constants/oauth.js is replaced by a narrow
    side-effect-only mock that overrides the env-reading helpers
    (getOauthConfig, fileSuffixForOauthConfig, MCP_CLIENT_METADATA_URL) and
    delegates pure data exports to the real module.
- tests/integration/dependency-overrides.test.ts:
  - mermaid does not export `./package.json` in its exports map, so
    require.resolve('mermaid/package.json') throws
    ERR_PACKAGE_PATH_NOT_EXPORTED in runtimes that honor exports semantics.
    The test now resolves the package entry and walks up to the package
    root via a small findPackageJson helper.
  - readFileSync from node:fs is replaced with `await Bun.file(...).text()`
    to match the project's Bun-API requirement.

Validation:
- bun run typecheck (clean).
- bun test → 3996 pass / 0 fail across 305 test files.

Targets PRs:
- amDosion/claude-code-bast#8 (fork-internal review)
- claude-code-best/claude-code#386 (upstream review, same head branch)
2026-04-29 15:17:50 +08:00

176 lines
5.9 KiB
TypeScript

import { describe, expect, test } from 'bun:test'
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
const repoRoot = resolve(import.meta.dir, '..', '..')
const uuidV4Pattern =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
async function findPackageJson(
startPath: string,
expectedName: string,
): Promise<string> {
let current = dirname(startPath)
for (let depth = 0; depth < 10; depth++) {
const candidate = join(current, 'package.json')
const file = Bun.file(candidate)
if (await file.exists()) {
try {
const parsed = JSON.parse(await file.text()) as { name?: unknown }
if (parsed.name === expectedName) {
return candidate
}
} catch {
// ignore parse errors and keep walking up
}
}
const parent = dirname(current)
if (parent === current) {
break
}
current = parent
}
throw new Error(
`package.json with name "${expectedName}" not found above ${startPath}`,
)
}
describe('dependency security overrides', () => {
test('mcpb can load patched inquirer prompts from its package context', async () => {
const mcpbRequire = createRequire(import.meta.resolve('@anthropic-ai/mcpb'))
const promptsPath = mcpbRequire.resolve('@inquirer/prompts')
const prompts = (await import(pathToFileURL(promptsPath).href)) as {
input?: unknown
select?: unknown
}
expect(typeof prompts.input).toBe('function')
expect(typeof prompts.select).toBe('function')
})
test('google auth gaxios multipart boundary still uses a UUID', async () => {
const vertexRequire = createRequire(
import.meta.resolve('@anthropic-ai/vertex-sdk'),
)
const gaxios = vertexRequire('gaxios') as {
request(options: {
adapter(options: { headers: Headers; url: string }): Promise<{
config: unknown
data: string
headers: Record<string, string>
request: { responseURL: string }
status: number
statusText: string
}>
multipart: Array<{ body: string; headers: Record<string, string> }>
url: string
}): Promise<{ status: number }>
}
let contentType: string | undefined
const response = await gaxios.request({
url: 'https://example.com/upload',
multipart: [
{ body: 'payload', headers: { 'Content-Type': 'text/plain' } },
],
adapter: async options => {
contentType = options.headers.get('content-type') ?? undefined
return {
config: options,
data: '',
headers: {},
request: { responseURL: options.url },
status: 200,
statusText: 'OK',
}
},
})
expect(response.status).toBe(200)
expect(contentType).toMatch(/^multipart\/related; boundary=[0-9a-f-]{36}$/)
expect(contentType?.split('boundary=')[1]).toMatch(uuidV4Pattern)
})
test('azure identity msal guid generation works through its package context', () => {
const identityRequire = createRequire(
import.meta.resolve('@azure/identity'),
)
const msal = identityRequire('@azure/msal-node') as {
CryptoProvider: new () => { createNewGuid(): string }
}
const cryptoProvider = new msal.CryptoProvider()
expect(cryptoProvider.createNewGuid()).toMatch(uuidV4Pattern)
})
test('remote control markdown renderer resolves streamdown and mermaid', async () => {
const rcsRequire = createRequire(
join(repoRoot, 'packages/remote-control-server/package.json'),
)
const streamdownPath = rcsRequire.resolve('streamdown')
const streamdown = (await import(pathToFileURL(streamdownPath).href)) as {
Streamdown?: unknown
}
const streamdownRequire = createRequire(streamdownPath)
const uuid = (await import(
pathToFileURL(streamdownRequire.resolve('uuid')).href
)) as { v4(): string }
const mermaidPath = streamdownRequire.resolve('mermaid')
// mermaid does not export ./package.json in its exports map, so resolving
// 'mermaid/package.json' throws ERR_PACKAGE_PATH_NOT_EXPORTED in runtimes
// that honor exports semantics. Walk up from the resolved entry until a
// package.json with name === 'mermaid' is found.
const mermaidPackagePath = await findPackageJson(mermaidPath, 'mermaid')
const mermaidPackage = JSON.parse(
await Bun.file(mermaidPackagePath).text(),
) as {
name?: unknown
exports?: { '.'?: { import?: unknown } }
}
expect(streamdown.Streamdown).toBeDefined()
expect(uuid.v4()).toMatch(uuidV4Pattern)
expect(mermaidPackage.name).toBe('mermaid')
expect(mermaidPath).toContain('mermaid.core.mjs')
expect(mermaidPackage.exports?.['.']?.import).toBe(
'./dist/mermaid.core.mjs',
)
})
test('grpc proto-loader keeps its protobuf 7 parser path working', () => {
const exporterRequire = createRequire(
import.meta.resolve('@opentelemetry/exporter-trace-otlp-grpc'),
)
const grpcRequire = createRequire(exporterRequire.resolve('@grpc/grpc-js'))
const protoLoader = grpcRequire('@grpc/proto-loader') as {
loadSync(
path: string,
options?: Record<string, unknown>,
): Record<string, unknown>
}
const tempDir = mkdtempSync(join(tmpdir(), 'proto-loader-smoke-'))
const protoPath = join(tempDir, 'service.proto')
writeFileSync(
protoPath,
[
'syntax = "proto3";',
'package smoke;',
'message Ping { string id = 1; }',
'service PingService { rpc Send(Ping) returns (Ping); }',
].join('\n'),
)
try {
const loaded = protoLoader.loadSync(protoPath, { keepCase: true })
expect(loaded['smoke.Ping']).toBeDefined()
expect(loaded['smoke.PingService']).toBeDefined()
} finally {
rmSync(tempDir, { force: true, recursive: true })
}
})
})