// learning_test.go -- 权限学习系统的单元测试. // // 覆盖场景: // - TrackDecision 决策追踪 // - SuggestPermanentRules 永久规则建议(达到阈值后建议) // - 只记录 allow/deny 决策 // - Reset 重置所有追踪数据 // - Stats 统计信息 // - 建议只出现一次(不重复建议) package permission import ( "testing" ) // TestLearningTracker_TrackDecision 测试决策追踪 func TestLearningTracker_TrackDecision(t *testing.T) { lt := NewLearningTracker(3) lt.TrackDecision("Bash", map[string]any{"command": "npm install"}, DecisionAllow) lt.TrackDecision("Bash", map[string]any{"command": "npm test"}, DecisionAllow) stats := lt.Stats() if stats.TotalDecisions != 2 { t.Errorf("总决策数: %d, 期望 2", stats.TotalDecisions) } if stats.TotalAllowed != 2 { t.Errorf("总允许数: %d, 期望 2", stats.TotalAllowed) } } // TestLearningTracker_IgnoreAskDecision 测试 Ask 决策不记录 func TestLearningTracker_IgnoreAskDecision(t *testing.T) { lt := NewLearningTracker(3) lt.TrackDecision("Bash", map[string]any{"command": "npm install"}, DecisionAsk) stats := lt.Stats() if stats.TotalDecisions != 0 { t.Errorf("Ask 决策不应被记录, 总数: %d", stats.TotalDecisions) } } // TestLearningTracker_SuggestPermanentRules 测试达到阈值后建议永久规则 func TestLearningTracker_SuggestPermanentRules(t *testing.T) { lt := NewLearningTracker(3) // 允许 npm 命令 3 次(达到阈值) for i := 0; i < 3; i++ { lt.TrackDecision("Bash", map[string]any{"command": "npm install"}, DecisionAllow) } suggestions := lt.SuggestPermanentRules() if len(suggestions) == 0 { t.Fatal("达到阈值应有规则建议") } found := false for _, s := range suggestions { if s.RuleString == "Bash(prefix:npm)" { found = true break } } if !found { t.Error("应建议 Bash(prefix:npm) 规则") } } // TestLearningTracker_NoSuggestionBelowThreshold 测试未达阈值不建议 func TestLearningTracker_NoSuggestionBelowThreshold(t *testing.T) { lt := NewLearningTracker(3) // 只允许 2 次(未达阈值) lt.TrackDecision("Bash", map[string]any{"command": "npm install"}, DecisionAllow) lt.TrackDecision("Bash", map[string]any{"command": "npm test"}, DecisionAllow) suggestions := lt.SuggestPermanentRules() if len(suggestions) != 0 { t.Errorf("未达阈值不应有建议, 实际 %d 条", len(suggestions)) } } // TestLearningTracker_SuggestOnlyOnce 测试建议只出现一次 func TestLearningTracker_SuggestOnlyOnce(t *testing.T) { lt := NewLearningTracker(3) for i := 0; i < 5; i++ { lt.TrackDecision("Bash", map[string]any{"command": "npm install"}, DecisionAllow) } // 第一次应有建议 s1 := lt.SuggestPermanentRules() if len(s1) == 0 { t.Fatal("第一次应有建议") } // 第二次不应重复建议 s2 := lt.SuggestPermanentRules() if len(s2) != 0 { t.Errorf("不应重复建议, 第二次有 %d 条", len(s2)) } } // TestLearningTracker_Reset 测试重置 func TestLearningTracker_Reset(t *testing.T) { lt := NewLearningTracker(3) for i := 0; i < 5; i++ { lt.TrackDecision("Bash", map[string]any{"command": "npm install"}, DecisionAllow) } lt.Reset() stats := lt.Stats() if stats.TotalDecisions != 0 { t.Errorf("重置后总决策数应为 0, 实际: %d", stats.TotalDecisions) } suggestions := lt.SuggestPermanentRules() if len(suggestions) != 0 { t.Errorf("重置后不应有建议, 实际 %d 条", len(suggestions)) } } // TestLearningTracker_FileDecision 测试文件操作决策追踪 func TestLearningTracker_FileDecision(t *testing.T) { lt := NewLearningTracker(2) lt.TrackDecision("Edit", map[string]any{"file_path": "/src/a.go"}, DecisionAllow) lt.TrackDecision("Edit", map[string]any{"file_path": "/src/b.go"}, DecisionAllow) suggestions := lt.SuggestPermanentRules() if len(suggestions) == 0 { t.Fatal("文件操作达到阈值应有建议") } } // TestLearningTracker_WebFetchDecision 测试网页访问决策追踪 func TestLearningTracker_WebFetchDecision(t *testing.T) { lt := NewLearningTracker(2) lt.TrackDecision("WebFetch", map[string]any{"url": "https://example.com/a"}, DecisionAllow) lt.TrackDecision("WebFetch", map[string]any{"url": "https://example.com/b"}, DecisionAllow) suggestions := lt.SuggestPermanentRules() if len(suggestions) == 0 { t.Fatal("WebFetch 达到阈值应有建议") } } // TestLearningTracker_DenyPreventssSuggestion 测试拒绝次数过多时不建议 func TestLearningTracker_DenyPreventssSuggestion(t *testing.T) { lt := NewLearningTracker(3) // 允许 3 次但拒绝 2 次(allow > deny*2 才建议) lt.TrackDecision("Bash", map[string]any{"command": "npm install"}, DecisionAllow) lt.TrackDecision("Bash", map[string]any{"command": "npm install"}, DecisionAllow) lt.TrackDecision("Bash", map[string]any{"command": "npm install"}, DecisionAllow) lt.TrackDecision("Bash", map[string]any{"command": "npm install"}, DecisionDeny) lt.TrackDecision("Bash", map[string]any{"command": "npm install"}, DecisionDeny) suggestions := lt.SuggestPermanentRules() // allow=3, deny=2, 3 > 2*2=4 为假,不应建议 if len(suggestions) != 0 { t.Errorf("拒绝次数过多不应建议, 实际 %d 条", len(suggestions)) } } // TestExtractDirPattern 测试目录模式提取 func TestExtractDirPattern(t *testing.T) { tests := []struct { path string want string }{ {"/src/main.go", "/src"}, {"/a/b/c/d.go", "/a/b/c"}, {"main.go", "."}, {"", ""}, } for _, tt := range tests { got := extractDirPattern(tt.path) if got != tt.want { t.Errorf("extractDirPattern(%q) = %q, 期望 %q", tt.path, got, tt.want) } } }