Skip to content

Commit ba8e3e7

Browse files
committed
UAuthApp & UAuthStateView Tests
1 parent a9d0520 commit ba8e3e7

File tree

4 files changed

+366
-2
lines changed

4 files changed

+366
-2
lines changed

src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthStateView.razor.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,18 @@ private async Task<bool> EvaluateAuthorizationAsync()
9898
var results = new List<bool>();
9999

100100
if (roles.Count > 0)
101-
results.Add(roles.Any(AuthState.IsInRole));
101+
{
102+
results.Add(MatchAll
103+
? roles.All(AuthState.IsInRole)
104+
: roles.Any(AuthState.IsInRole));
105+
}
102106

103107
if (permissions.Count > 0)
104-
results.Add(permissions.Any(AuthState.HasPermission));
108+
{
109+
results.Add(MatchAll
110+
? permissions.All(AuthState.HasPermission)
111+
: permissions.Any(AuthState.HasPermission));
112+
}
105113

106114
if (!string.IsNullOrWhiteSpace(Policy))
107115
results.Add(await EvaluatePolicyAsync());
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using Bunit;
2+
using CodeBeam.UltimateAuth.Client;
3+
using CodeBeam.UltimateAuth.Client.Abstractions;
4+
using CodeBeam.UltimateAuth.Client.Blazor;
5+
using CodeBeam.UltimateAuth.Client.Infrastructure;
6+
using CodeBeam.UltimateAuth.Tests.Unit.Helpers;
7+
using FluentAssertions;
8+
using Microsoft.AspNetCore.Components;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Moq;
11+
12+
namespace CodeBeam.UltimateAuth.Tests.Unit;
13+
14+
public class UAuthAppTests
15+
{
16+
17+
private (BunitContext ctx, Mock<IUAuthStateManager> stateManager, Mock<IUAuthClientBootstrapper> bootstrapper, Mock<ISessionCoordinator> coordinator)
18+
19+
CreateUAuthAppTestContext(UAuthState state, bool authenticated = true)
20+
{
21+
var ctx = new BunitContext();
22+
23+
var stateManager = new Mock<IUAuthStateManager>();
24+
stateManager.Setup(x => x.State).Returns(state);
25+
stateManager.Setup(x => x.EnsureAsync(It.IsAny<bool>()))
26+
.Returns(Task.CompletedTask);
27+
28+
var bootstrapper = new Mock<IUAuthClientBootstrapper>();
29+
bootstrapper.Setup(x => x.EnsureStartedAsync())
30+
.Returns(Task.CompletedTask);
31+
32+
var coordinator = new Mock<ISessionCoordinator>();
33+
coordinator.Setup(x => x.StartAsync()).Returns(Task.CompletedTask);
34+
coordinator.Setup(x => x.StopAsync()).Returns(Task.CompletedTask);
35+
36+
ctx.Services.AddSingleton(stateManager.Object);
37+
ctx.Services.AddSingleton(bootstrapper.Object);
38+
ctx.Services.AddSingleton(coordinator.Object);
39+
40+
var auth = ctx.AddAuthorization();
41+
if (authenticated)
42+
auth.SetAuthorized("test-user");
43+
else
44+
auth.SetNotAuthorized();
45+
46+
return (ctx, stateManager, bootstrapper, coordinator);
47+
}
48+
49+
[Fact]
50+
public async Task Should_Initialize_And_Bootstrap_On_First_Render()
51+
{
52+
var state = TestAuthState.Anonymous();
53+
var (ctx, stateManager, bootstrapper, _) = CreateUAuthAppTestContext(state);
54+
var cut = ctx.Render<UAuthApp>();
55+
await cut.InvokeAsync(() => Task.CompletedTask);
56+
57+
bootstrapper.Verify(x => x.EnsureStartedAsync(), Times.Once);
58+
stateManager.Verify(x => x.EnsureAsync(It.IsAny<bool>()), Times.AtLeastOnce);
59+
}
60+
61+
[Fact]
62+
public async Task Should_Start_Coordinator_When_Authenticated()
63+
{
64+
var state = TestAuthState.Authenticated();
65+
var (ctx, _, _, coordinator) = CreateUAuthAppTestContext(state);
66+
var cut = ctx.Render<UAuthApp>();
67+
await cut.InvokeAsync(() => Task.CompletedTask);
68+
69+
coordinator.Verify(x => x.StartAsync(), Times.Once);
70+
}
71+
72+
[Fact]
73+
public async Task Should_Stop_Coordinator_When_State_Cleared()
74+
{
75+
var state = TestAuthState.Authenticated();
76+
var (ctx, _, _, coordinator) = CreateUAuthAppTestContext(state);
77+
var cut = ctx.Render<UAuthApp>();
78+
state.Clear();
79+
await cut.InvokeAsync(() => Task.CompletedTask);
80+
81+
coordinator.Verify(x => x.StopAsync(), Times.Once);
82+
}
83+
84+
[Fact]
85+
public async Task Should_Stop_Coordinator_On_Dispose()
86+
{
87+
var state = TestAuthState.Authenticated();
88+
var (ctx, _, _, coordinator) = CreateUAuthAppTestContext(state);
89+
var cut = ctx.Render<UAuthApp>();
90+
await cut.Instance.DisposeAsync();
91+
92+
coordinator.Verify(x => x.StopAsync(), Times.Once);
93+
}
94+
95+
[Fact]
96+
public async Task Should_Call_Ensure_When_State_Is_Stale()
97+
{
98+
var state = TestAuthState.Authenticated();
99+
state.MarkStale();
100+
var (ctx, stateManager, _, _) = CreateUAuthAppTestContext(state);
101+
var cut = ctx.Render<UAuthApp>();
102+
await cut.InvokeAsync(() => Task.CompletedTask);
103+
104+
stateManager.Verify(x => x.EnsureAsync(true), Times.AtLeastOnce);
105+
}
106+
107+
[Fact]
108+
public async Task Should_Invoke_Callback_On_Reauth()
109+
{
110+
var state = TestAuthState.Authenticated();
111+
var (ctx, _, _, coordinator) = CreateUAuthAppTestContext(state);
112+
var called = false;
113+
var cut = ctx.Render<UAuthApp>(p => p.Add(x => x.OnReauthRequired, EventCallback.Factory.Create(this, () => called = true)));
114+
115+
await cut.InvokeAsync(() => Task.CompletedTask);
116+
coordinator.Raise(x => x.ReauthRequired += null);
117+
await cut.InvokeAsync(() => Task.CompletedTask);
118+
119+
called.Should().BeTrue();
120+
}
121+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using Bunit;
2+
using CodeBeam.UltimateAuth.Authorization.Contracts;
3+
using CodeBeam.UltimateAuth.Authorization.Reference;
4+
using CodeBeam.UltimateAuth.Client;
5+
using CodeBeam.UltimateAuth.Client.Blazor;
6+
using CodeBeam.UltimateAuth.Core.Domain;
7+
using CodeBeam.UltimateAuth.Tests.Unit.Helpers;
8+
using FluentAssertions;
9+
using Microsoft.AspNetCore.Components;
10+
using Microsoft.Extensions.DependencyInjection;
11+
using Moq;
12+
using System.Security.Claims;
13+
14+
namespace CodeBeam.UltimateAuth.Tests.Unit;
15+
16+
public class UAuthStateViewTests
17+
{
18+
private IRenderedComponent<UAuthStateView> RenderWithAuth(BunitContext ctx, UAuthState state, Action<ComponentParameterCollectionBuilder<UAuthStateView>> parameters)
19+
{
20+
var wrapper = ctx.Render<CascadingValue<UAuthState>>(p => p
21+
.Add(x => x.Value, state)
22+
.AddChildContent<UAuthStateView>(parameters)
23+
);
24+
25+
return wrapper.FindComponent<UAuthStateView>();
26+
}
27+
28+
private static RenderFragment Html(string html)
29+
=> b => b.AddMarkupContent(0, html);
30+
31+
[Fact]
32+
public void Should_Render_NotAuthorized_When_Not_Authenticated()
33+
{
34+
using var ctx = new BunitContext();
35+
var state = UAuthState.Anonymous();
36+
ctx.Services.AddSingleton(Mock.Of<IAuthorizationService>());
37+
38+
var cut = ctx.Render<CascadingValue<UAuthState>>(parameters => parameters
39+
.Add(p => p.Value, state)
40+
.AddChildContent<UAuthStateView>(child => child
41+
.Add(p => p.NotAuthorized, (RenderFragment)(b => b.AddMarkupContent(0, "<div>nope</div>")))
42+
)
43+
);
44+
45+
cut.Markup.Should().Contain("nope");
46+
}
47+
48+
[Fact]
49+
public void Should_Render_ChildContent_When_Authorized_Without_Conditions()
50+
{
51+
using var ctx = new BunitContext();
52+
var state = TestAuthState.Authenticated();
53+
ctx.Services.AddSingleton(Mock.Of<IAuthorizationService>());
54+
55+
var cut = RenderWithAuth(ctx, state, p => p
56+
.Add(x => x.ChildContent, s => b => b.AddContent(0, "authorized"))
57+
);
58+
59+
cut.Markup.Should().Contain("authorized");
60+
}
61+
62+
[Fact]
63+
public void Should_Render_NotAuthorized_When_Role_Not_Match()
64+
{
65+
using var ctx = new BunitContext();
66+
var state = TestAuthState.WithRoles("user");
67+
ctx.Services.AddSingleton(Mock.Of<IAuthorizationService>());
68+
69+
var cut = RenderWithAuth(ctx, state, p => p
70+
.Add(x => x.Roles, "admin")
71+
.Add(x => x.NotAuthorized, Html("<div>nope</div>"))
72+
);
73+
74+
cut.Markup.Should().Contain("nope");
75+
}
76+
77+
[Fact]
78+
public void Should_Render_Authorized_When_Role_Matches()
79+
{
80+
using var ctx = new BunitContext();
81+
var state = TestAuthState.WithRoles("admin");
82+
ctx.Services.AddSingleton(Mock.Of<IAuthorizationService>());
83+
84+
var cut = RenderWithAuth(ctx, state, p => p
85+
.Add(x => x.Roles, "admin")
86+
.Add(x => x.Authorized, s => b => b.AddContent(0, "ok"))
87+
);
88+
89+
cut.Markup.Should().Contain("ok");
90+
}
91+
92+
[Fact]
93+
public void Should_Check_Permissions()
94+
{
95+
using var ctx = new BunitContext();
96+
var state = TestAuthState.WithPermissions("read");
97+
ctx.Services.AddSingleton(Mock.Of<IAuthorizationService>());
98+
99+
var cut = RenderWithAuth(ctx, state, p => p
100+
.Add(x => x.Permissions, "write")
101+
.Add(x => x.NotAuthorized, Html("<div>no</div>"))
102+
);
103+
104+
cut.Markup.Should().Contain("no");
105+
}
106+
107+
[Fact]
108+
public void Should_Require_All_When_MatchAll_True()
109+
{
110+
using var ctx = new BunitContext();
111+
var state = TestAuthState.WithRoles("admin");
112+
ctx.Services.AddSingleton(Mock.Of<IAuthorizationService>());
113+
114+
var cut = RenderWithAuth(ctx, state, p => p
115+
.Add(x => x.Roles, "admin,user")
116+
.Add(x => x.MatchAll, true)
117+
.Add(x => x.NotAuthorized, Html("<div>no</div>"))
118+
);
119+
120+
cut.Markup.Should().Contain("no");
121+
}
122+
123+
[Fact]
124+
public void Should_Allow_Any_When_MatchAll_False()
125+
{
126+
using var ctx = new BunitContext();
127+
var state = TestAuthState.WithRoles("admin");
128+
ctx.Services.AddSingleton(Mock.Of<IAuthorizationService>());
129+
130+
var cut = RenderWithAuth(ctx, state, p => p
131+
.Add(x => x.Roles, "admin,user")
132+
.Add(x => x.MatchAll, false)
133+
.Add(x => x.Authorized, s => b => b.AddContent(0, "ok"))
134+
);
135+
136+
cut.Markup.Should().Contain("ok");
137+
}
138+
139+
[Fact]
140+
public void Should_Render_Inactive_When_Session_Not_Active()
141+
{
142+
using var ctx = new BunitContext();
143+
var state = TestAuthState.WithSession(SessionState.Revoked);
144+
ctx.Services.AddSingleton(Mock.Of<IAuthorizationService>());
145+
146+
var cut = RenderWithAuth(ctx, state, p => p
147+
.Add(x => x.Inactive, s => b => b.AddContent(0, "inactive"))
148+
);
149+
150+
cut.Markup.Should().Contain("inactive");
151+
}
152+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using CodeBeam.UltimateAuth.Client;
2+
using CodeBeam.UltimateAuth.Core.Contracts;
3+
using CodeBeam.UltimateAuth.Core.Domain;
4+
using CodeBeam.UltimateAuth.Core.MultiTenancy;
5+
using System.Security.Claims;
6+
7+
namespace CodeBeam.UltimateAuth.Tests.Unit.Helpers;
8+
9+
public static class TestAuthState
10+
{
11+
public static UAuthState Anonymous()
12+
=> UAuthState.Anonymous();
13+
14+
public static UAuthState Authenticated(
15+
string userId = "user-1",
16+
params (string Type, string Value)[] claims)
17+
{
18+
var state = UAuthState.Anonymous();
19+
20+
var identity = new AuthIdentitySnapshot
21+
{
22+
UserKey = UserKey.FromString(userId),
23+
Tenant = TenantKeys.Single,
24+
SessionState = SessionState.Active,
25+
UserStatus = UserStatus.Active
26+
};
27+
28+
var snapshot = new AuthStateSnapshot
29+
{
30+
Identity = identity,
31+
Claims = ClaimsSnapshot.From(claims)
32+
};
33+
34+
state.ApplySnapshot(snapshot, DateTimeOffset.UtcNow);
35+
36+
return state;
37+
}
38+
39+
public static UAuthState WithRoles(params string[] roles)
40+
{
41+
return Authenticated(
42+
claims: roles.Select(r => (ClaimTypes.Role, r)).ToArray());
43+
}
44+
45+
public static UAuthState WithPermissions(params string[] permissions)
46+
{
47+
return Authenticated(
48+
claims: permissions.Select(p => ("uauth:permission", p)).ToArray());
49+
}
50+
51+
public static UAuthState WithSession(SessionState sessionState)
52+
{
53+
var state = Authenticated();
54+
55+
var identity = state.Identity! with
56+
{
57+
SessionState = sessionState
58+
};
59+
60+
var snapshot = new AuthStateSnapshot
61+
{
62+
Identity = identity,
63+
Claims = state.Claims
64+
};
65+
66+
state.ApplySnapshot(snapshot, DateTimeOffset.UtcNow);
67+
68+
return state;
69+
}
70+
71+
public static UAuthState Full(
72+
string userId,
73+
string[] roles,
74+
string[] permissions)
75+
{
76+
var claims = new List<(string, string)>();
77+
78+
claims.AddRange(roles.Select(r => (ClaimTypes.Role, r)));
79+
claims.AddRange(permissions.Select(p => ("uauth:permission", p)));
80+
81+
return Authenticated(userId, claims.ToArray());
82+
}
83+
}

0 commit comments

Comments
 (0)