using System.Security.Claims; using Microsoft.AspNetCore.Components.Authorization; 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; /// /// R03:2.5 — inside a Blazor circuit there is no HttpContext after the first render, so an /// HTTP-only accessor stamps every audit row from an interactive page "system". The circuit's /// is consulted first; the HTTP context is the fallback. /// public sealed class AuthenticationStateAuditActorAccessorTests { [Fact] public void Circuit_principal_wins_when_there_is_no_http_context() { var accessor = new AuthenticationStateAuditActorAccessor( new HttpContextAccessorStub(null), new FixedStateProvider(Authenticated(new Claim(ZbClaimTypes.Username, "alice")))); accessor.CurrentActor.ShouldBe("alice"); } [Fact] public void Http_principal_is_the_fallback_outside_a_circuit() { var context = new DefaultHttpContext { User = Authenticated(new Claim(ZbClaimTypes.Username, "bob")) }; var accessor = new AuthenticationStateAuditActorAccessor(new HttpContextAccessorStub(context), authenticationState: null); accessor.CurrentActor.ShouldBe("bob"); } [Fact] public void Unauthenticated_circuit_falls_through_to_http_then_null() { var anonymousCircuit = new FixedStateProvider(new ClaimsPrincipal(new ClaimsIdentity())); new AuthenticationStateAuditActorAccessor(new HttpContextAccessorStub(null), anonymousCircuit) .CurrentActor.ShouldBeNull(); var context = new DefaultHttpContext { User = Authenticated(new Claim(ZbClaimTypes.Name, "carol")) }; new AuthenticationStateAuditActorAccessor(new HttpContextAccessorStub(context), anonymousCircuit) .CurrentActor.ShouldBe("carol"); } /// An incomplete state task is "no circuit identity", never a block. [Fact] public void Pending_authentication_state_is_not_awaited() { var accessor = new AuthenticationStateAuditActorAccessor( new HttpContextAccessorStub(null), new PendingStateProvider()); accessor.CurrentActor.ShouldBeNull(); } private static ClaimsPrincipal Authenticated(params Claim[] claims) => new(new ClaimsIdentity(claims, "TestScheme", ZbClaimTypes.Name, ZbClaimTypes.Role)); private sealed class HttpContextAccessorStub(HttpContext? context) : IHttpContextAccessor { public HttpContext? HttpContext { get; set; } = context; } private sealed class FixedStateProvider(ClaimsPrincipal user) : AuthenticationStateProvider { public override Task GetAuthenticationStateAsync() => Task.FromResult(new AuthenticationState(user)); } private sealed class PendingStateProvider : AuthenticationStateProvider { public override Task GetAuthenticationStateAsync() => new TaskCompletionSource().Task; } }