lint/pkg/lint/scanner_test.go
Snider 6806b20651 fix(dx): fix build error, replace os.* with go-io, add scanner tests
- Fix RegisterCommands call to match single-arg signature; register
  locales separately via i18n.RegisterLocales
- Replace os.Stat/os.ReadDir with coreio.Local equivalents in
  detect.go, format.go, main.go, cmd_docblock.go
- Add tests for languagesFromRules, IsExcludedDir, ScanFile edge cases
- Coverage: pkg/lint 71.0% → 72.9%, pkg/detect 100%, pkg/php 68.7%

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-17 09:02:06 +00:00

313 lines
7.3 KiB
Go

package lint
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDetectLanguage_Good(t *testing.T) {
tests := []struct {
filename string
want string
}{
{"main.go", "go"},
{"handler.go", "go"},
{"model.php", "php"},
{"app.ts", "ts"},
{"component.tsx", "ts"},
{"main.cpp", "cpp"},
{"lib.cc", "cpp"},
{"header.h", "cpp"},
{"core.c", "cpp"},
{"app.js", "js"},
{"component.jsx", "js"},
{"unknown.rs", ""},
{"noextension", ""},
{"file.py", "py"},
}
for _, tt := range tests {
t.Run(tt.filename, func(t *testing.T) {
got := DetectLanguage(tt.filename)
assert.Equal(t, tt.want, got)
})
}
}
func TestScanDir_Good_FindsMatches(t *testing.T) {
dir := t.TempDir()
// Create a Go file with a TODO.
goFile := filepath.Join(dir, "main.go")
err := os.WriteFile(goFile, []byte("package main\n\n// TODO: fix this\nfunc main() {}\n"), 0o644)
require.NoError(t, err)
rules := []Rule{
{
ID: "test-001",
Title: "Found a TODO",
Severity: "low",
Languages: []string{"go"},
Pattern: `TODO`,
Fix: "Remove TODO",
Detection: "regex",
},
}
s, err := NewScanner(rules)
require.NoError(t, err)
findings, err := s.ScanDir(dir)
require.NoError(t, err)
require.Len(t, findings, 1)
assert.Equal(t, "test-001", findings[0].RuleID)
assert.Equal(t, 3, findings[0].Line)
}
func TestScanDir_Good_ExcludesVendor(t *testing.T) {
dir := t.TempDir()
// Create vendor directory with a matching file.
vendorDir := filepath.Join(dir, "vendor")
require.NoError(t, os.MkdirAll(vendorDir, 0o755))
err := os.WriteFile(filepath.Join(vendorDir, "lib.go"), []byte("// TODO: vendor code\n"), 0o644)
require.NoError(t, err)
// Create node_modules directory with a matching file.
nodeDir := filepath.Join(dir, "node_modules")
require.NoError(t, os.MkdirAll(nodeDir, 0o755))
err = os.WriteFile(filepath.Join(nodeDir, "index.js"), []byte("// TODO: node code\n"), 0o644)
require.NoError(t, err)
// Create .git directory with a matching file.
gitDir := filepath.Join(dir, ".git")
require.NoError(t, os.MkdirAll(gitDir, 0o755))
err = os.WriteFile(filepath.Join(gitDir, "config"), []byte("// TODO: git\n"), 0o644)
require.NoError(t, err)
// Create testdata directory with a matching file.
testdataDir := filepath.Join(dir, "testdata")
require.NoError(t, os.MkdirAll(testdataDir, 0o755))
err = os.WriteFile(filepath.Join(testdataDir, "sample.go"), []byte("// TODO: testdata\n"), 0o644)
require.NoError(t, err)
// Create .core directory with a matching file.
coreDir := filepath.Join(dir, ".core")
require.NoError(t, os.MkdirAll(coreDir, 0o755))
err = os.WriteFile(filepath.Join(coreDir, "build.go"), []byte("// TODO: build\n"), 0o644)
require.NoError(t, err)
rules := []Rule{
{
ID: "test-001",
Title: "Found a TODO",
Severity: "low",
Languages: []string{"go", "js"},
Pattern: `TODO`,
Fix: "Remove TODO",
Detection: "regex",
},
}
s, err := NewScanner(rules)
require.NoError(t, err)
findings, err := s.ScanDir(dir)
require.NoError(t, err)
assert.Empty(t, findings, "should not find matches in excluded directories")
}
func TestScanDir_Good_LanguageFiltering(t *testing.T) {
dir := t.TempDir()
// Create Go file with a match.
err := os.WriteFile(filepath.Join(dir, "main.go"), []byte("// TODO: go\n"), 0o644)
require.NoError(t, err)
// Create PHP file with a match — rule only targets Go.
err = os.WriteFile(filepath.Join(dir, "index.php"), []byte("// TODO: php\n"), 0o644)
require.NoError(t, err)
rules := []Rule{
{
ID: "go-only",
Title: "Go TODO",
Severity: "low",
Languages: []string{"go"},
Pattern: `TODO`,
Fix: "Remove TODO",
Detection: "regex",
},
}
s, err := NewScanner(rules)
require.NoError(t, err)
findings, err := s.ScanDir(dir)
require.NoError(t, err)
require.Len(t, findings, 1)
assert.Contains(t, findings[0].File, "main.go")
}
func TestScanFile_Good(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "test.go")
err := os.WriteFile(file, []byte("package main\n\npanic(\"boom\")\n"), 0o644)
require.NoError(t, err)
rules := []Rule{
{
ID: "test-panic",
Title: "Panic found",
Severity: "high",
Languages: []string{"go"},
Pattern: `\bpanic\(`,
Fix: "Return error",
Detection: "regex",
},
}
s, err := NewScanner(rules)
require.NoError(t, err)
findings, err := s.ScanFile(file)
require.NoError(t, err)
require.Len(t, findings, 1)
assert.Equal(t, "test-panic", findings[0].RuleID)
}
func TestScanDir_Good_Subdirectories(t *testing.T) {
dir := t.TempDir()
// Create a nested file.
subDir := filepath.Join(dir, "pkg", "store")
require.NoError(t, os.MkdirAll(subDir, 0o755))
err := os.WriteFile(filepath.Join(subDir, "db.go"), []byte("// TODO: deep\n"), 0o644)
require.NoError(t, err)
rules := []Rule{
{
ID: "test-001",
Title: "Found a TODO",
Severity: "low",
Languages: []string{"go"},
Pattern: `TODO`,
Fix: "Remove TODO",
Detection: "regex",
},
}
s, err := NewScanner(rules)
require.NoError(t, err)
findings, err := s.ScanDir(dir)
require.NoError(t, err)
require.Len(t, findings, 1)
}
func TestLanguagesFromRules_Good(t *testing.T) {
rules := []Rule{
{Languages: []string{"go", "php"}},
{Languages: []string{"go", "ts"}},
{Languages: []string{"py"}},
}
langs := languagesFromRules(rules)
assert.Equal(t, []string{"go", "php", "py", "ts"}, langs)
}
func TestLanguagesFromRules_Good_Empty(t *testing.T) {
langs := languagesFromRules(nil)
assert.Empty(t, langs)
}
func TestIsExcludedDir_Good(t *testing.T) {
tests := []struct {
name string
want bool
}{
{"vendor", true},
{"node_modules", true},
{".git", true},
{"testdata", true},
{".core", true},
{".hidden", true}, // any dot-prefixed dir
{".idea", true}, // any dot-prefixed dir
{"src", false},
{"pkg", false},
{"cmd", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, IsExcludedDir(tt.name))
})
}
}
func TestScanFile_Bad_UnrecognisedExtension(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "readme.txt")
require.NoError(t, os.WriteFile(file, []byte("TODO: fix this"), 0o644))
rules := []Rule{
{
ID: "test-001",
Title: "Found TODO",
Severity: "low",
Languages: []string{"go"},
Pattern: `TODO`,
Fix: "Remove TODO",
Detection: "regex",
},
}
s, err := NewScanner(rules)
require.NoError(t, err)
findings, err := s.ScanFile(file)
require.NoError(t, err)
assert.Empty(t, findings, "should not match unrecognised extensions")
}
func TestScanFile_Bad_NonexistentFile(t *testing.T) {
rules := []Rule{
{
ID: "test-001",
Title: "Test",
Severity: "low",
Languages: []string{"go"},
Pattern: `TODO`,
Fix: "Fix",
Detection: "regex",
},
}
s, err := NewScanner(rules)
require.NoError(t, err)
_, err = s.ScanFile("/nonexistent/test.go")
assert.Error(t, err)
}
func TestScanDir_Bad_NonexistentDir(t *testing.T) {
rules := []Rule{
{
ID: "test-001",
Title: "Test",
Severity: "low",
Languages: []string{"go"},
Pattern: `TODO`,
Fix: "Fix",
Detection: "regex",
},
}
s, err := NewScanner(rules)
require.NoError(t, err)
_, err = s.ScanDir("/nonexistent/path")
assert.Error(t, err)
}