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

194 lines
5.9 KiB
Go

package review
import (
"path/filepath"
"testing"
"github.com/zzet/gortex/internal/config"
"github.com/zzet/gortex/internal/persistence"
)
func openTempSuppressionStore(t *testing.T) *SuppressionStore {
t.Helper()
path := filepath.Join(t.TempDir(), "sidecar.sqlite")
sidecar, err := persistence.OpenSidecar(path)
if err != nil || sidecar == nil {
t.Fatalf("open sidecar: %v", err)
}
t.Cleanup(func() { _ = sidecar.Close() })
return NewSuppressionStore(sidecar)
}
func suppFinding() Finding {
return Finding{
Rule: "nil-deref",
Category: "nil-deref",
File: "pkg/x.go",
SymbolID: "pkg/x.go::Foo",
Line: 14,
SourceLine: "result := obj.Field()",
Severity: SevError,
Message: "possible nil dereference",
}
}
// TestSuppressionStoreRoundTrip proves Suppress → IsSuppressed → List →
// Unsuppress round-trips through the sidecar.
func TestSuppressionStoreRoundTrip(t *testing.T) {
store := openTempSuppressionStore(t)
f := suppFinding()
key := IdentityKey(f)
// Not suppressed initially.
if store.IsSuppressed("rk", key) {
t.Fatal("finding must not be suppressed before Suppress")
}
// Suppress, then IsSuppressed is true.
if err := store.Suppress("rk", f, "false positive: guarded above", "me@zzet.org"); err != nil {
t.Fatalf("suppress: %v", err)
}
if !store.IsSuppressed("rk", key) {
t.Fatal("finding must be suppressed after Suppress")
}
// Scope isolation: another repo key is unaffected.
if store.IsSuppressed("other", key) {
t.Fatal("suppression must be per-repo")
}
// List returns the row with its context + a non-zero hit count (the two
// IsSuppressed calls above bumped it).
rows, err := store.List("rk")
if err != nil {
t.Fatalf("list: %v", err)
}
if len(rows) != 1 {
t.Fatalf("want 1 suppression, got %d", len(rows))
}
r := rows[0]
if r.IdentityKey != key || r.Rule != f.Rule || r.SymbolID != f.SymbolID {
t.Fatalf("list row context wrong: %+v", r)
}
if r.Reason != "false positive: guarded above" || r.Author != "me@zzet.org" {
t.Fatalf("list row reason/author wrong: %+v", r)
}
if r.HitCount < 1 {
t.Fatalf("IsSuppressed must bump hit_count, got %d", r.HitCount)
}
// Unsuppress, then IsSuppressed is false again.
if err := store.Unsuppress("rk", key); err != nil {
t.Fatalf("unsuppress: %v", err)
}
if store.IsSuppressed("rk", key) {
t.Fatal("finding must not be suppressed after Unsuppress")
}
}
// TestSuppressionSurvivesLineDrift proves a suppression keyed off identity stays
// in effect after the finding moves to a different line (same trimmed source).
func TestSuppressionSurvivesLineDrift(t *testing.T) {
store := openTempSuppressionStore(t)
at10 := suppFinding()
at10.Line = 10
if err := store.Suppress("rk", at10, "", ""); err != nil {
t.Fatalf("suppress: %v", err)
}
at40 := suppFinding()
at40.Line = 40 // same code, shifted down
if !store.IsSuppressed("rk", IdentityKey(at40)) {
t.Fatal("suppression must survive line drift")
}
}
// TestNilSuppressionStoreTolerated proves a nil store and a store over a nil
// sidecar are no-ops: IsSuppressed false, mutators succeed, List empty.
func TestNilSuppressionStoreTolerated(t *testing.T) {
for name, store := range map[string]*SuppressionStore{
"nil store": nil,
"nil sidecar": NewSuppressionStore(nil),
} {
f := suppFinding()
if store.IsSuppressed("rk", IdentityKey(f)) {
t.Fatalf("%s: IsSuppressed must be false", name)
}
if err := store.Suppress("rk", f, "", ""); err != nil {
t.Fatalf("%s: Suppress must be a no-op, got %v", name, err)
}
if err := store.Unsuppress("rk", IdentityKey(f)); err != nil {
t.Fatalf("%s: Unsuppress must be a no-op, got %v", name, err)
}
rows, err := store.List("rk")
if err != nil {
t.Fatalf("%s: List must not error, got %v", name, err)
}
if len(rows) != 0 {
t.Fatalf("%s: List must be empty, got %d", name, len(rows))
}
}
}
// TestGateDropsSuppressedFinding proves the gate, wired with a suppression
// store, drops a suppressed finding and counts it under IdentitySuppressed while
// keeping the rest — and that the count rolls into GateStats.Suppressed().
func TestGateDropsSuppressedFinding(t *testing.T) {
store := openTempSuppressionStore(t)
bad := suppFinding() // the one we will suppress
good := Finding{
Rule: "check-then-act",
Category: "concurrency",
File: "pkg/y.go",
SymbolID: "pkg/y.go::Bar",
Line: 7,
SourceLine: "if !ok { return }",
Severity: SevWarning,
Message: "check then act",
}
if err := store.Suppress("rk", bad, "known FP", "tester"); err != nil {
t.Fatalf("suppress: %v", err)
}
in := []Finding{bad, good}
kept, stats := NewGate(config.ReviewConfig{}).
WithSuppression(store, "rk").
Apply(in)
if len(kept) != 1 {
t.Fatalf("want 1 kept, got %d: %+v", len(kept), kept)
}
if kept[0].SymbolID != good.SymbolID {
t.Fatalf("wrong finding kept: %+v", kept[0])
}
if stats.IdentitySuppressed != 1 {
t.Fatalf("want IdentitySuppressed=1, got %d (%+v)", stats.IdentitySuppressed, stats)
}
if stats.Suppressed() != 1 {
t.Fatalf("want Suppressed()=1, got %d", stats.Suppressed())
}
// The kept finding carries a stamped identity key (so a later suppress can
// reference it without re-deriving).
if kept[0].IdentityKey == "" {
t.Fatal("gate must stamp IdentityKey on kept findings")
}
// A different repo key is unaffected: nothing is suppressed.
kept2, stats2 := NewGate(config.ReviewConfig{}).
WithSuppression(store, "other-repo").
Apply(in)
if len(kept2) != 2 || stats2.IdentitySuppressed != 0 {
t.Fatalf("suppression must be per-repo: kept %d, suppressed %d", len(kept2), stats2.IdentitySuppressed)
}
// A nil store leaves everything (suppression is purely additive).
kept3, stats3 := NewGate(config.ReviewConfig{}).
WithSuppression(nil, "rk").
Apply(in)
if len(kept3) != 2 || stats3.IdentitySuppressed != 0 {
t.Fatalf("nil suppression store must keep everything: kept %d, suppressed %d", len(kept3), stats3.IdentitySuppressed)
}
}