Testing

How to run tests, write new tests, and understand the test structure.

Quick Start

# Run all tests
cd backend && go test ./... -count=1

# Run with verbose output
go test ./... -v -count=1

# Run tests for a specific package
go test ./internal/vex/ -v -count=1

# Run a single test by name
go test ./internal/vex/ -run TestNormalizeVulnID -v

# Run with race detection (used in CI)
go test ./... -count=1 -race

# Check coverage
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out

Test Structure

Tests live next to the code they test, using Go’s _test.go convention:

backend/internal/
├── config/
│   ├── config.go
│   └── config_test.go
├── github/
│   ├── purl.go
│   ├── purl_test.go
│   ├── resolver.go
│   └── resolver_test.go
├── license/
│   ├── checker.go
│   └── checker_test.go
├── osv/
│   ├── client.go
│   └── client_test.go
├── osvutil/
│   ├── osvutil.go
│   └── osvutil_test.go
├── repo/
│   ├── scanner.go
│   └── scanner_test.go
├── s3/
│   ├── client.go
│   └── client_test.go
├── spdx/
│   ├── parser.go
│   └── parser_test.go
└── vex/
    ├── parser.go
    └── parser_test.go

Current Test Inventory

PackageTestsSubtestsWhat’s Covered
config70Default values, env vars, S3 buckets JSON, shared credentials
github/purl222ExtractGitHubRepo (19 PURL patterns incl. well-known Go module mappings: golang.org/x/*, gopkg.in/*, go.uber.org/*, k8s.io/*, oras.land/*, dario.cat/*, go.yaml.in/*), RepoKey
github/resolver110Resolve (incl. well-known mapping for golang.org/x/crypto), cache, metadata, preload, license overrides
license2420Categorize, Check, policy, exceptions, prefix matching
osv60QueryBatch, errors, cancellation
osvutil535Severity, CVSS, fixed versions
repo50File scanning, SHA256, nested dirs
s3415ClassifyKey, ParseURI, defaults
spdx87Full parse, in-toto attestation envelope unwrapping, invalid JSON, deterministic IDs
vex58Parse, normalizeVulnID, URL patterns
Total77107184 test invocations

Test Patterns

Table-Driven Tests

func TestCategorize(t *testing.T) {
    tests := []struct {
        input    string
        expected Category
    }{
        {"MIT", CategoryPermissive},
        {"GPL-3.0-only", CategoryCopyleft},
    }
    for _, tt := range tests {
        t.Run(tt.input, func(t *testing.T) {
            got := Categorize(tt.input)
            if got != tt.expected {
                t.Errorf("Categorize(%q) = %q, want %q", tt.input, got, tt.expected)
            }
        })
    }
}

httptest Mock Server

func TestQueryBatch_MockServer(t *testing.T) {
    server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"results": [{"vulns": [...]}]}`))
    }))
    defer server.Close()
    // Use server.URL as base URL...
}

t.TempDir() for Filesystem Tests

func TestScanner_Scan(t *testing.T) {
    tmpDir := t.TempDir()
    os.WriteFile(filepath.Join(tmpDir, "test.spdx.json"), []byte(`{...}`), 0644)
    scanner := NewScanner(tmpDir)
    files, err := scanner.Scan()
    // Assert...
}

Test Requirements

  • No external dependencies. No running ClickHouse, network, or Docker needed.
  • No test order dependency. Each test is self-contained.
  • Race-safe. All tests must pass with -race flag.
  • Use subtests (t.Run()) for table-driven tests.

CI Integration

Tests run automatically on every push/PR:

- name: Test
  working-directory: backend
  run: go test ./... -count=1 -race

Angular (Frontend) Tests

The Angular frontend uses Vitest (not Karma/Jasmine). Tests live alongside components as *.spec.ts files.

Quick Start

cd ui

# Run all tests
npx ng test

# Run once (no watch)
npx ng test --watch=false

Current Test Inventory (Frontend)

Spec FileTestsWhat’s Covered
app.spec.ts3App creation, navbar brand, navigation links (10 routes)
dashboard.component.spec.ts2Component creation, KPI rendering
sbom-detail.component.spec.ts4Tab switching, vuln/license/dep views
cve-impact.component.spec.ts2CVE search, project listing
license-violations.component.spec.ts2Violations tab, exceptions tab
dependency-stats.component.spec.ts2Top dependencies, unique deps counter
version-skew.spec.ts3Model parsing, empty results, sorting
package-search.spec.ts8Search/detail response parsing, pagination, URL encoding, sorting
plus more…Various model and component tests
Total53

Test Patterns (Angular)

  • Model tests — Verify TypeScript interfaces match API shapes (no HTTP mocking needed)
  • Component tests — Use TestBed with provideHttpClientTesting() for HTTP mocking
  • OnPush strategy — Tests call fixture.detectChanges() and verify DOM output