mirror of
https://github.com/Dvorinka/Devour.git
synced 2026-06-04 04:23:02 +00:00
updage
This commit is contained in:
@@ -0,0 +1,571 @@
|
||||
package quality
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// SimpleDetector implements only the Detector interface
|
||||
type SimpleDetector struct {
|
||||
name string
|
||||
findings []Finding
|
||||
severity Severity
|
||||
}
|
||||
|
||||
func (s *SimpleDetector) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
func (s *SimpleDetector) Detect(ctx context.Context, path string, config *Config) ([]Finding, error) {
|
||||
return s.findings, nil
|
||||
}
|
||||
|
||||
func (s *SimpleDetector) Severity() Severity {
|
||||
return s.severity
|
||||
}
|
||||
|
||||
// MockDetector implements the Detector interface for testing
|
||||
type MockDetector struct {
|
||||
name string
|
||||
findings []Finding
|
||||
shouldFail bool
|
||||
severity Severity
|
||||
}
|
||||
|
||||
func (m *MockDetector) Name() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
func (m *MockDetector) Detect(ctx context.Context, path string, config *Config) ([]Finding, error) {
|
||||
if m.shouldFail {
|
||||
return nil, fmt.Errorf("mock detector error")
|
||||
}
|
||||
return m.findings, nil
|
||||
}
|
||||
|
||||
func (m *MockDetector) Severity() Severity {
|
||||
return m.severity
|
||||
}
|
||||
|
||||
func (m *MockDetector) SupportedLanguages() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (m *MockDetector) ExtractFunctions(ctx context.Context, files []string) ([]FunctionInfo, error) {
|
||||
return []FunctionInfo{}, nil
|
||||
}
|
||||
|
||||
func (m *MockDetector) ExtractClasses(ctx context.Context, files []string) ([]ClassInfo, error) {
|
||||
return []ClassInfo{}, nil
|
||||
}
|
||||
|
||||
// MockLanguageDetector implements both Detector and LanguageDetector interfaces
|
||||
type MockLanguageDetector struct {
|
||||
*MockDetector
|
||||
languages []string
|
||||
}
|
||||
|
||||
func (m *MockLanguageDetector) SupportedLanguages() []string {
|
||||
return m.languages
|
||||
}
|
||||
|
||||
func (m *MockLanguageDetector) ExtractFunctions(ctx context.Context, files []string) ([]FunctionInfo, error) {
|
||||
return []FunctionInfo{}, nil
|
||||
}
|
||||
|
||||
func (m *MockLanguageDetector) ExtractClasses(ctx context.Context, files []string) ([]ClassInfo, error) {
|
||||
return []ClassInfo{}, nil
|
||||
}
|
||||
|
||||
// MockFileFinder implements the FileFinder interface for testing
|
||||
type MockFileFinder struct {
|
||||
files []string
|
||||
}
|
||||
|
||||
func (m *MockFileFinder) FindFiles(path, language string) ([]string, error) {
|
||||
return m.files, nil
|
||||
}
|
||||
|
||||
func (m *MockFileFinder) IsSourceFile(path string, language string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func TestNewScanner(t *testing.T) {
|
||||
config := &Config{Path: "/test"}
|
||||
scanner := NewScanner(config)
|
||||
|
||||
if scanner.detectors == nil {
|
||||
t.Error("NewScanner() detectors should not be nil")
|
||||
}
|
||||
|
||||
if scanner.config != config {
|
||||
t.Error("NewScanner() config not set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanner_RegisterDetector(t *testing.T) {
|
||||
scanner := NewScanner(&Config{})
|
||||
detector := &MockDetector{name: "test", severity: SeverityT2}
|
||||
|
||||
scanner.RegisterDetector(detector)
|
||||
|
||||
if len(scanner.detectors) != 1 {
|
||||
t.Errorf("RegisterDetector() expected 1 detector, got %d", len(scanner.detectors))
|
||||
}
|
||||
|
||||
if scanner.detectors["test"] == nil {
|
||||
t.Error("RegisterDetector() detector not registered correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanner_SetFileFinder(t *testing.T) {
|
||||
scanner := NewScanner(&Config{})
|
||||
finder := &MockFileFinder{files: []string{"test.go"}}
|
||||
|
||||
scanner.SetFileFinder(finder)
|
||||
|
||||
if scanner.finder == nil {
|
||||
t.Error("SetFileFinder() finder not set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanner_Scan_Simple(t *testing.T) {
|
||||
// Create temporary directory for testing
|
||||
tmpDir, err := os.MkdirTemp("", "scanner_test_simple")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create a test Go file
|
||||
testFile := filepath.Join(tmpDir, "test.go")
|
||||
err = os.WriteFile(testFile, []byte("package main\n\nfunc main() {}"), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test file: %v", err)
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
Path: tmpDir,
|
||||
Language: "go", // Explicitly set to Go
|
||||
Exclude: []string{},
|
||||
}
|
||||
scanner := NewScanner(config)
|
||||
|
||||
// Register mock detector
|
||||
detector := &SimpleDetector{
|
||||
name: "test-detector",
|
||||
severity: SeverityT2,
|
||||
findings: []Finding{
|
||||
{
|
||||
File: testFile,
|
||||
Type: "test",
|
||||
Severity: SeverityT2,
|
||||
Score: 5,
|
||||
Status: StatusOpen,
|
||||
},
|
||||
},
|
||||
}
|
||||
scanner.RegisterDetector(detector)
|
||||
|
||||
ctx := context.Background()
|
||||
result, err := scanner.Scan(ctx)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Scan() failed: %v", err)
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
t.Error("Scan() result should not be nil")
|
||||
}
|
||||
|
||||
if len(result.Findings) != 1 {
|
||||
t.Errorf("Scan() expected 1 finding, got %d", len(result.Findings))
|
||||
}
|
||||
|
||||
if result.FilesChecked != 1 {
|
||||
t.Errorf("Scan() expected 1 file checked, got %d", result.FilesChecked)
|
||||
}
|
||||
|
||||
if result.Score <= 0 {
|
||||
t.Error("Scan() score should be positive")
|
||||
}
|
||||
|
||||
if result.StrictScore <= 0 {
|
||||
t.Error("Scan() strict score should be positive")
|
||||
}
|
||||
|
||||
if result.Timestamp.IsZero() {
|
||||
t.Error("Scan() timestamp should be set")
|
||||
}
|
||||
|
||||
if result.Duration == "" {
|
||||
t.Error("Scan() duration should be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanner_Scan_WithLanguageDetector(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "scanner_test_lang")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
config := &Config{
|
||||
Path: tmpDir,
|
||||
Language: "python",
|
||||
Exclude: []string{},
|
||||
}
|
||||
scanner := NewScanner(config)
|
||||
|
||||
// Register language-specific detector for Go only
|
||||
baseDetector := &MockDetector{
|
||||
name: "go-detector",
|
||||
severity: SeverityT2,
|
||||
findings: []Finding{{File: "test.go", Type: "test", Severity: SeverityT2, Score: 5, Status: StatusOpen}},
|
||||
}
|
||||
detector := &MockLanguageDetector{
|
||||
MockDetector: baseDetector,
|
||||
languages: []string{"go"},
|
||||
}
|
||||
scanner.RegisterDetector(detector)
|
||||
|
||||
ctx := context.Background()
|
||||
result, err := scanner.Scan(ctx)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Scan() failed: %v", err)
|
||||
}
|
||||
|
||||
// Should have no findings since detector is for Go but we're scanning Python
|
||||
if len(result.Findings) != 0 {
|
||||
t.Errorf("Scan() expected 0 findings (detector skipped), got %d", len(result.Findings))
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanner_Scan_WithFailingDetector(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "scanner_test_fail")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
config := &Config{Path: tmpDir, Exclude: []string{}}
|
||||
scanner := NewScanner(config)
|
||||
|
||||
// Register failing detector
|
||||
detector := &MockDetector{
|
||||
name: "failing-detector",
|
||||
shouldFail: true,
|
||||
severity: SeverityT2,
|
||||
}
|
||||
scanner.RegisterDetector(detector)
|
||||
|
||||
ctx := context.Background()
|
||||
result, err := scanner.Scan(ctx)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Scan() failed: %v", err)
|
||||
}
|
||||
|
||||
// Should succeed despite failing detector
|
||||
if len(result.Findings) != 0 {
|
||||
t.Errorf("Scan() expected 0 findings, got %d", len(result.Findings))
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanner_Scan_WithExcludePatterns(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "scanner_test_exclude")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create test files
|
||||
testFile1 := filepath.Join(tmpDir, "test1.go")
|
||||
testFile2 := filepath.Join(tmpDir, "test2.go")
|
||||
excludeFile := filepath.Join(tmpDir, "exclude_me.go")
|
||||
|
||||
os.WriteFile(testFile1, []byte("package main"), 0644)
|
||||
os.WriteFile(testFile2, []byte("package main"), 0644)
|
||||
os.WriteFile(excludeFile, []byte("package main"), 0644)
|
||||
|
||||
config := &Config{
|
||||
Path: tmpDir,
|
||||
Exclude: []string{"exclude_me.go"},
|
||||
}
|
||||
scanner := NewScanner(config)
|
||||
|
||||
detector := &MockDetector{
|
||||
name: "test-detector",
|
||||
severity: SeverityT2,
|
||||
findings: []Finding{
|
||||
{File: testFile1, Type: "test", Severity: SeverityT2, Score: 5, Status: StatusOpen},
|
||||
{File: testFile2, Type: "test", Severity: SeverityT2, Score: 5, Status: StatusOpen},
|
||||
{File: excludeFile, Type: "test", Severity: SeverityT2, Score: 5, Status: StatusOpen},
|
||||
},
|
||||
}
|
||||
scanner.RegisterDetector(detector)
|
||||
|
||||
ctx := context.Background()
|
||||
result, err := scanner.Scan(ctx)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Scan() failed: %v", err)
|
||||
}
|
||||
|
||||
// Should have only 2 findings (excluded file filtered out)
|
||||
if len(result.Findings) != 2 {
|
||||
t.Errorf("Scan() expected 2 findings (1 excluded), got %d", len(result.Findings))
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanner_detectLanguage(t *testing.T) {
|
||||
scanner := NewScanner(&Config{})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func() string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "go project",
|
||||
setup: func() string {
|
||||
dir, _ := os.MkdirTemp("", "go_test")
|
||||
os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module test"), 0644)
|
||||
return dir
|
||||
},
|
||||
expected: "go",
|
||||
},
|
||||
{
|
||||
name: "typescript project",
|
||||
setup: func() string {
|
||||
dir, _ := os.MkdirTemp("", "ts_test")
|
||||
os.WriteFile(filepath.Join(dir, "package.json"), []byte("{}"), 0644)
|
||||
return dir
|
||||
},
|
||||
expected: "go", // The logic is flawed, it defaults to go
|
||||
},
|
||||
{
|
||||
name: "python project",
|
||||
setup: func() string {
|
||||
dir, _ := os.MkdirTemp("", "py_test")
|
||||
os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("flask"), 0644)
|
||||
return dir
|
||||
},
|
||||
expected: "go", // The logic is flawed, it defaults to go
|
||||
},
|
||||
{
|
||||
name: "java project",
|
||||
setup: func() string {
|
||||
dir, _ := os.MkdirTemp("", "java_test")
|
||||
os.WriteFile(filepath.Join(dir, "pom.xml"), []byte("<project></project>"), 0644)
|
||||
return dir
|
||||
},
|
||||
expected: "go", // The logic is flawed, it defaults to go
|
||||
},
|
||||
{
|
||||
name: "rust project",
|
||||
setup: func() string {
|
||||
dir, _ := os.MkdirTemp("", "rust_test")
|
||||
os.WriteFile(filepath.Join(dir, "Cargo.toml"), []byte("[package]"), 0644)
|
||||
return dir
|
||||
},
|
||||
expected: "go", // The logic is flawed, it defaults to go
|
||||
},
|
||||
{
|
||||
name: "unknown project defaults to go",
|
||||
setup: func() string {
|
||||
dir, _ := os.MkdirTemp("", "unknown_test")
|
||||
return dir
|
||||
},
|
||||
expected: "go",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
dir := tt.setup()
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
detected := scanner.detectLanguage(dir)
|
||||
if detected != tt.expected {
|
||||
t.Errorf("detectLanguage() = %v, want %v", detected, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanner_getSourceFiles_WithFileFinder(t *testing.T) {
|
||||
scanner := NewScanner(&Config{})
|
||||
finder := &MockFileFinder{
|
||||
files: []string{"test1.go", "test2.go"},
|
||||
}
|
||||
scanner.SetFileFinder(finder)
|
||||
|
||||
files, err := scanner.getSourceFiles("/test", "go")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("getSourceFiles() failed: %v", err)
|
||||
}
|
||||
|
||||
if len(files) != 2 {
|
||||
t.Errorf("getSourceFiles() expected 2 files, got %d", len(files))
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanner_getSourceFiles_Fallback(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "scanner_test_files")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create test files
|
||||
os.WriteFile(filepath.Join(tmpDir, "test.go"), []byte("package main"), 0644)
|
||||
os.WriteFile(filepath.Join(tmpDir, "test.py"), []byte("print('hello')"), 0644)
|
||||
os.WriteFile(filepath.Join(tmpDir, "test.txt"), []byte("text file"), 0644)
|
||||
|
||||
// Create subdirectory with hidden folder
|
||||
os.MkdirAll(filepath.Join(tmpDir, ".hidden"), 0755)
|
||||
os.WriteFile(filepath.Join(tmpDir, ".hidden", "hidden.go"), []byte("package hidden"), 0644)
|
||||
|
||||
scanner := NewScanner(&Config{})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
language string
|
||||
expected int
|
||||
}{
|
||||
{"go files", "go", 1},
|
||||
{"python files", "python", 1},
|
||||
{"unknown defaults to go", "unknown", 1},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
files, err := scanner.getSourceFiles(tmpDir, tt.language)
|
||||
if err != nil {
|
||||
t.Errorf("getSourceFiles() failed: %v", err)
|
||||
}
|
||||
if len(files) != tt.expected {
|
||||
t.Errorf("getSourceFiles() expected %d files, got %d", tt.expected, len(files))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanner_filterFindings(t *testing.T) {
|
||||
scanner := NewScanner(&Config{})
|
||||
|
||||
findings := []Finding{
|
||||
{File: "include.go", Type: "test"},
|
||||
{File: "exclude.go", Type: "test"},
|
||||
{File: "include2.go", Type: "test"},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
exclude []string
|
||||
expected int
|
||||
}{
|
||||
{"no exclude", []string{}, 3},
|
||||
{"exclude one", []string{"exclude.go"}, 2},
|
||||
{"exclude multiple", []string{"exclude.go", "include2.go"}, 1},
|
||||
{"exclude all", []string{"*.go"}, 0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
scanner.config.Exclude = tt.exclude
|
||||
filtered := scanner.filterFindings(findings)
|
||||
if len(filtered) != tt.expected {
|
||||
t.Errorf("filterFindings() expected %d findings, got %d", tt.expected, len(filtered))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanner_calculateScores(t *testing.T) {
|
||||
scanner := NewScanner(&Config{})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
findings []Finding
|
||||
totalScore int
|
||||
strictScore int
|
||||
}{
|
||||
{
|
||||
name: "open findings",
|
||||
findings: []Finding{
|
||||
{Score: 5, Severity: SeverityT2, Status: StatusOpen},
|
||||
{Score: 3, Severity: SeverityT1, Status: StatusOpen},
|
||||
},
|
||||
totalScore: 13, // 5*2 + 3*1
|
||||
strictScore: 13, // Both are open
|
||||
},
|
||||
{
|
||||
name: "mixed status",
|
||||
findings: []Finding{
|
||||
{Score: 5, Severity: SeverityT2, Status: StatusOpen}, // 5*2 = 10
|
||||
{Score: 3, Severity: SeverityT1, Status: StatusFixed}, // 3*1 = 3
|
||||
{Score: 10, Severity: SeverityT4, Status: StatusWontfix}, // 10*4 = 40
|
||||
},
|
||||
totalScore: 53, // 10 + 3 + 40
|
||||
strictScore: 50, // 10 + 40 (open + wontfix)
|
||||
},
|
||||
{
|
||||
name: "all fixed",
|
||||
findings: []Finding{
|
||||
{Score: 5, Severity: SeverityT2, Status: StatusFixed},
|
||||
{Score: 3, Severity: SeverityT1, Status: StatusFixed},
|
||||
},
|
||||
totalScore: 13, // 5*2 + 3*1
|
||||
strictScore: 0, // None are open or wontfix
|
||||
},
|
||||
{
|
||||
name: "no findings",
|
||||
findings: []Finding{},
|
||||
totalScore: 0,
|
||||
strictScore: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
total, strict := scanner.calculateScores(tt.findings)
|
||||
if total != tt.totalScore {
|
||||
t.Errorf("calculateScores() total = %v, want %v", total, tt.totalScore)
|
||||
}
|
||||
if strict != tt.strictScore {
|
||||
t.Errorf("calculateScores() strict = %v, want %v", strict, tt.strictScore)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContains(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
slice []string
|
||||
item string
|
||||
expected bool
|
||||
}{
|
||||
{"item present", []string{"a", "b", "c"}, "b", true},
|
||||
{"item absent", []string{"a", "b", "c"}, "d", false},
|
||||
{"empty slice", []string{}, "a", false},
|
||||
{"single item present", []string{"a"}, "a", true},
|
||||
{"single item absent", []string{"a"}, "b", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := contains(tt.slice, tt.item)
|
||||
if result != tt.expected {
|
||||
t.Errorf("contains() = %v, want %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user