using w4c_workflows.Services.Audit; using Xunit; namespace w4c_workflows.Tests; public class SecretRedactorTests { [Theory] [InlineData("api_key=abc123def", "abc123def")] [InlineData("apiKey: abc123def", "abc123def")] [InlineData("password=hunter2", "hunter2")] [InlineData("""{"client_secret":"s3cr3t-value"}""", "s3cr3t-value")] [InlineData("Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.abc", "eyJhbGciOiJIUzI1NiJ9")] [InlineData("Authorization: Basic YWRhOnNlY3JldA==", "YWRhOnNlY3JldA")] [InlineData("https://user:p4ssw0rd@example.com/x", "p4ssw0rd")] [InlineData("access_token=opaque-token-value", "opaque-token-value")] public void Redacts_secret_values(string input, string secret) { var redacted = SecretRedactor.Redact(input); Assert.DoesNotContain(secret, redacted); Assert.Contains(SecretRedactor.Placeholder, redacted); } [Fact] public void Keeps_the_field_name_for_context() { Assert.Contains("api_key", SecretRedactor.Redact("api_key=abc123")); } [Fact] public void Leaves_ordinary_text_untouched() { Assert.Equal("hello world", SecretRedactor.Redact("hello world")); } [Fact] public void Handles_null_and_empty() { Assert.Null(SecretRedactor.Redact(null)); Assert.Equal(string.Empty, SecretRedactor.Redact(string.Empty)); } // P1-10: a connector secret substituted into a URL path/query is not // key=value shaped, so the regex rules cannot find it. Passing the resolved // secret values must remove the literal wherever it appears. [Fact] public void Redacts_known_secret_literals_anywhere_in_the_text() { var secret = "ghp_superSecretToken12345"; var text = $"request failed: GET https://api.example.com/{secret}/items?x=1"; var redacted = SecretRedactor.Redact(text, new[] { secret }); Assert.DoesNotContain(secret, redacted); Assert.Contains(SecretRedactor.Placeholder, redacted); } [Fact] public void Ignores_short_known_values_to_avoid_over_redaction() { Assert.Equal("id 42 ok", SecretRedactor.Redact("id 42 ok", new[] { "42" })); } }