using System.Security.Claims; using Microsoft.AspNetCore.Http; using ZB.MOM.WW.Auth.AspNetCore; using ZB.MOM.WW.ScadaBridge.Security; namespace ZB.MOM.WW.ScadaBridge.Security.Tests; /// /// Phase 3 (wire audit Actor from the Auth principal): unit tests for /// . The accessor resolves the audit /// Actor from the authenticated principal on the ambient /// — the canonical username claim /// with an fallback — and /// returns null whenever there is no authenticated interactive user, so the /// caller keeps its existing actor/fallback rather than echoing an unauthenticated /// principal. /// public class HttpAuditActorAccessorTests { /// /// Minimal test double returning a fixed /// (possibly null) . /// private sealed class StubHttpContextAccessor : IHttpContextAccessor { public HttpContext? HttpContext { get; set; } } private static HttpContext AuthenticatedContext(params Claim[] claims) { var ctx = new DefaultHttpContext { User = new ClaimsPrincipal(new ClaimsIdentity(claims, authenticationType: "TestAuth")), }; return ctx; } [Fact] public void CurrentActor_Authenticated_ReturnsUsernameClaim() { var ctx = AuthenticatedContext( new Claim(JwtTokenService.UsernameClaimType, "alice"), // A different Identity.Name proves the username claim is preferred. new Claim(ClaimTypes.Name, "Alice Liddell")); var accessor = new HttpAuditActorAccessor( new StubHttpContextAccessor { HttpContext = ctx }); Assert.Equal("alice", accessor.CurrentActor); } [Fact] public void CurrentActor_Authenticated_NoUsernameClaim_FallsBackToIdentityName() { // No canonical username claim; Identity.Name (pinned to ZbClaimTypes.Name) // is the documented fallback. DefaultHttpContext maps the ClaimTypes.Name // claim onto Identity.Name. var ctx = AuthenticatedContext(new Claim(ClaimTypes.Name, "bob")); var accessor = new HttpAuditActorAccessor( new StubHttpContextAccessor { HttpContext = ctx }); Assert.Equal("bob", accessor.CurrentActor); } [Fact] public void CurrentActor_Authenticated_PrefersUsernameOverZbName() { // Both the canonical username and the canonical name claim present — the // username claim wins. var ctx = AuthenticatedContext( new Claim(JwtTokenService.UsernameClaimType, "svc-user"), new Claim(ZbClaimTypes.Name, "Service User")); var accessor = new HttpAuditActorAccessor( new StubHttpContextAccessor { HttpContext = ctx }); Assert.Equal("svc-user", accessor.CurrentActor); } [Fact] public void CurrentActor_Unauthenticated_ReturnsNull() { // An anonymous identity (no authenticationType) is NOT authenticated — // never echo it back as an actor even if a name claim is somehow present. var ctx = new DefaultHttpContext { User = new ClaimsPrincipal( new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "ghost") })), }; var accessor = new HttpAuditActorAccessor( new StubHttpContextAccessor { HttpContext = ctx }); Assert.Null(accessor.CurrentActor); } [Fact] public void CurrentActor_NoAmbientHttpContext_ReturnsNull() { var accessor = new HttpAuditActorAccessor( new StubHttpContextAccessor { HttpContext = null }); Assert.Null(accessor.CurrentActor); } [Fact] public void CurrentActor_AuthenticatedButNoUsableName_ReturnsNull() { // Authenticated identity carrying only an unrelated claim (no username, // no name) — there is nothing usable to record, so fall back to null. var ctx = AuthenticatedContext(new Claim(ZbClaimTypes.Role, "Administrator")); var accessor = new HttpAuditActorAccessor( new StubHttpContextAccessor { HttpContext = ctx }); Assert.Null(accessor.CurrentActor); } }