claude-code/docs/test-plans/04-permission-system.md
2026-04-01 21:19:41 +08:00

5.1 KiB
Raw Blame History

权限系统测试计划

概述

权限系统控制工具是否可以执行,包含规则解析器、权限检查管线和权限模式判断。测试重点是纯函数解析器和规则匹配逻辑。

被测文件

文件 关键导出
src/utils/permissions/permissionRuleParser.ts permissionRuleValueFromString, permissionRuleValueToString, escapeRuleContent, unescapeRuleContent, normalizeLegacyToolName, getLegacyToolNames
src/utils/permissions/PermissionMode.ts 权限模式常量和辅助函数
src/utils/permissions/permissions.ts hasPermissionsToUseTool, getDenyRuleForTool, checkRuleBasedPermissions
src/types/permissions.ts PermissionMode, PermissionBehavior, PermissionRule 类型定义

测试用例

src/utils/permissions/permissionRuleParser.ts

describe('escapeRuleContent')

  • test('escapes backslashes first') — 'test\\value''test\\\\value'
  • test('escapes opening parentheses') — 'print(1)''print\\(1\\)'
  • test('escapes closing parentheses') — 'func()''func\\(\\)'
  • test('handles combined escape') — 'echo "test\\nvalue"' 中的 \\ 先转义
  • test('handles empty string') — ''''
  • test('no-op for string without special chars') — 'npm install' 原样返回

describe('unescapeRuleContent')

  • test('unescapes parentheses') — 'print\\(1\\)''print(1)'
  • test('unescapes backslashes last') — 'test\\\\nvalue''test\\nvalue'
  • test('handles empty string')
  • test('roundtrip: escape then unescape returns original') — unescapeRuleContent(escapeRuleContent(x)) === x

describe('permissionRuleValueFromString')

  • test('parses tool name only') — 'Bash'{ toolName: 'Bash' }
  • test('parses tool name with content') — 'Bash(npm install)'{ toolName: 'Bash', ruleContent: 'npm install' }
  • test('parses content with escaped parentheses') — 'Bash(python -c "print\\(1\\)")' → ruleContent 为 'python -c "print(1)"'
  • test('treats empty parens as tool-wide rule') — 'Bash()'{ toolName: 'Bash' }(无 ruleContent
  • test('treats wildcard content as tool-wide rule') — 'Bash(*)'{ toolName: 'Bash' }
  • test('normalizes legacy tool names') — 'Task'{ toolName: 'Agent' }(或对应的 AGENT_TOOL_NAME
  • test('handles malformed input: no closing paren') — 'Bash(npm' → 整个字符串作为 toolName
  • test('handles malformed input: content after closing paren') — 'Bash(npm)extra' → 整个字符串作为 toolName
  • test('handles missing tool name') — '(foo)' → 整个字符串作为 toolName

describe('permissionRuleValueToString')

  • test('serializes tool name only') — { toolName: 'Bash' }'Bash'
  • test('serializes with content') — { toolName: 'Bash', ruleContent: 'npm install' }'Bash(npm install)'
  • test('escapes content with parentheses') — ruleContent 含 () 时正确转义
  • test('roundtrip: fromString then toString preserves value') — 往返一致

describe('normalizeLegacyToolName')

  • test('maps Task to Agent tool name') — 'Task' → AGENT_TOOL_NAME
  • test('maps KillShell to TaskStop tool name') — 'KillShell' → TASK_STOP_TOOL_NAME
  • test('maps AgentOutputTool to TaskOutput tool name')
  • test('returns unknown names unchanged') — 'UnknownTool''UnknownTool'

describe('getLegacyToolNames')

  • test('returns legacy names for canonical name') — 给定 AGENT_TOOL_NAME 返回包含 'Task'
  • test('returns empty array for name with no legacy aliases')

src/utils/permissions/permissions.ts — 需 Mock

describe('getDenyRuleForTool')

  • test('returns deny rule matching tool name') — 匹配到 blanket deny 规则时返回
  • test('returns null when no deny rules match') — 无匹配时返回 null
  • test('matches MCP tools by server prefix') — mcp__server 规则匹配该 server 下的 MCP 工具
  • test('does not match content-specific deny rules') — 有 ruleContent 的 deny 规则不作为 blanket deny

describe('checkRuleBasedPermissions')(集成级别)

  • test('deny rule takes precedence over allow') — 同时有 allow 和 deny 时 deny 优先
  • test('ask rule prompts user') — 匹配 ask 规则返回 { behavior: 'ask' }
  • test('allow rule permits execution') — 匹配 allow 规则返回 { behavior: 'allow' }
  • test('passthrough when no rules match') — 无匹配规则返回 passthrough

Mock 需求

依赖 Mock 方式 说明
bun:bundle (feature) 已 polyfill BRIEF_TOOL_NAME 条件加载
Tool 常量导入 实际值 AGENT_TOOL_NAME 等从常量文件导入
appState mock object hasPermissionsToUseTool 中的状态依赖
Tool 对象 mock object 模拟 tool 的 name, checkPermissions 等

集成测试场景

describe('Permission pipeline end-to-end')

  • test('deny rule blocks tool before it runs') — deny 规则在 call 前拦截
  • test('bypassPermissions mode allows all') — bypass 模式下 ask → allow
  • test('dontAsk mode converts ask to deny') — dontAsk 模式下 ask → deny