package logger import ( "bytes" "strings" "testing" ) func TestNewTextLogger(t *testing.T) { var buf bytes.Buffer l := New(&buf, LevelDebug, "text") l.Info("hello world", "key", "value") output := buf.String() if !strings.Contains(output, "hello world") { t.Errorf("expected output to contain 'hello world', got: %s", output) } if !strings.Contains(output, "key") { t.Errorf("expected output to contain 'key', got: %s", output) } if !strings.Contains(output, "value") { t.Errorf("expected output to contain 'value', got: %s", output) } } func TestNewJSONLogger(t *testing.T) { var buf bytes.Buffer l := New(&buf, LevelDebug, "json") l.Info("hello world", "key", "value") output := buf.String() if !strings.Contains(output, `"msg":"hello world"`) { t.Errorf("expected JSON output to contain msg, got: %s", output) } } func TestLogLevels(t *testing.T) { var buf bytes.Buffer l := New(&buf, LevelWarn, "text") l.Debug("debug msg") l.Info("info msg") l.Warn("warn msg") l.Error("error msg") output := buf.String() if strings.Contains(output, "debug") { t.Error("debug message should be filtered out at WARN level") } if strings.Contains(output, "info") { t.Error("info message should be filtered out at WARN level") } if !strings.Contains(output, "warn") { t.Error("warn message should appear") } if !strings.Contains(output, "error") { t.Error("error message should appear") } } func TestWithFields(t *testing.T) { var buf bytes.Buffer l := New(&buf, LevelDebug, "text") child := l.With("request_id", "abc123", "user_id", "user-1") child.Info("request processed") output := buf.String() if !strings.Contains(output, "request_id") || !strings.Contains(output, "abc123") { t.Errorf("expected 'request_id=abc123' in output, got: %s", output) } if !strings.Contains(output, "user_id") || !strings.Contains(output, "user-1") { t.Errorf("expected 'user_id=user-1' in output, got: %s", output) } } func TestNewNop(t *testing.T) { l := NewNop() var buf bytes.Buffer // Nop logger should not write anything l2 := New(&buf, LevelDebug, "text") l2.Info("should appear") if !strings.Contains(buf.String(), "should appear") { t.Error("normal logger should write") } // Nop logger itself is fine to call l.Info("should not appear") l.Error("should not appear") } func TestParseLevel(t *testing.T) { tests := []struct { input string expected Level hasError bool }{ {"debug", LevelDebug, false}, {"DEBUG", LevelDebug, false}, {"info", LevelInfo, false}, {"warn", LevelWarn, false}, {"error", LevelError, false}, {"ERROR", LevelError, false}, {"invalid", LevelInfo, true}, {"", LevelInfo, true}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { level, err := ParseLevel(tt.input) if tt.hasError && err == nil { t.Errorf("ParseLevel(%q) expected error but got none", tt.input) } if !tt.hasError && err != nil { t.Errorf("ParseLevel(%q) unexpected error: %v", tt.input, err) } if level != tt.expected { t.Errorf("ParseLevel(%q) = %v, want %v", tt.input, level, tt.expected) } }) } } func TestRedaction(t *testing.T) { var buf bytes.Buffer l := New(&buf, LevelDebug, "text") l.Info("auth attempt", "api_key", "sk-abcdefgh12345678", "user_id", "user-1") output := buf.String() if strings.Contains(output, "sk-abcdefgh12345678") { t.Errorf("API key should be redacted, got: %s", output) } if !strings.Contains(output, "sk-") && !strings.Contains(output, "****") { t.Errorf("expected redacted value with mask, got: %s", output) } if !strings.Contains(output, "user-1") { t.Errorf("non-sensitive field should NOT be redacted, got: %s", output) } } func TestRedactSensitiveKeys(t *testing.T) { sensitiveTests := []string{"api_key", "API_KEY", "password", "secret", "token", "jwt_secret", "admin_password", "llm_api_key", "dashscope_api_key", "access_token"} nonSensitiveTests := []string{"user_id", "request_id", "model", "port", "host", "query", "duration_ms", "count"} for _, key := range sensitiveTests { val := redactValue(key, "super-secret-value-12345") if s, ok := val.(string); ok { if s == "super-secret-value-12345" { t.Errorf("key %q should be redacted, got raw value instead", key) } if !strings.Contains(s, "****") { t.Errorf("key %q redacted value should contain '****', got: %s", key, s) } } } for _, key := range nonSensitiveTests { val := redactValue(key, "normal-value") if s, ok := val.(string); ok { if s != "normal-value" { t.Errorf("key %q should NOT be redacted, got: %s", key, s) } } } } func TestRedactShortValue(t *testing.T) { // Short values (< 4 chars) should be fully masked val := redactValue("api_key", "ab") if s, ok := val.(string); ok { if s != "****" { t.Errorf("short value should be fully masked, got: %s", s) } } } func TestOddNumberOfArgs(t *testing.T) { // Go's slog vet check prevents odd numbers of arguments at compile time. // Our wrapper detects this and appends a !MISSING placeholder internally. // This test verifies buildAttrs handles odd args correctly. var buf bytes.Buffer l := New(&buf, LevelDebug, "text") attrs := l.buildAttrs("key1", "value1", "key2") if len(attrs) != 2 { t.Errorf("expected 2 attrs, got %d", len(attrs)) } if attrs[1].Key != "key2" { t.Errorf("expected attrs[1].Key=key2, got %s", attrs[1].Key) } // The value for key2 should be "!MISSING" if attrs[1].Value.String() != "!MISSING" { t.Errorf("expected !MISSING placeholder, got %s", attrs[1].Value.String()) } }