using Shouldly; using Xunit; using ZB.MOM.WW.Audit; using ZB.MOM.WW.OtOpcUa.AdminUI.Audit; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Audit; /// /// Unit tests for , the pure factory that maps a certificate /// trust/untrust/delete action into a canonical . /// public sealed class CertAuditEventsTests { private const string Thumb = "AABBCCDDEEFF00112233445566778899AABBCCDD"; /// A successful Trust maps Category/Action/SourceNode/Actor and Success, with the /// thumbprint carried in the details payload. [Fact] public void Build_trust_success_maps_core_fields() { var evt = CertAuditEvents.Build("Trust", "rejected→trusted", Thumb, "alice", success: true, error: null); evt.Category.ShouldBe("Certificate"); evt.Action.ShouldBe("Trust"); evt.SourceNode.ShouldBe(Thumb); evt.Actor.ShouldBe("alice"); evt.Outcome.ShouldBe(AuditOutcome.Success); evt.EventId.ShouldNotBe(Guid.Empty); evt.OccurredAtUtc.ShouldBeGreaterThan(DateTimeOffset.UtcNow.AddSeconds(-5)); evt.DetailsJson.ShouldNotBeNull(); evt.DetailsJson!.ShouldContain(Thumb); } /// A successful Untrust carries the Untrust action and Success outcome. [Fact] public void Build_untrust_success_maps_action_and_outcome() { var evt = CertAuditEvents.Build("Untrust", "trusted", Thumb, "bob", success: true, error: null); evt.Action.ShouldBe("Untrust"); evt.Outcome.ShouldBe(AuditOutcome.Success); evt.DetailsJson!.ShouldContain(Thumb); } /// A successful Delete carries the Delete action and Success outcome. [Fact] public void Build_delete_success_maps_action_and_outcome() { var evt = CertAuditEvents.Build("Delete", "rejected", Thumb, "carol", success: true, error: null); evt.Action.ShouldBe("Delete"); evt.Outcome.ShouldBe(AuditOutcome.Success); } /// A failed Trust maps the Failure outcome and surfaces the error text in the details /// payload. [Fact] public void Build_trust_failure_maps_failure_and_carries_error() { var evt = CertAuditEvents.Build( "Trust", "rejected", Thumb, "alice", success: false, error: "certificate not found in rejected"); evt.Action.ShouldBe("Trust"); evt.Outcome.ShouldBe(AuditOutcome.Failure); evt.DetailsJson!.ShouldContain("certificate not found in rejected"); } /// A failed Delete maps the Failure outcome. [Fact] public void Build_delete_failure_maps_failure() { var evt = CertAuditEvents.Build( "Delete", "rejected", Thumb, "dave", success: false, error: "delete failed: access denied"); evt.Action.ShouldBe("Delete"); evt.Outcome.ShouldBe(AuditOutcome.Failure); evt.DetailsJson!.ShouldContain("delete failed: access denied"); } /// A failed Untrust maps the Failure outcome. [Fact] public void Build_untrust_failure_maps_failure() { var evt = CertAuditEvents.Build( "Untrust", "trusted", Thumb, "erin", success: false, error: "store write failed"); evt.Action.ShouldBe("Untrust"); evt.Outcome.ShouldBe(AuditOutcome.Failure); evt.DetailsJson!.ShouldContain("store write failed"); } /// On success the error text AND the error key are omitted from the details payload. [Fact] public void Build_success_omits_error_text() { var evt = CertAuditEvents.Build("Trust", "rejected", Thumb, "alice", success: true, error: "ignored"); evt.DetailsJson!.ShouldNotContain("ignored"); evt.DetailsJson!.ShouldNotContain("error"); } /// The public Category constant matches the value stamped onto built events. [Fact] public void Category_constant_matches_built_event() { CertAuditEvents.Category.ShouldBe("Certificate"); CertAuditEvents.Build("Trust", "s", Thumb, "a", success: true, error: null).Category .ShouldBe(CertAuditEvents.Category); } }