// explainer_test.go -- 权限请求说明生成器的单元测试. // // 覆盖场景: // - ExplainPermissionRequest 各种工具类型的说明 // - AssessRisk 风险评估 // - SuggestRules 规则建议 // - truncateString 字符串截断 // - formatSize 大小格式化 package permission import ( "strings" "testing" ) // TestExplainPermissionRequest 测试各类工具权限说明生成 func TestExplainPermissionRequest(t *testing.T) { tests := []struct { name string toolName string input map[string]any contains string }{ {"Bash 命令", "Bash", map[string]any{"command": "npm install"}, "npm"}, {"空 Bash 命令", "Bash", map[string]any{}, "Shell"}, {"文件编辑", "Edit", map[string]any{"file_path": "/src/main.go"}, "main.go"}, {"文件写入", "Write", map[string]any{"file_path": "/src/new.go", "content": "package main"}, "new.go"}, {"网页访问", "WebFetch", map[string]any{"url": "https://example.com"}, "example.com"}, {"子代理", "Agent", map[string]any{"prompt": "帮我检查代码"}, "子代理"}, {"通用工具", "CustomTool", map[string]any{}, "CustomTool"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { msg := ExplainPermissionRequest(tt.toolName, tt.input) if !strings.Contains(msg, tt.contains) { t.Errorf("说明应包含 %q, 实际: %q", tt.contains, msg) } }) } } // TestAssessRisk 测试风险评估 func TestAssessRisk(t *testing.T) { tests := []struct { name string toolName string input map[string]any want RiskLevel }{ {"rm 命令高风险", "Bash", map[string]any{"command": "rm -rf /"}, RiskHigh}, // sudo 提权本身属于高风险--IsDangerousCommand 递归检测识别 sudo 前缀 // (原测试期望 Medium,原因是 ExtractCommandName 跳过 sudo 提取出 "apt",apt 不在高风险列表. // 新语义:sudo 提权无论子命令是什么都视为高风险,与 dangerousPatterns 中 sudo:nil 一致.) {"sudo 高风险", "Bash", map[string]any{"command": "sudo apt install"}, RiskHigh}, {"ls 低风险", "Bash", map[string]any{"command": "ls -la"}, RiskLow}, {"npm 中风险", "Bash", map[string]any{"command": "npm install"}, RiskMedium}, {"空命令高风险", "Bash", map[string]any{}, RiskHigh}, {"只读工具低风险", "Glob", map[string]any{}, RiskLow}, {"编辑普通文件中风险", "Edit", map[string]any{"file_path": "/src/main.go"}, RiskMedium}, {"编辑 .env 高风险", "Edit", map[string]any{"file_path": "/project/.env"}, RiskHigh}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := AssessRisk(tt.toolName, tt.input) if got != tt.want { t.Errorf("AssessRisk(%q) = %q, 期望 %q", tt.toolName, got, tt.want) } }) } } // TestSuggestRules 测试规则建议 func TestSuggestRules(t *testing.T) { // Bash 命令建议 suggestions := SuggestRules("Bash", map[string]any{"command": "npm install lodash"}) if len(suggestions) == 0 { t.Fatal("应有规则建议") } found := false for _, s := range suggestions { if strings.Contains(s.RuleString, "Bash(prefix:npm)") { found = true break } } if !found { t.Error("应建议 Bash(prefix:npm) 规则") } // 文件操作建议 suggestions = SuggestRules("Edit", map[string]any{"file_path": "/src/main.go"}) if len(suggestions) == 0 { t.Fatal("应有文件操作规则建议") } // WebFetch 建议 suggestions = SuggestRules("WebFetch", map[string]any{"url": "https://example.com/api"}) if len(suggestions) == 0 { t.Fatal("应有 WebFetch 规则建议") } if !strings.Contains(suggestions[0].RuleString, "domain:example.com") { t.Errorf("应建议域名规则, 实际: %s", suggestions[0].RuleString) } } // TestTruncateString 测试字符串截断 func TestTruncateString(t *testing.T) { tests := []struct { name string s string maxLen int want string }{ {"不截断", "hello", 10, "hello"}, {"精确长度", "hello", 5, "hello"}, {"截断加省略号", "hello world", 8, "hello..."}, {"极短最大长度", "hello", 3, "hel"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := truncateString(tt.s, tt.maxLen) if got != tt.want { t.Errorf("truncateString(%q, %d) = %q, 期望 %q", tt.s, tt.maxLen, got, tt.want) } }) } } // TestFormatSize 测试大小格式化 func TestFormatSize(t *testing.T) { tests := []struct { bytes int want string }{ {100, "100 字节"}, {1024, "1.0 KB"}, {1048576, "1.0 MB"}, } for _, tt := range tests { got := formatSize(tt.bytes) if got != tt.want { t.Errorf("formatSize(%d) = %q, 期望 %q", tt.bytes, got, tt.want) } } }