using System.Security.Claims; using Microsoft.AspNetCore.Http; using Shouldly; using Xunit; using ZB.MOM.WW.Auth.AspNetCore; using ZB.MOM.WW.OtOpcUa.Security.Audit; namespace ZB.MOM.WW.OtOpcUa.Security.Tests.Audit; /// /// Unit tests for . /// /// Covers the three cases: /// /// Authenticated principal with a claim → /// returns the username claim value. /// Authenticated principal with only a / no /// username claim → falls back to the Name claim. /// No HTTP context (null) or unauthenticated principal → returns /// . /// /// /// public sealed class HttpAuditActorAccessorTests { // ── helpers ────────────────────────────────────────────────────────────────── private static IHttpContextAccessor ContextWith(ClaimsPrincipal principal) { var context = new DefaultHttpContext { User = principal }; return new HttpContextAccessorStub(context); } private static IHttpContextAccessor NoContext() => new HttpContextAccessorStub(null); private static ClaimsPrincipal AuthenticatedWith(params Claim[] claims) { var identity = new ClaimsIdentity( claims, authenticationType: "TestScheme", // non-null authenticationType → IsAuthenticated = true nameType: ZbClaimTypes.Name, roleType: ZbClaimTypes.Role); return new ClaimsPrincipal(identity); } private static ClaimsPrincipal Unauthenticated() => new(new ClaimsIdentity()); // no authenticationType → IsAuthenticated = false // ── tests ───────────────────────────────────────────────────────────────────── /// /// An authenticated principal that carries /// returns exactly that claim value — it is the canonical actor string. /// [Fact] public void Returns_username_claim_for_authenticated_principal() { var principal = AuthenticatedWith( new Claim(ZbClaimTypes.Username, "alice"), new Claim(ZbClaimTypes.Name, "alice-name"), new Claim(ZbClaimTypes.DisplayName, "Alice User")); var sut = new HttpAuditActorAccessor(ContextWith(principal)); sut.CurrentActor.ShouldBe("alice"); } /// /// When the principal has no claim but does have /// a claim, the Name claim value is returned as the /// fallback actor. /// [Fact] public void Falls_back_to_Name_claim_when_Username_claim_is_absent() { var principal = AuthenticatedWith( new Claim(ZbClaimTypes.Name, "bob")); var sut = new HttpAuditActorAccessor(ContextWith(principal)); sut.CurrentActor.ShouldBe("bob"); } /// /// An unauthenticated principal (Identity.IsAuthenticated == false) returns null — /// the caller's fallback (typically ) is used. /// [Fact] public void Returns_null_for_unauthenticated_principal() { var sut = new HttpAuditActorAccessor(ContextWith(Unauthenticated())); sut.CurrentActor.ShouldBeNull(); } /// /// When there is no current HttpContext (e.g. background task, actor mailbox /// worker), returns null. /// [Fact] public void Returns_null_when_no_HttpContext() { var sut = new HttpAuditActorAccessor(NoContext()); sut.CurrentActor.ShouldBeNull(); } // ── stub ────────────────────────────────────────────────────────────────────── private sealed class HttpContextAccessorStub(HttpContext? context) : IHttpContextAccessor { public HttpContext? HttpContext { get; set; } = context; } }