package logger import ( "bytes" "context" "errors" "log/slog" "strings" "sync" "testing" ) func TestSecretStore_AddRemoveMask(t *testing.T) { store := NewSecretStore() // No secrets: text unchanged if got := store.Mask("password=secret123"); got != "password=secret123" { t.Errorf("expected unchanged text, got %q", got) } // Add a secret store.Add("secret123") if got := store.Mask("password=secret123"); got != "password=***" { t.Errorf("expected masked, got %q", got) } // Remove the secret store.Remove("secret123") if got := store.Mask("password=secret123"); got != "password=secret123" { t.Errorf("expected unmasked after remove, got %q", got) } } func TestSecretStore_EmptyString(t *testing.T) { store := NewSecretStore() store.Add("") if got := store.Mask("test"); got != "test" { t.Errorf("empty secret should be ignored, got %q", got) } store.Remove("") // should not panic } func TestSecretStore_MultipleSecrets(t *testing.T) { store := NewSecretStore() store.Add("pass1") store.Add("pass2") got := store.Mask("url=rtsp://user:pass1@host and also pwd=pass2&rate=0") if strings.Contains(got, "pass1") || strings.Contains(got, "pass2") { t.Errorf("both passwords should be masked, got %q", got) } } func TestSecretStore_ConcurrentAccess(t *testing.T) { store := NewSecretStore() var wg sync.WaitGroup // Simulate concurrent scans adding/removing/masking for i := 0; i < 100; i++ { wg.Add(3) secret := "secret" + string(rune('A'+i%26)) go func(s string) { defer wg.Done() store.Add(s) }(secret) go func() { defer wg.Done() _ = store.Mask("some text with secretA in it") }() go func(s string) { defer wg.Done() store.Remove(s) }(secret) } wg.Wait() } func TestSecretMaskingHandler_MasksStringAttrs(t *testing.T) { var buf bytes.Buffer store := NewSecretStore() store.Add("mypassword") inner := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}) handler := NewSecretMaskingHandler(inner, store) log := slog.New(handler) log.Debug("testing stream", "url", "rtsp://admin:mypassword@192.168.1.10/stream") output := buf.String() if strings.Contains(output, "mypassword") { t.Errorf("password should be masked in output: %s", output) } if !strings.Contains(output, "***") { t.Errorf("expected *** in output: %s", output) } } func TestSecretMaskingHandler_MasksMessage(t *testing.T) { var buf bytes.Buffer store := NewSecretStore() store.Add("secretpwd") inner := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}) handler := NewSecretMaskingHandler(inner, store) log := slog.New(handler) log.Debug("failed with secretpwd in message") output := buf.String() if strings.Contains(output, "secretpwd") { t.Errorf("password should be masked in message: %s", output) } } func TestSecretMaskingHandler_MasksErrorValues(t *testing.T) { var buf bytes.Buffer store := NewSecretStore() store.Add("r6wnm0wlix") inner := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}) handler := NewSecretMaskingHandler(inner, store) log := slog.New(handler) err := errors.New(`Get "http://10.0.20.111/cgi-bin/encoder?PWD=r6wnm0wlix&USER=admin": dial tcp`) log.Debug("request failed", "error", err) output := buf.String() if strings.Contains(output, "r6wnm0wlix") { t.Errorf("password should be masked in error: %s", output) } } func TestSecretMaskingHandler_NoSecretsPassthrough(t *testing.T) { var buf bytes.Buffer store := NewSecretStore() inner := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}) handler := NewSecretMaskingHandler(inner, store) log := slog.New(handler) log.Debug("normal message", "key", "value") output := buf.String() if !strings.Contains(output, "normal message") || !strings.Contains(output, "value") { t.Errorf("output should pass through unchanged: %s", output) } } func TestSecretMaskingHandler_MasksMultipleOccurrences(t *testing.T) { var buf bytes.Buffer store := NewSecretStore() store.Add("secret123") inner := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}) handler := NewSecretMaskingHandler(inner, store) log := slog.New(handler) log.Debug("test", "url1", "rtsp://user:secret123@host1/stream", "url2", "http://host2/snap?pwd=secret123", "path", "/user=admin_password=secret123_channel=1", ) output := buf.String() if strings.Contains(output, "secret123") { t.Errorf("all occurrences should be masked: %s", output) } } func TestSecretMaskingHandler_Enabled(t *testing.T) { store := NewSecretStore() inner := slog.NewTextHandler(&bytes.Buffer{}, &slog.HandlerOptions{Level: slog.LevelInfo}) handler := NewSecretMaskingHandler(inner, store) if handler.Enabled(context.Background(), slog.LevelDebug) { t.Error("debug should be disabled when level is info") } if !handler.Enabled(context.Background(), slog.LevelInfo) { t.Error("info should be enabled") } } func TestSecretMaskingHandler_WithAttrs(t *testing.T) { var buf bytes.Buffer store := NewSecretStore() store.Add("secretval") inner := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}) handler := NewSecretMaskingHandler(inner, store) child := handler.WithAttrs([]slog.Attr{slog.String("static", "has secretval inside")}) log := slog.New(child) log.Debug("test") output := buf.String() if strings.Contains(output, "secretval") { t.Errorf("pre-set attr should be masked: %s", output) } }