package shhh import ( "context" "testing" "github.com/stretchr/testify/require" ) type recordingSink struct { events []AuditEvent } func (r *recordingSink) RecordRedaction(_ context.Context, event AuditEvent) { r.events = append(r.events, event) } func TestRedactText_DefaultRules(t *testing.T) { sentinel, err := NewSentinel(Config{}) require.NoError(t, err) input := "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.secret" redacted, findings := sentinel.RedactText(context.Background(), input, map[string]string{"source": "http.request.headers.authorization"}) require.Equal(t, "Authorization: Bearer [REDACTED]", redacted) require.Len(t, findings, 1) require.Equal(t, "bearer-token", findings[0].Rule) require.Equal(t, 1, findings[0].Count) require.NotEmpty(t, findings[0].Locations) snapshot := sentinel.StatsSnapshot() require.Equal(t, uint64(1), snapshot.TotalScans) require.Equal(t, uint64(1), snapshot.TotalFindings) require.Equal(t, uint64(1), snapshot.PerRuleFindings["bearer-token"]) } func TestRedactMap_NestedStructures(t *testing.T) { sentinel, err := NewSentinel(Config{}) require.NoError(t, err) payload := map[string]any{ "config": map[string]any{ "api_key": "API_KEY=1234567890ABCDEFG", }, "tokens": []any{ "sk-test1234567890ABCDEF", map[string]any{"refresh": "refresh_token=abcdef12345"}, }, } findings := sentinel.RedactMap(context.Background(), payload) require.NotEmpty(t, findings) config := payload["config"].(map[string]any) require.Equal(t, "API_KEY=[REDACTED]", config["api_key"]) tokens := payload["tokens"].([]any) require.Equal(t, "[REDACTED]", tokens[0]) inner := tokens[1].(map[string]any) require.Equal(t, "refresh_token=[REDACTED]", inner["refresh"]) total := 0 for _, finding := range findings { total += finding.Count } require.Equal(t, 3, total) } func TestAuditSinkReceivesEvents(t *testing.T) { sink := &recordingSink{} cfg := Config{ DisableDefaultRules: true, CustomRules: []RuleConfig{ { Name: "custom-secret", Pattern: `(secret\s*=\s*)([A-Za-z0-9]{6,})`, ReplacementTemplate: "$1[REDACTED]", Severity: SeverityHigh, }, }, } sentinel, err := NewSentinel(cfg, WithAuditSink(sink)) require.NoError(t, err) _, findings := sentinel.RedactText(context.Background(), "secret=mysecretvalue", map[string]string{"source": "test"}) require.Len(t, findings, 1) require.Equal(t, 1, findings[0].Count) require.Len(t, sink.events, 1) require.Equal(t, "custom-secret", sink.events[0].Rule) require.NotEmpty(t, sink.events[0].Hash) require.Equal(t, "test", sink.events[0].Path) }