rdev/internal/logging/redact_test.go
jordan d69da6d627 feat: add structured logging infrastructure and SDLC extensions
Major changes:
- Add internal/logging package with field constants, context propagation,
  sensitive data auto-redaction, and per-component log levels
- Add worker timeout constants (TimeoutQuickOp, TimeoutHealthCheck, etc.)
- Extend SDLC with callback handlers, generate endpoints, and executor
- Add new cookbook trees for aeries and slackpath progression
- Add skeleton templates for queue, realtime, and microservices
- Add worker component template with async job processing
- Refactor services and handlers to use new logging infrastructure
- Split component.go into component_infra.go and component_listing.go

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 22:56:04 -07:00

302 lines
7.5 KiB
Go

package logging
import (
"bytes"
"encoding/json"
"strings"
"testing"
)
func TestIsSensitiveField(t *testing.T) {
tests := []struct {
fieldName string
want bool
}{
// Exact matches (should be sensitive)
{"password", true},
{"PASSWORD", true},
{"Password", true},
{"secret", true},
{"token", true},
{"api_key", true},
{"apikey", true},
{"api-key", true},
{"bearer", true},
{"authorization", true},
{"auth_token", true},
{"access_token", true},
{"refresh_token", true},
{"private_key", true},
{"secret_key", true},
{"client_secret", true},
{"cookie", true},
{"session", true},
{"x-api-key", true},
{"credentials", true},
{"credit_card", true},
{"cvv", true},
{"ssn", true},
{"bank_account", true},
// Pattern matches (contain sensitive keywords)
{"user_password", true},
{"db_password", true},
{"auth_header", true},
{"secret_value", true},
{"api_key_id", true},
{"bearer_token", true},
// Non-sensitive fields
{"name", false},
{"email", false},
{"user_id", false},
{"project_id", false},
{"status", false},
{"created_at", false},
{"component", false},
{"handler", false},
{"duration_ms", false},
{"request_id", false},
}
for _, tt := range tests {
t.Run(tt.fieldName, func(t *testing.T) {
got := IsSensitiveField(tt.fieldName)
if got != tt.want {
t.Errorf("IsSensitiveField(%q) = %v, want %v", tt.fieldName, got, tt.want)
}
})
}
}
func TestRedactValue(t *testing.T) {
tests := []struct {
name string
value string
want string
}{
// Bearer tokens
{
name: "bearer token",
value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
want: RedactedValue,
},
// AWS access keys
{
name: "aws access key",
value: "AKIAIOSFODNN7EXAMPLE",
want: RedactedValue,
},
// JWT tokens
{
name: "jwt token",
value: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U",
want: RedactedValue,
},
// Connection strings with passwords
{
name: "postgres connection string",
value: "postgres://user:secretpassword123@localhost:5432/mydb",
want: RedactedValue,
},
{
name: "redis connection string",
value: "redis://admin:p@ssw0rd@redis.example.com:6379",
want: RedactedValue,
},
// Private key header
{
name: "private key",
value: "-----BEGIN RSA PRIVATE KEY-----",
want: RedactedValue,
},
// API key patterns
{
name: "api key in string",
value: "api_key=abc123xyz456789012345678901234567890",
want: RedactedValue,
},
// Non-sensitive values should pass through
{
name: "normal string",
value: "hello world",
want: "hello world",
},
{
name: "email",
value: "user@example.com",
want: "user@example.com",
},
{
name: "uuid",
value: "550e8400-e29b-41d4-a716-446655440000",
want: "550e8400-e29b-41d4-a716-446655440000",
},
{
name: "path",
value: "/api/v1/projects/123",
want: "/api/v1/projects/123",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := RedactValue(tt.value)
if got != tt.want {
t.Errorf("RedactValue(%q) = %q, want %q", tt.value, got, tt.want)
}
})
}
}
func TestContainsSensitiveData(t *testing.T) {
tests := []struct {
name string
value string
want bool
}{
{"bearer token", "Authorization: Bearer xyz123", true},
{"jwt", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIn0.abc", true},
{"normal text", "This is a normal log message", false},
{"empty", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ContainsSensitiveData(tt.value)
if got != tt.want {
t.Errorf("ContainsSensitiveData(%q) = %v, want %v", tt.value, got, tt.want)
}
})
}
}
func TestRedactingHandlerFieldNames(t *testing.T) {
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.RedactEnabled = true
logger := NewWithWriter(cfg, &buf)
// Log with sensitive field names
logger.Info("test",
"password", "secret123",
"api_key", "key456",
"name", "visible",
)
output := buf.String()
// Sensitive fields should be redacted
if strings.Contains(output, "secret123") {
t.Errorf("password value should be redacted, got: %s", output)
}
if strings.Contains(output, "key456") {
t.Errorf("api_key value should be redacted, got: %s", output)
}
// Non-sensitive fields should be visible
if !strings.Contains(output, "visible") {
t.Errorf("non-sensitive field should be visible, got: %s", output)
}
// Redacted marker should appear
if !strings.Contains(output, RedactedValue) {
t.Errorf("expected redacted marker in output, got: %s", output)
}
}
func TestRedactingHandlerValues(t *testing.T) {
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.RedactEnabled = true
logger := NewWithWriter(cfg, &buf)
// Log with sensitive values
logger.Info("test",
"auth_header", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIn0.abc",
)
output := buf.String()
// JWT should be redacted
if strings.Contains(output, "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9") {
t.Errorf("JWT token should be redacted, got: %s", output)
}
}
func TestRedactingHandlerPreservesStructure(t *testing.T) {
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Format = FormatJSON
cfg.RedactEnabled = true
logger := NewWithWriter(cfg, &buf)
logger.Info("test message",
"user_id", "123",
"password", "secret",
"status", "success",
)
// Parse JSON to verify structure is preserved
var entry map[string]any
if err := json.Unmarshal(buf.Bytes(), &entry); err != nil {
t.Fatalf("expected valid JSON, got error: %v, output: %s", err, buf.String())
}
// Check that fields exist
if entry["user_id"] != "123" {
t.Errorf("expected user_id=123, got: %v", entry["user_id"])
}
if entry["status"] != "success" {
t.Errorf("expected status=success, got: %v", entry["status"])
}
// Password should be redacted
if entry["password"] != RedactedValue {
t.Errorf("expected password to be redacted, got: %v", entry["password"])
}
}
func TestRedactingHandlerDisabled(t *testing.T) {
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.RedactEnabled = false
logger := NewWithWriter(cfg, &buf)
// With redaction disabled, sensitive values should pass through
logger.Info("test", "password", "visible_password")
output := buf.String()
if !strings.Contains(output, "visible_password") {
t.Errorf("with redaction disabled, password should be visible, got: %s", output)
}
}
// TestSecurityCriticalPatterns tests patterns that MUST be redacted
func TestSecurityCriticalPatterns(t *testing.T) {
criticalPatterns := []struct {
name string
value string
}{
{"AWS access key", "AKIAIOSFODNN7EXAMPLE"},
{"Private key header", "-----BEGIN RSA PRIVATE KEY-----"},
{"Bearer token", "Bearer sk-1234567890abcdef"},
{"Postgres connection", "postgres://admin:secret@db:5432/app"},
{"MongoDB connection", "mongodb://user:pass@mongo:27017/db"},
{"Redis connection", "redis://:password@redis:6379"},
}
for _, tt := range criticalPatterns {
t.Run(tt.name, func(t *testing.T) {
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.RedactEnabled = true
logger := NewWithWriter(cfg, &buf)
logger.Info("test", "data", tt.value)
output := buf.String()
if strings.Contains(output, tt.value) && !strings.Contains(tt.value, RedactedValue) {
t.Errorf("SECURITY: %s should be redacted, but found in output: %s", tt.name, output)
}
})
}
}