Cybersecurity-Projects/PROJECTS/intermediate/secrets-scanner/internal/engine/integration_test.go

375 lines
8.4 KiB
Go

// ©AngelaMos | 2026
// integration_test.go
package engine_test
import (
"context"
"os"
"path/filepath"
"runtime"
"sort"
"testing"
"github.com/CarterPerez-dev/portia/internal/engine"
"github.com/CarterPerez-dev/portia/internal/rules"
"github.com/CarterPerez-dev/portia/internal/source"
"github.com/CarterPerez-dev/portia/pkg/types"
)
func testdataDir(t *testing.T) string {
t.Helper()
_, filename, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("unable to determine test file location")
}
return filepath.Join(
filepath.Dir(filename), "..", "..", "testdata", "fixtures",
)
}
func setupPipeline(t *testing.T) (*engine.Pipeline, *rules.Registry) {
t.Helper()
reg := rules.NewRegistry()
rules.RegisterBuiltins(reg)
return engine.NewPipeline(reg), reg
}
func findByRuleID(
findings []types.Finding, ruleID string,
) *types.Finding {
for i := range findings {
if findings[i].RuleID == ruleID {
return &findings[i]
}
}
return nil
}
func TestIntegrationFullScan(t *testing.T) {
dir := testdataDir(t)
if _, err := os.Stat(dir); os.IsNotExist(err) {
t.Skip("testdata/fixtures not found")
}
p, _ := setupPipeline(t)
src := source.NewDirectory(dir, 0, nil)
result, err := p.Run(context.Background(), src)
if err != nil {
t.Fatalf("pipeline run failed: %v", err)
}
if len(result.Findings) == 0 {
t.Fatal("expected findings, got none")
}
ruleIDs := make(map[string]bool)
for _, f := range result.Findings {
ruleIDs[f.RuleID] = true
}
expectedRules := []string{
"aws-access-key-id",
"stripe-live-secret",
"ssh-private-key-rsa",
"generic-password",
}
for _, id := range expectedRules {
if !ruleIDs[id] {
t.Errorf("expected finding for rule %q, not found", id)
}
}
}
func TestIntegrationAWSKeyDetection(t *testing.T) {
dir := testdataDir(t)
p, _ := setupPipeline(t)
src := source.NewDirectory(dir, 0, nil)
result, err := p.Run(context.Background(), src)
if err != nil {
t.Fatalf("pipeline run failed: %v", err)
}
f := findByRuleID(result.Findings, "aws-access-key-id")
if f == nil {
t.Fatal("aws-access-key-id finding not found")
}
if f.Severity != types.SeverityCritical {
t.Errorf("expected CRITICAL, got %s", f.Severity)
}
if f.FilePath != "config.py" {
t.Errorf("expected config.py, got %s", f.FilePath)
}
}
func TestIntegrationStripeKeyDetection(t *testing.T) {
dir := testdataDir(t)
p, _ := setupPipeline(t)
src := source.NewDirectory(dir, 0, nil)
result, err := p.Run(context.Background(), src)
if err != nil {
t.Fatalf("pipeline run failed: %v", err)
}
f := findByRuleID(result.Findings, "stripe-live-secret")
if f == nil {
t.Fatal("stripe-live-secret finding not found")
}
if f.Severity != types.SeverityCritical {
t.Errorf("expected CRITICAL, got %s", f.Severity)
}
if f.FilePath != "payment.env" {
t.Errorf("expected payment.env, got %s", f.FilePath)
}
}
func TestIntegrationPrivateKeyDetection(t *testing.T) {
dir := testdataDir(t)
p, _ := setupPipeline(t)
src := source.NewDirectory(dir, 0, nil)
result, err := p.Run(context.Background(), src)
if err != nil {
t.Fatalf("pipeline run failed: %v", err)
}
f := findByRuleID(result.Findings, "ssh-private-key-rsa")
if f == nil {
t.Fatal("ssh-private-key-rsa finding not found")
}
if f.Severity != types.SeverityCritical {
t.Errorf("expected CRITICAL, got %s", f.Severity)
}
if f.FilePath != "key.pem" {
t.Errorf("expected key.pem, got %s", f.FilePath)
}
}
func TestIntegrationPasswordDetection(t *testing.T) {
dir := testdataDir(t)
p, _ := setupPipeline(t)
src := source.NewDirectory(dir, 0, nil)
result, err := p.Run(context.Background(), src)
if err != nil {
t.Fatalf("pipeline run failed: %v", err)
}
f := findByRuleID(result.Findings, "generic-password")
if f == nil {
t.Fatal("generic-password finding not found")
}
if f.Severity != types.SeverityHigh {
t.Errorf("expected HIGH, got %s", f.Severity)
}
if f.Entropy <= 0 {
t.Error("expected positive entropy for password")
}
}
func TestIntegrationGitHubTokenDetection(t *testing.T) {
dir := testdataDir(t)
p, _ := setupPipeline(t)
src := source.NewDirectory(dir, 0, nil)
result, err := p.Run(context.Background(), src)
if err != nil {
t.Fatalf("pipeline run failed: %v", err)
}
f := findByRuleID(result.Findings, "github-pat-classic")
if f == nil {
f = findByRuleID(result.Findings, "github-pat-fine")
if f == nil {
t.Fatal(
"github-pat-classic or github-pat-fine finding not found",
)
}
}
if f.Severity != types.SeverityCritical {
t.Errorf("expected CRITICAL, got %s", f.Severity)
}
if f.FilePath != "github_token.js" {
t.Errorf("expected github_token.js, got %s", f.FilePath)
}
}
func TestIntegrationConnectionStringDetection(t *testing.T) {
dir := testdataDir(t)
p, _ := setupPipeline(t)
src := source.NewDirectory(dir, 0, nil)
result, err := p.Run(context.Background(), src)
if err != nil {
t.Fatalf("pipeline run failed: %v", err)
}
f := findByRuleID(result.Findings, "postgres-connection")
if f == nil {
t.Fatal("postgres-connection finding not found")
}
if f.FilePath != "connection.yml" {
t.Errorf("expected connection.yml, got %s", f.FilePath)
}
}
func TestIntegrationNoFalsePositivesOnSafeFiles(t *testing.T) {
dir := testdataDir(t)
p, _ := setupPipeline(t)
src := source.NewDirectory(dir, 0, nil)
result, err := p.Run(context.Background(), src)
if err != nil {
t.Fatalf("pipeline run failed: %v", err)
}
for _, f := range result.Findings {
if f.FilePath == "safe.txt" {
t.Errorf(
"unexpected finding in safe.txt: %s (%s)",
f.RuleID, f.Secret,
)
}
}
}
func TestIntegrationTemplatesFiltered(t *testing.T) {
dir := testdataDir(t)
p, _ := setupPipeline(t)
src := source.NewDirectory(dir, 0, nil)
result, err := p.Run(context.Background(), src)
if err != nil {
t.Fatalf("pipeline run failed: %v", err)
}
for _, f := range result.Findings {
if f.FilePath == "template.env" {
t.Errorf(
"unexpected finding in template.env: %s (%s)",
f.RuleID, f.Secret,
)
}
}
}
func TestIntegrationExcludePaths(t *testing.T) {
dir := testdataDir(t)
p, _ := setupPipeline(t)
src := source.NewDirectory(dir, 0, []string{"*.py"})
result, err := p.Run(context.Background(), src)
if err != nil {
t.Fatalf("pipeline run failed: %v", err)
}
for _, f := range result.Findings {
if f.FilePath == "config.py" {
t.Error("config.py should be excluded")
}
}
}
func TestIntegrationDisableRules(t *testing.T) {
dir := testdataDir(t)
reg := rules.NewRegistry()
rules.RegisterBuiltins(reg)
reg.Disable("aws-access-key-id", "stripe-live-secret")
p := engine.NewPipeline(reg)
src := source.NewDirectory(dir, 0, nil)
result, err := p.Run(context.Background(), src)
if err != nil {
t.Fatalf("pipeline run failed: %v", err)
}
for _, f := range result.Findings {
if f.RuleID == "aws-access-key-id" {
t.Error("aws-access-key-id should be disabled")
}
if f.RuleID == "stripe-live-secret" {
t.Error("stripe-live-secret should be disabled")
}
}
}
func TestIntegrationFindingsSortBySeverity(t *testing.T) {
dir := testdataDir(t)
p, _ := setupPipeline(t)
src := source.NewDirectory(dir, 0, nil)
result, err := p.Run(context.Background(), src)
if err != nil {
t.Fatalf("pipeline run failed: %v", err)
}
sorted := make([]types.Finding, len(result.Findings))
copy(sorted, result.Findings)
sort.Slice(sorted, func(i, j int) bool {
return sorted[i].Severity.Rank() < sorted[j].Severity.Rank()
})
if len(sorted) > 0 &&
sorted[0].Severity != types.SeverityCritical {
t.Errorf(
"expected first sorted finding to be CRITICAL, got %s",
sorted[0].Severity,
)
}
}
func TestIntegrationResultMetadata(t *testing.T) {
dir := testdataDir(t)
p, reg := setupPipeline(t)
src := source.NewDirectory(dir, 0, nil)
result, err := p.Run(context.Background(), src)
if err != nil {
t.Fatalf("pipeline run failed: %v", err)
}
if result.TotalRules != reg.Len() {
t.Errorf(
"expected %d rules, got %d",
reg.Len(), result.TotalRules,
)
}
if result.TotalFindings < len(result.Findings) {
t.Errorf(
"total findings %d < unique findings %d",
result.TotalFindings, len(result.Findings),
)
}
}
func TestIntegrationContextCancellation(t *testing.T) {
dir := testdataDir(t)
p, _ := setupPipeline(t)
src := source.NewDirectory(dir, 0, nil)
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := p.Run(ctx, src)
if err == nil {
t.Log("expected error or empty result from cancelled context")
}
}