package observability import ( "bytes" "encoding/json" "os" "strings" "testing" ) func TestParseLogLevel(t *testing.T) { tests := []struct { input string want LogLevel }{ {"debug", LevelDebug}, {"info", LevelInfo}, {"warn", LevelWarn}, {"warning", LevelWarn}, {"error", LevelError}, {"invalid", LevelInfo}, } for _, tt := range tests { got := ParseLogLevel(tt.input) if got != tt.want { t.Errorf("ParseLogLevel(%q) = %d, want %d", tt.input, got, tt.want) } } } func TestLoggerMaskSensitive(t *testing.T) { logger := &Logger{ level: LevelInfo, maskFields: []string{"api_key", "secret", "password", "token"}, } data := map[string]any{ "api_key": "sk-12345", "message": "hello", "nested": map[string]any{ "secret": "my-secret", }, } masked := logger.maskSensitive(data) if masked["api_key"] != "***REDACTED***" { t.Errorf("expected api_key redacted, got %v", masked["api_key"]) } if masked["message"] != "hello" { t.Errorf("expected message preserved, got %v", masked["message"]) } nested, ok := masked["nested"].(map[string]any) if !ok { t.Fatal("expected nested map") } if nested["secret"] != "***REDACTED***" { t.Errorf("expected nested secret redacted, got %v", nested["secret"]) } } func TestLogFieldsBuilder(t *testing.T) { f := F().RequestID("req-1").TaskID("task-1").Event("test_event").Set("custom", "value") if f.fields["request_id"] != "req-1" { t.Error("request_id not set") } if f.fields["task_id"] != "task-1" { t.Error("task_id not set") } if f.fields["event"] != "test_event" { t.Error("event not set") } if f.fields["custom"] != "value" { t.Error("custom not set") } } func TestLoggerWrite(t *testing.T) { // Use a temp file to capture output tmpFile, err := os.CreateTemp("", "logtest*.json") if err != nil { t.Fatalf("create temp file: %v", err) } defer os.Remove(tmpFile.Name()) logger := NewLogger(LevelDebug, tmpFile, "metadata_only") logger.Info("test message", F().RequestID("req-123").Event("unit_test")) tmpFile.Close() data, err := os.ReadFile(tmpFile.Name()) if err != nil { t.Fatalf("read log file: %v", err) } var entry map[string]any if err := json.Unmarshal(bytes.TrimSpace(data), &entry); err != nil { t.Fatalf("parse log json: %v\nraw: %s", err, string(data)) } if entry["level"] != "INFO" { t.Errorf("expected level INFO, got %v", entry["level"]) } if entry["message"] != "test message" { t.Errorf("expected message 'test message', got %v", entry["message"]) } if entry["request_id"] != "req-123" { t.Errorf("expected request_id req-123, got %v", entry["request_id"]) } if entry["event"] != "unit_test" { t.Errorf("expected event unit_test, got %v", entry["event"]) } } func TestLoggerLevelFiltering(t *testing.T) { // This test verifies that debug messages are not logged when level is INFO tmpFile, err := os.CreateTemp("", "logtest*.json") if err != nil { t.Fatalf("create temp file: %v", err) } defer os.Remove(tmpFile.Name()) logger := NewLogger(LevelWarn, tmpFile, "metadata_only") logger.Info("should not appear", F().Event("info_event")) logger.Warn("should appear", F().Event("warn_event")) tmpFile.Close() data, _ := os.ReadFile(tmpFile.Name()) if strings.Contains(string(data), "should not appear") { t.Error("INFO message was logged when level is WARN") } if !strings.Contains(string(data), "should appear") { t.Error("WARN message was not logged") } }