using ZB.MOM.WW.Audit; using ZB.MOM.WW.ScadaBridge.AuditLog.Redaction; using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit; namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Redaction; /// /// ScadaBridge audit re-architecture stage C2 (Task 2.5) tests for /// — the minimal always-safe /// fallback. /// Header-only scrub of the always-sensitive default headers inside /// DetailsJson's RequestSummary / ResponseSummary; never throws, never /// performs body / SQL / truncation work. /// public class SafeDefaultAuditRedactorTests { private static AuditEvent EventWith(string? request = null, string? response = null) { var details = new AuditDetails { RequestSummary = request, ResponseSummary = response, }; return new AuditEvent { EventId = Guid.NewGuid(), OccurredAtUtc = DateTimeOffset.UtcNow, Actor = "tester", Action = "ApiOutbound.ApiCall", Outcome = AuditOutcome.Success, DetailsJson = AuditDetailsCodec.Serialize(details), }; } private static AuditDetails Details(AuditEvent evt) => AuditDetailsCodec.Deserialize(evt.DetailsJson); [Fact] public void Redacts_DefaultSensitiveHeaders_InRequestSummary() { var evt = EventWith(request: "Authorization: Bearer secret-token\nContent-Type: application/json"); var result = SafeDefaultAuditRedactor.Instance.Apply(evt); var d = Details(result); Assert.Contains("Authorization: ", d.RequestSummary!); Assert.DoesNotContain("secret-token", d.RequestSummary!); Assert.Contains("Content-Type: application/json", d.RequestSummary!); } [Fact] public void Redacts_DefaultSensitiveHeaders_InResponseSummary() { var evt = EventWith(response: "Set-Cookie: sessionid=abc123\nX-Other: ok"); var result = SafeDefaultAuditRedactor.Instance.Apply(evt); var d = Details(result); Assert.Contains("Set-Cookie: ", d.ResponseSummary!); Assert.DoesNotContain("abc123", d.ResponseSummary!); Assert.Contains("X-Other: ok", d.ResponseSummary!); } [Fact] public void CaseInsensitive_HeaderName_Redacted() { var evt = EventWith(request: "authorization: Bearer x-y-z"); var result = SafeDefaultAuditRedactor.Instance.Apply(evt); Assert.Contains("", Details(result).RequestSummary!); Assert.DoesNotContain("x-y-z", Details(result).RequestSummary!); } [Fact] public void NonSensitiveHeader_Preserved() { var evt = EventWith(request: "X-Request-Id: abc-123\nAccept: application/json"); var result = SafeDefaultAuditRedactor.Instance.Apply(evt); var d = Details(result); Assert.Contains("X-Request-Id: abc-123", d.RequestSummary!); Assert.Contains("Accept: application/json", d.RequestSummary!); } [Fact] public void NullDetails_FastPath_ReturnsSameInstance() { var evt = new AuditEvent { EventId = Guid.NewGuid(), OccurredAtUtc = DateTimeOffset.UtcNow, Actor = "tester", Action = "ApiOutbound.ApiCall", Outcome = AuditOutcome.Success, DetailsJson = null, }; var result = SafeDefaultAuditRedactor.Instance.Apply(evt); Assert.Same(evt, result); } [Fact] public void MalformedDetailsJson_NeverThrows() { var evt = new AuditEvent { EventId = Guid.NewGuid(), OccurredAtUtc = DateTimeOffset.UtcNow, Actor = "tester", Action = "ApiOutbound.ApiCall", Outcome = AuditOutcome.Success, DetailsJson = "{not valid json]", }; var ex = Record.Exception(() => SafeDefaultAuditRedactor.Instance.Apply(evt)); Assert.Null(ex); } }