package logging import ( "context" "encoding/json" "net/http" "path/filepath" "strings" "testing" "github.com/rs/zerolog" ) func TestRedactHeaders(t *testing.T) { tests := []struct { name string headers http.Header check func(t *testing.T, result string) }{ { name: "redacts Authorization", headers: http.Header{ "Authorization": []string{"Bearer secret-token"}, }, check: func(t *testing.T, result string) { var m map[string]string if err := json.Unmarshal([]byte(result), &m); err != nil { t.Fatalf("unmarshal: %v", err) } if m["Authorization"] != "***" { t.Errorf("Authorization = %q, want ***", m["Authorization"]) } }, }, { name: "redacts x-api-key", headers: http.Header{ "X-Api-Key": []string{"sk-ant-secret"}, }, check: func(t *testing.T, result string) { var m map[string]string if err := json.Unmarshal([]byte(result), &m); err != nil { t.Fatalf("unmarshal: %v", err) } if m["X-Api-Key"] != "***" { t.Errorf("X-Api-Key = %q, want ***", m["X-Api-Key"]) } }, }, { name: "preserves other headers", headers: http.Header{ "Content-Type": []string{"application/json"}, "Accept": []string{"text/html", "application/json"}, }, check: func(t *testing.T, result string) { var m map[string]string if err := json.Unmarshal([]byte(result), &m); err != nil { t.Fatalf("unmarshal: %v", err) } if m["Content-Type"] != "application/json" { t.Errorf("Content-Type = %q, want application/json", m["Content-Type"]) } if m["Accept"] != "text/html, application/json" { t.Errorf("Accept = %q, want 'text/html, application/json'", m["Accept"]) } }, }, { name: "case-insensitive redaction", headers: http.Header{ "authorization": []string{"Bearer token"}, "X-API-KEY": []string{"key123"}, }, check: func(t *testing.T, result string) { var m map[string]string if err := json.Unmarshal([]byte(result), &m); err != nil { t.Fatalf("unmarshal: %v", err) } // http.Header canonicalizes keys, but RedactHeaders lowercases for comparison for _, v := range m { if v != "***" { t.Errorf("expected all values to be ***, got %q", v) } } }, }, { name: "empty headers", headers: http.Header{}, check: func(t *testing.T, result string) { if result != "{}" { t.Errorf("result = %q, want {}", result) } }, }, { name: "mixed sensitive and non-sensitive", headers: http.Header{ "Authorization": []string{"Bearer tok"}, "X-Api-Key": []string{"key"}, "Content-Type": []string{"application/json"}, "X-Request-Id": []string{"abc123"}, }, check: func(t *testing.T, result string) { var m map[string]string if err := json.Unmarshal([]byte(result), &m); err != nil { t.Fatalf("unmarshal: %v", err) } if m["Authorization"] != "***" { t.Errorf("Authorization = %q, want ***", m["Authorization"]) } if m["X-Api-Key"] != "***" { t.Errorf("X-Api-Key = %q, want ***", m["X-Api-Key"]) } if m["Content-Type"] != "application/json" { t.Errorf("Content-Type = %q", m["Content-Type"]) } if m["X-Request-Id"] != "abc123" { t.Errorf("X-Request-Id = %q", m["X-Request-Id"]) } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := RedactHeaders(tt.headers) // Result should be valid JSON if !json.Valid([]byte(result)) { t.Fatalf("result is not valid JSON: %q", result) } tt.check(t, result) }) } } func TestRedactHeaders_ReturnsJSON(t *testing.T) { h := http.Header{"Foo": []string{"bar"}} result := RedactHeaders(h) if !strings.HasPrefix(result, "{") || !strings.HasSuffix(result, "}") { t.Errorf("result not JSON object: %q", result) } } func TestStatusLevel(t *testing.T) { tests := []struct { status int want zerolog.Level }{ {200, zerolog.InfoLevel}, {201, zerolog.InfoLevel}, {204, zerolog.InfoLevel}, {301, zerolog.InfoLevel}, {399, zerolog.InfoLevel}, {400, zerolog.WarnLevel}, {401, zerolog.WarnLevel}, {403, zerolog.WarnLevel}, {404, zerolog.WarnLevel}, {429, zerolog.WarnLevel}, {499, zerolog.WarnLevel}, {500, zerolog.ErrorLevel}, {502, zerolog.ErrorLevel}, {503, zerolog.ErrorLevel}, {599, zerolog.ErrorLevel}, } for _, tt := range tests { got := statusLevel(tt.status) if got != tt.want { t.Errorf("statusLevel(%d) = %v, want %v", tt.status, got, tt.want) } } } func TestSetup_WithFile(t *testing.T) { dir := t.TempDir() logFile := filepath.Join(dir, "test.log") logger := Setup(Config{ Level: "debug", File: logFile, MaxSizeMB: 10, MaxBackups: 1, MaxAgeDays: 1, }) // Verify logger works (no panic) logger.Info().Msg("test message") } func TestSetup_WithoutFile(t *testing.T) { // File empty — should use console or stderr mode depending on TTY logger := Setup(Config{ Level: "warn", }) // Verify logger works (no panic) logger.Warn().Msg("test warning") } func TestSetup_DefaultLevel(t *testing.T) { // Empty level should default to info logger := Setup(Config{}) _ = logger // verify no panic } func TestSetup_InvalidLevel(t *testing.T) { // Invalid level should default to info logger := Setup(Config{Level: "not-a-level"}) _ = logger // verify no panic } func TestFromContext_NoLogger(t *testing.T) { // Background context has no zerolog logger — should return global ctx := context.Background() l := FromContext(ctx) if l == nil { t.Fatal("FromContext returned nil") } } func TestFromContext_WithLogger(t *testing.T) { logger := zerolog.Nop() ctx := logger.WithContext(context.Background()) l := FromContext(ctx) if l == nil { t.Fatal("FromContext returned nil") } }