Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

142 lines
4.4 KiB
Go

package astquery
import (
"sort"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestSASTCatalog_HitsSpecTarget asserts the rationale from the
// N54 gap-analysis row: SAST rule library reaches 190+ rules across
// the supported languages. Failing this test means new rules either
// regressed in count or lost their Category tag.
func TestSASTCatalog_HitsSpecTarget(t *testing.T) {
all := DescribeDetectors()
require.NotEmpty(t, all)
var sastCount, hygieneCount, totalCategorised int
for _, d := range all {
switch d.Category {
case CategorySAST:
sastCount++
totalCategorised++
case CategoryHygiene:
hygieneCount++
totalCategorised++
}
}
t.Logf("total detectors=%d sast=%d hygiene=%d uncategorised=%d",
len(all), sastCount, hygieneCount, len(all)-totalCategorised)
require.GreaterOrEqual(t, sastCount+hygieneCount, 175,
"sast + hygiene rules should be ≥175 (N54 target was 190+ across all categories including the 16 legacy uncategorised); got sast=%d hygiene=%d", sastCount, hygieneCount)
require.GreaterOrEqual(t, sastCount, 150,
"sast-categorised rules should be ≥150 to maintain Bandit parity")
}
// TestSASTCatalog_AllRulesHaveCWE confirms every sast-categorised rule
// carries a CWE identifier. Pure-hygiene rules don't (intentional —
// they're not security findings).
func TestSASTCatalog_AllRulesHaveCWE(t *testing.T) {
missing := []string{}
for _, d := range DescribeDetectors() {
if d.Category != CategorySAST {
continue
}
if strings.TrimSpace(d.CWE) == "" {
missing = append(missing, d.Name)
}
}
sort.Strings(missing)
assert.Empty(t, missing, "every SAST rule must carry a CWE id; missing on: %v", missing)
}
// TestSASTCatalog_LanguageCoverage spot-checks that each tracked
// language has at least the floor count of rules the gap-analysis
// row commits to.
func TestSASTCatalog_LanguageCoverage(t *testing.T) {
want := map[string]int{
"python": 90,
"go": 15,
"javascript": 10,
"typescript": 10,
"java": 8,
"ruby": 8,
"php": 8,
"rust": 4,
}
counts := map[string]int{}
for _, d := range DescribeDetectors() {
if d.Category != CategorySAST && d.Category != CategoryHygiene {
continue
}
for _, lang := range d.Languages {
counts[lang]++
}
}
t.Logf("per-language counts: %+v", counts)
for lang, floor := range want {
assert.GreaterOrEqual(t, counts[lang], floor,
"language %q has %d rules; floor is %d", lang, counts[lang], floor)
}
}
// TestSASTCatalog_NoDuplicateNames keeps the registry honest — a
// duplicate Name silently overwrites the older entry via the
// RegisterDetector map.
func TestSASTCatalog_NoDuplicateNames(t *testing.T) {
seen := make(map[string]int)
for _, d := range DescribeDetectors() {
seen[d.Name]++
}
for name, n := range seen {
require.Equal(t, 1, n, "detector name %q registered %d times", name, n)
}
}
// TestSASTCatalog_AllSeveritiesValid enforces the small severity
// taxonomy so downstream consumers (SARIF / DefectDojo / GitHub
// Code Scanning) don't see surprise labels.
func TestSASTCatalog_AllSeveritiesValid(t *testing.T) {
allowed := map[string]struct{}{"error": {}, "warning": {}, "info": {}}
for _, d := range DescribeDetectors() {
_, ok := allowed[strings.ToLower(d.Severity)]
assert.True(t, ok, "rule %q has invalid severity %q", d.Name, d.Severity)
}
}
// TestDetectorsByCategory exercises the public lookup the analyze
// dispatcher relies on. A simple round-trip: every Category we filter
// for must come back, and an unknown category returns 0.
func TestDetectorsByCategory(t *testing.T) {
sast := DetectorsByCategory(CategorySAST)
require.NotEmpty(t, sast)
for _, d := range sast {
require.Equal(t, CategorySAST, d.Category)
}
hyg := DetectorsByCategory(CategoryHygiene)
require.NotEmpty(t, hyg)
for _, d := range hyg {
require.Equal(t, CategoryHygiene, d.Category)
}
none := DetectorsByCategory("does-not-exist")
require.Empty(t, none)
all := DetectorsByCategory()
require.GreaterOrEqual(t, len(all), len(sast)+len(hyg))
}
// TestLookupDetector returns nil for unknown names and a *Detector
// for known ones — the contract analyze handlers rely on.
func TestLookupDetector(t *testing.T) {
require.NotNil(t, LookupDetector("py-eval-use"))
require.NotNil(t, LookupDetector("go-tls-insecure-skip-verify"))
require.NotNil(t, LookupDetector("js-eval-use"))
require.Nil(t, LookupDetector("xyzzy-does-not-exist"))
}