// rules_test.go -- 权限规则解析和序列化的单元测试. // // 覆盖场景: // - ParseRule 解析各种格式(纯工具名,带括号内容,通配符) // - ParseContent 解析规则内容类型(prefix,path,domain,none) // - SerializeRule 序列化规则回字符串 // - LoadRulesFromSettings 从配置加载规则 // - SourcePriority 优先级数值 package permission import ( "testing" ) // TestParseRule 测试规则解析 func TestParseRule(t *testing.T) { tests := []struct { name string ruleStr string source RuleSource behavior Decision wantTool string wantContent string }{ {"纯工具名", "Bash", SourceUser, DecisionAllow, "Bash", ""}, {"通配符", "*", SourceProject, DecisionAllow, "*", ""}, {"前缀规则", "Bash(prefix:npm)", SourceLocal, DecisionAllow, "Bash", "prefix:npm"}, {"路径规则", "Edit(/src/**)", SourceCLI, DecisionDeny, "Edit", "/src/**"}, {"域名规则", "WebFetch(domain:example.com)", SourceSession, DecisionAllow, "WebFetch", "domain:example.com"}, {"带空格", " Bash ", SourceUser, DecisionAllow, "Bash", ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rule := ParseRule(tt.ruleStr, tt.source, tt.behavior) if rule.ToolName != tt.wantTool { t.Errorf("ToolName: %q, 期望 %q", rule.ToolName, tt.wantTool) } if rule.Content != tt.wantContent { t.Errorf("Content: %q, 期望 %q", rule.Content, tt.wantContent) } if rule.Source != tt.source { t.Errorf("Source: %q, 期望 %q", rule.Source, tt.source) } if rule.Behavior != tt.behavior { t.Errorf("Behavior: %q, 期望 %q", rule.Behavior, tt.behavior) } }) } } // TestParseContent 测试规则内容解析 func TestParseContent(t *testing.T) { tests := []struct { name string content string wantType ContentType wantValue string }{ {"空内容", "", ContentNone, ""}, {"前缀", "prefix:npm install", ContentPrefix, "npm install"}, {"域名", "domain:example.com", ContentDomain, "example.com"}, {"绝对路径", "/src/**", ContentPath, "/src/**"}, {"相对路径", "./src/**", ContentPath, "./src/**"}, {"通配符路径", "**/*.go", ContentPath, "**/*.go"}, {"未知内容当前缀", "something", ContentPrefix, "something"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { parsed := ParseContent(tt.content) if parsed.Type != tt.wantType { t.Errorf("Type: %q, 期望 %q", parsed.Type, tt.wantType) } if parsed.Value != tt.wantValue { t.Errorf("Value: %q, 期望 %q", parsed.Value, tt.wantValue) } }) } } // TestSerializeRule 测试规则序列化 func TestSerializeRule(t *testing.T) { tests := []struct { name string rule Rule want string }{ {"无内容", Rule{ToolName: "Bash"}, "Bash"}, {"通配符", Rule{ToolName: "*"}, "*"}, {"有内容", Rule{ToolName: "Bash", Content: "prefix:npm"}, "Bash(prefix:npm)"}, {"路径规则", Rule{ToolName: "Edit", Content: "/src/**"}, "Edit(/src/**)"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := SerializeRule(tt.rule) if got != tt.want { t.Errorf("SerializeRule = %q, 期望 %q", got, tt.want) } }) } } // TestLoadRulesFromSettings 测试从设置加载规则 func TestLoadRulesFromSettings(t *testing.T) { allowed := []string{"Bash(prefix:npm)", "Glob", "Grep"} denied := []string{"Bash(prefix:rm)"} rules := LoadRulesFromSettings(allowed, denied, SourceProject) // 应有 4 条规则(3 allow + 1 deny) if len(rules) != 4 { t.Fatalf("期望 4 条规则, 实际 %d 条", len(rules)) } // 检查 allow 规则 allowCount := 0 denyCount := 0 for _, r := range rules { if r.Behavior == DecisionAllow { allowCount++ } if r.Behavior == DecisionDeny { denyCount++ } if r.Source != SourceProject { t.Errorf("所有规则的来源应为 project, 实际: %q", r.Source) } } if allowCount != 3 { t.Errorf("期望 3 条 allow 规则, 实际 %d", allowCount) } if denyCount != 1 { t.Errorf("期望 1 条 deny 规则, 实际 %d", denyCount) } } // TestSourcePriority 测试规则来源优先级 func TestSourcePriority(t *testing.T) { // 验证优先级从低到高 priorities := []RuleSource{SourceUser, SourceProject, SourceLocal, SourceFlag, SourcePolicy, SourceCLI, SourceSession} for i := 1; i < len(priorities); i++ { if SourcePriority(priorities[i]) <= SourcePriority(priorities[i-1]) { t.Errorf("%q 优先级应高于 %q", priorities[i], priorities[i-1]) } } // 未知来源返回 -1 if SourcePriority("unknown") != -1 { t.Error("未知来源应返回 -1") } }