namespace ScadaLink.NotificationService.Tests; /// /// NS-009: Tests for scrubbing SMTP credential secrets out of log/result text. /// public class CredentialRedactorTests { [Fact] public void Scrub_BasicAuthPassword_IsMasked() { // Password 'Hunter2pass!word' is 16 chars (>= MinSecretLength=12) and // therefore qualifies as a redactable secret-shaped trailing component. var text = "535 5.7.8 Authentication failed for user 'svc' with password 'Hunter2pass!word'"; var result = CredentialRedactor.Scrub(text, "svc:Hunter2pass!word"); Assert.DoesNotContain("Hunter2pass!word", result); Assert.DoesNotContain("svc:Hunter2pass!word", result); } [Fact] public void Scrub_OAuth2ClientSecret_IsMasked() { var text = "Token request failed: client_secret=Sup3rSecretValue rejected by tenant"; var result = CredentialRedactor.Scrub(text, "tenant-guid:client-guid:Sup3rSecretValue"); Assert.DoesNotContain("Sup3rSecretValue", result); } [Fact] public void Scrub_NullCredentials_ReturnsTextUnchanged() { Assert.Equal("plain text", CredentialRedactor.Scrub("plain text", null)); } [Fact] public void Scrub_NullText_ReturnsEmpty() { Assert.Equal(string.Empty, CredentialRedactor.Scrub(null, "user:pass")); } // --- NS-025: don't over-mask short non-secret components --- [Fact] public void Scrub_ShortUserName_IsNotMaskedOutsidePackedString() { // 'root' is the Basic Auth user name — short, common, and absolutely // not a secret. It must NOT be masked when it appears in unrelated // diagnostic text like a file path. var text = "Config file at /root/.config/scada.conf was not found."; var result = CredentialRedactor.Scrub(text, "root:hunter2longenoughpwd"); Assert.Contains("/root/.config", result); } [Fact] public void Scrub_TenantId_IsNotMaskedOutsidePackedString() { // The tenant id is not secret — only the client secret is. A tenant id // appearing in unrelated text (e.g. an error-code suffix) must survive. var text = "Error code tnt-1234567890-abcd reported by upstream"; var result = CredentialRedactor.Scrub(text, "tnt-1234567890-abcd:cli-guid:RealClientSecretLongEnough"); Assert.Contains("tnt-1234567890-abcd", result); } [Fact] public void Scrub_FullPackedCredential_IsAlwaysMaskedRegardlessOfLength() { // Even a short packed string must be masked when it appears verbatim — // that exact appearance can only come from the credential itself. var text = "Auth bundle was rejected: u:p"; var result = CredentialRedactor.Scrub(text, "u:p"); Assert.DoesNotContain("u:p", result); } }