package quality import ( "context" "testing" ) // DetectorMock implements the Detector interface for testing type DetectorMock struct { name string severity Severity } func (m *DetectorMock) Name() string { return m.name } func (m *DetectorMock) Detect(ctx context.Context, path string, config *Config) ([]Finding, error) { return []Finding{ { Type: "mock_finding", Severity: m.severity, Status: StatusOpen, Score: 5, }, }, nil } func (m *DetectorMock) Severity() Severity { return m.severity } // LanguageDetectorMock implements both Detector and LanguageDetector interfaces type LanguageDetectorMock struct { *DetectorMock supportedLanguages []string } func (m *LanguageDetectorMock) SupportedLanguages() []string { return m.supportedLanguages } func (m *LanguageDetectorMock) ExtractFunctions(ctx context.Context, files []string) ([]FunctionInfo, error) { var functions []FunctionInfo for _, file := range files { functions = append(functions, FunctionInfo{ Name: "test_func", File: file, }) } return functions, nil } func (m *LanguageDetectorMock) ExtractClasses(ctx context.Context, files []string) ([]ClassInfo, error) { var classes []ClassInfo for _, file := range files { classes = append(classes, ClassInfo{ Name: "TestClass", File: file, }) } return classes, nil } // FileFinderMock implements the FileFinder interface for testing type FileFinderMock struct { files []string } func (m *FileFinderMock) FindFiles(path string, language string) ([]string, error) { return m.files, nil } func (m *FileFinderMock) IsSourceFile(path string, language string) bool { return true } func TestNewBaseDetector(t *testing.T) { finder := &FileFinderMock{files: []string{"test.go"}} detector := NewBaseDetector("test-detector", SeverityT2, finder) if detector == nil { t.Error("NewBaseDetector() should not return nil") } if detector.name != "test-detector" { t.Errorf("NewBaseDetector() name = %v, want test-detector", detector.name) } if detector.severity != SeverityT2 { t.Errorf("NewBaseDetector() severity = %v, want T2", detector.severity) } if detector.finder != finder { t.Error("NewBaseDetector() finder not set correctly") } } func TestBaseDetector_Name(t *testing.T) { detector := NewBaseDetector("test-name", SeverityT1, nil) if detector.Name() != "test-name" { t.Errorf("Name() = %v, want test-name", detector.Name()) } } func TestBaseDetector_Severity(t *testing.T) { tests := []struct { name string severity Severity }{ {"T1 severity", SeverityT1}, {"T2 severity", SeverityT2}, {"T3 severity", SeverityT3}, {"T4 severity", SeverityT4}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { detector := NewBaseDetector("test", tt.severity, nil) if detector.Severity() != tt.severity { t.Errorf("Severity() = %v, want %v", detector.Severity(), tt.severity) } }) } } func TestBaseDetector_FindFiles(t *testing.T) { tests := []struct { name string finder FileFinder expected []string }{ { name: "with finder", finder: &FileFinderMock{files: []string{"file1.go", "file2.go"}}, expected: []string{"file1.go", "file2.go"}, }, { name: "without finder", finder: nil, expected: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { detector := NewBaseDetector("test", SeverityT1, tt.finder) files, err := detector.FindFiles("/test/path", "go") if err != nil { t.Errorf("FindFiles() unexpected error: %v", err) } if len(files) != len(tt.expected) { t.Errorf("FindFiles() expected %d files, got %d", len(tt.expected), len(files)) } for i, file := range files { if i < len(tt.expected) && file != tt.expected[i] { t.Errorf("FindFiles() file %d = %v, want %v", i, file, tt.expected[i]) } } }) } } func TestShouldExclude(t *testing.T) { tests := []struct { name string path string excludes []string expected bool }{ {"no excludes", "test.go", []string{}, false}, {"empty excludes", "test.go", []string{""}, false}, {"exact match", "test.go", []string{"test.go"}, true}, {"pattern match", "test_*.go", []string{"test_*.go"}, true}, {"no match", "other.go", []string{"test.go"}, false}, { name: "directory match", path: "vendor/lib.go", excludes: []string{"vendor"}, expected: false, }, // filepath.Match doesn't match directories this way {"base directory match", "lib.go", []string{"lib.go"}, true}, {"multiple patterns", "test.go", []string{"*.py", "test.go"}, true}, {"invalid pattern", "test.go", []string{"[invalid"}, false}, {"complex pattern", "internal/test/file.go", []string{"internal/*/file.go"}, true}, {"case sensitive", "Test.go", []string{"test.go"}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := ShouldExclude(tt.path, tt.excludes) if result != tt.expected { t.Errorf("ShouldExclude(%s, %v) = %v, want %v", tt.path, tt.excludes, result, tt.expected) } }) } } func TestShouldExclude_EdgeCases(t *testing.T) { tests := []struct { name string path string excludes []string expected bool }{ {"empty path", "", []string{"*"}, true}, {"empty pattern", "test.go", []string{""}, false}, {"star pattern", "any_file.go", []string{"*"}, true}, { name: "question mark", path: "file.go", excludes: []string{"file.?"}, expected: false}, // filepath.Match doesn't support ? this way { name: "character class", path: "file.go", excludes: []string{"file.[go]"}, expected: false}, // filepath.Match doesn't support character classes {"nested pattern", "a/b/c/file.go", []string{"a/*/c/file.go"}, true}, {"absolute path", "/absolute/path/file.go", []string{"*.go"}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := ShouldExclude(tt.path, tt.excludes) if result != tt.expected { t.Errorf("ShouldExclude(%s, %v) = %v, want %v", tt.path, tt.excludes, result, tt.expected) } }) } } func TestMockDetector_Interface(t *testing.T) { // Verify that DetectorMock implements Detector interface var _ Detector = &DetectorMock{name: "test", severity: SeverityT1} detector := &DetectorMock{name: "test-detector", severity: SeverityT2} ctx := context.Background() findings, err := detector.Detect(ctx, "/test/path", &Config{}) if err != nil { t.Errorf("MockDetector.Detect() unexpected error: %v", err) } if len(findings) != 1 { t.Errorf("DetectorMock.Detect() expected 1 finding, got %d", len(findings)) } if findings[0].Type != "mock_finding" { t.Errorf("DetectorMock.Detect() finding type = %v, want mock_finding", findings[0].Type) } if findings[0].Severity != SeverityT2 { t.Errorf("DetectorMock.Detect() finding severity = %v, want T2", findings[0].Severity) } } func TestLanguageDetectorMock_Interface(t *testing.T) { // Verify that LanguageDetectorMock implements LanguageDetector interface var _ LanguageDetector = &LanguageDetectorMock{ DetectorMock: &DetectorMock{name: "test", severity: SeverityT1}, supportedLanguages: []string{"go", "python"}, } detector := &LanguageDetectorMock{ DetectorMock: &DetectorMock{name: "test-lang", severity: SeverityT3}, supportedLanguages: []string{"go", "python", "javascript"}, } if len(detector.SupportedLanguages()) != 3 { t.Errorf("LanguageDetectorMock.SupportedLanguages() expected 3 languages, got %d", len(detector.SupportedLanguages())) } ctx := context.Background() files := []string{"file1.go", "file2.py"} functions, err := detector.ExtractFunctions(ctx, files) if err != nil { t.Errorf("LanguageDetectorMock.ExtractFunctions() unexpected error: %v", err) } if len(functions) != 2 { t.Errorf("LanguageDetectorMock.ExtractFunctions() expected 2 functions, got %d", len(functions)) } classes, err := detector.ExtractClasses(ctx, files) if err != nil { t.Errorf("LanguageDetectorMock.ExtractClasses() unexpected error: %v", err) } if len(classes) != 2 { t.Errorf("LanguageDetectorMock.ExtractClasses() expected 2 classes, got %d", len(classes)) } } func TestMockFileFinder_Interface(t *testing.T) { // Verify that FileFinderMock implements FileFinder interface var _ FileFinder = &FileFinderMock{files: []string{"test.go"}} finder := &FileFinderMock{files: []string{"file1.go", "file2.go"}} files, err := finder.FindFiles("/test/path", "go") if err != nil { t.Errorf("FileFinderMock.FindFiles() unexpected error: %v", err) } if len(files) != 2 { t.Errorf("FileFinderMock.FindFiles() expected 2 files, got %d", len(files)) } if !finder.IsSourceFile("test.go", "go") { t.Error("FileFinderMock.IsSourceFile() should return true") } } func TestBaseDetector_Integration(t *testing.T) { // Test BaseDetector with real mock implementations finder := &FileFinderMock{files: []string{"main.go", "utils.go"}} detector := NewBaseDetector("integration-test", SeverityT2, finder) // Test all methods if detector.Name() != "integration-test" { t.Errorf("Integration test: Name() = %v, want integration-test", detector.Name()) } if detector.Severity() != SeverityT2 { t.Errorf("Integration test: Severity() = %v, want T2", detector.Severity()) } files, err := detector.FindFiles("/project", "go") if err != nil { t.Errorf("Integration test: FindFiles() unexpected error: %v", err) } if len(files) != 2 { t.Errorf("Integration test: FindFiles() expected 2 files, got %d", len(files)) } }