From ab43a39368b200fcdd523fd056dce258aab2eaab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?=
<78308169+mckaragoz@users.noreply.github.com>
Date: Sat, 31 Oct 2026 23:52:48 +0300
Subject: [PATCH 1/3] test(identity-lab): add comprehensive bUnit page render
coverage
---
...Beam.UltimateAuth.IdentityLab.Tests.csproj | 2 +
.../README.md | 18 +-
.../PageRenderContext.ApplicationPages.cs | 135 ++++++++++++
.../Rendering/PageRenderContext.cs | 144 +++++++++++++
.../Rendering/PageRenderTests.cs | 154 ++++++++++++++
.../Rendering/RemainingPageRenderTests.cs | 192 ++++++++++++++++++
.../Rendering/RouteSmokeCoverageTests.cs | 59 ++++++
.../docs/page-render-coverage.md | 143 +++++++++++++
8 files changed, 845 insertions(+), 2 deletions(-)
create mode 100644 tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderContext.ApplicationPages.cs
create mode 100644 tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderContext.cs
create mode 100644 tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderTests.cs
create mode 100644 tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/RemainingPageRenderTests.cs
create mode 100644 tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/RouteSmokeCoverageTests.cs
create mode 100644 tests/CodeBeam.UltimateAuth.IdentityLab.Tests/docs/page-render-coverage.md
diff --git a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/CodeBeam.UltimateAuth.IdentityLab.Tests.csproj b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/CodeBeam.UltimateAuth.IdentityLab.Tests.csproj
index a33d9b29..8adc52da 100644
--- a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/CodeBeam.UltimateAuth.IdentityLab.Tests.csproj
+++ b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/CodeBeam.UltimateAuth.IdentityLab.Tests.csproj
@@ -11,6 +11,8 @@
+
+
diff --git a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/README.md b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/README.md
index c5afa29d..20985388 100644
--- a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/README.md
+++ b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/README.md
@@ -1,12 +1,12 @@
# Identity Lab security suite
-Requires .NET 10 SDK and Docker with permission to start containers. Run from the repository root:
+The security and integration tests require .NET 10 SDK and Docker with permission to start containers. Run from the repository root:
```sh
dotnet test tests/CodeBeam.UltimateAuth.IdentityLab.Tests -c Release --logger trx
```
-By default, each database test class starts PostgreSQL 17 through Testcontainers. Every test gets a new database, applies the actual LabDbContext migrations, and drops its database on disposal. Tests never use EF InMemory. Nothing connects to application production databases.
+By default, each database test class starts PostgreSQL 17 through Testcontainers. Every test gets a new database, applies the actual LabDbContext migrations, and drops its database on disposal. Security and integration tests never use EF InMemory. Nothing connects to application production databases.
Alternatively set `IDENTITYLAB_TEST_POSTGRES` to a connection string for a **dedicated test PostgreSQL instance**. Its login needs CREATE DATABASE and permission to drop the databases it creates. Only databases named `identitylab_test_` are created/dropped. This setting bypasses Docker; it does not bypass relational tests. Missing infrastructure fails fixture initialization rather than silently skipping tests.
@@ -15,3 +15,17 @@ Tests replace authenticated-context sources and selected framework store/client
`Regression/SystemRoleIdentityTests.cs` records an expected rejection that the inspected implementation does not enforce. It is intentionally neither skipped nor inverted to accept unsafe behavior. The `KnownDefect=SystemRoleIdentity` trait identifies it; normal local and CI runs include it. Expect that regression to fail until separately approved production work addresses the issue. Do not enable this suite as a required green check before defect triage.
See [coverage matrix](docs/security-coverage.md) and [execution report](docs/execution-report.md). The project is included in the solution. Repository CI excludes it from the general matrix test step and runs it in a dedicated .NET 10 PostgreSQL job; the dedicated job uploads TRX results even on failure.
+
+## Page render suite
+
+The `Rendering` tests use bUnit and need only the .NET 10 SDK; no Docker or PostgreSQL is required:
+
+```sh
+dotnet test tests/CodeBeam.UltimateAuth.IdentityLab.Tests/CodeBeam.UltimateAuth.IdentityLab.Tests.csproj -c Release --filter Category=Render
+```
+
+The real pages and child components render with MudBlazor services and a popover provider. External client calls use explicit Moq responses. Dashboard initialization uses real Lab services with a uniquely named EF InMemory LabDbContext store; this verifies component initialization and navigation branches, not relational behavior. Existing service regression tests and their PostgreSQL fixtures are unchanged. Direct page rendering does not exercise router authorization or layouts.
+
+All 24 routable application page components have smoke test mappings (28 page-render cases and one route inventory guard). New pages need a real render test annotated with `[SmokeTestFor(typeof(PageComponent))]`; the route guard reports missing types and their route aliases. Existing behavioral render cases are preserved. No additional dependencies were introduced by the completion phase.
+
+See [page inventory and validation limits](docs/page-render-coverage.md).
diff --git a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderContext.ApplicationPages.cs b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderContext.ApplicationPages.cs
new file mode 100644
index 00000000..5f994ee9
--- /dev/null
+++ b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderContext.ApplicationPages.cs
@@ -0,0 +1,135 @@
+using CodeBeam.UltimateAuth.Authorization;
+using CodeBeam.UltimateAuth.Authorization.Contracts;
+using CodeBeam.UltimateAuth.Client.Diagnostics;
+using CodeBeam.UltimateAuth.Core.Contracts;
+using CodeBeam.UltimateAuth.Core.Domain;
+using CodeBeam.UltimateAuth.Core.MultiTenancy;
+using CodeBeam.UltimateAuth.Core.Runtime;
+using CodeBeam.UltimateAuth.IdentityLab.Authorization;
+using CodeBeam.UltimateAuth.IdentityLab.Contracts;
+using CodeBeam.UltimateAuth.IdentityLab.Services;
+using CodeBeam.UltimateAuth.Server.Auth;
+using CodeBeam.UltimateAuth.Users.Contracts;
+using Microsoft.Extensions.DependencyInjection;
+using Moq;
+
+namespace CodeBeam.UltimateAuth.IdentityLab.Tests.Rendering;
+
+internal sealed partial class PageRenderContext
+{
+ public static UserKey RenderUser => UserKey.FromString("render-user");
+
+ public void AddManagementServices()
+ {
+ AddLabServices();
+ var summaries = new Mock(MockBehavior.Strict);
+ summaries.Setup(x => x.GetByKeysAsync(It.IsAny>(), It.IsAny()))
+ .ReturnsAsync(new[] { new UserSummary
+ {
+ UserKey = RenderUser, DisplayName = "Render Person", UserName = "render-person",
+ PrimaryEmail = "render@example.test", Status = UserStatus.Active
+ } });
+ var summaryFactory = new Mock(MockBehavior.Strict);
+ summaryFactory.Setup(x => x.Create(TenantKeys.Single)).Returns(summaries.Object);
+ Services.AddSingleton(summaryFactory.Object);
+ Services.AddScoped();
+ Services.AddScoped();
+ Services.AddScoped();
+ Services.AddScoped();
+ Services.AddScoped();
+ Services.AddScoped();
+ var product = new Mock(MockBehavior.Strict);
+ product.Setup(x => x.Get()).Returns(new UAuthProductInfo { Version = "render-test" });
+ Services.AddSingleton(product.Object);
+ }
+
+ public void SetupProfileAndAccess()
+ {
+ Users.Setup(x => x.GetMeAsync(It.IsAny()))
+ .ReturnsAsync(Success(new UserView
+ {
+ UserKey = RenderUser, DisplayName = "Render Person", UserName = "render-person",
+ PrimaryEmail = "render@example.test", Status = UserStatus.Active
+ }));
+ Identifiers.Setup(x => x.GetMyAsync(It.IsAny()))
+ .ReturnsAsync(Success(Empty()));
+ Sessions.Setup(x => x.GetMyChainsAsync(It.IsAny()))
+ .ReturnsAsync(Success(Empty()));
+ Authorization.Setup(x => x.GetMyRolesAsync(It.IsAny()))
+ .ReturnsAsync(Success(new UserRolesGetResult { UserKey = RenderUser, Roles = Empty() }));
+ Authorization.Setup(x => x.GetMyPermissionsAsync())
+ .ReturnsAsync(Success(new UserPermissionsResult { Permissions = Array.Empty() }));
+ }
+
+ public void AddAdminServices()
+ {
+ AddManagementServices();
+ var role = Role.Create(LabRoleIds.Admin, TenantKeys.Single, "Ladmin", null, DateTimeOffset.UtcNow);
+ var roles = new Mock(MockBehavior.Strict);
+ roles.Setup(x => x.GetByIdsAsync(It.IsAny>(), It.IsAny()))
+ .ReturnsAsync(new[] { role });
+ var roleFactory = new Mock(MockBehavior.Strict);
+ roleFactory.Setup(x => x.Create(TenantKeys.Single)).Returns(roles.Object);
+ var assignments = new Mock(MockBehavior.Strict);
+ assignments.Setup(x => x.GetAssignmentsAsync(RenderUser, It.IsAny()))
+ .ReturnsAsync(new[] { new UserRole { Tenant = TenantKeys.Single, UserKey = RenderUser, RoleId = role.Id } });
+ var assignmentFactory = new Mock(MockBehavior.Strict);
+ assignmentFactory.Setup(x => x.Create(TenantKeys.Single)).Returns(assignments.Object);
+ Services.AddSingleton(roleFactory.Object);
+ Services.AddSingleton(assignmentFactory.Object);
+ Services.AddSingleton(Mock.Of());
+ Services.AddScoped();
+ Services.AddScoped();
+ }
+
+ public void AddDemoStoreServices()
+ {
+ AddLabServices();
+ var flow = new Mock(MockBehavior.Strict);
+ flow.Setup(x => x.BeginAsync(It.IsAny(), It.IsAny()))
+ .ReturnsAsync(Contexts.AuthenticatedFlow(RenderUser));
+ var contexts = new Mock(MockBehavior.Strict);
+ contexts.Setup(x => x.CreateAsync(It.IsAny(), It.IsAny(), It.IsAny(),
+ It.IsAny(), It.IsAny>(), It.IsAny()))
+ .ReturnsAsync(Contexts.Create(LabDemoPermissions.ProductsRead, RenderUser));
+ Services.AddSingleton(flow.Object);
+ Services.AddSingleton(contexts.Object);
+ // An active sandbox member without demo role assignments is a valid empty-access state.
+ var assignments = new Mock(MockBehavior.Strict);
+ assignments.Setup(x => x.GetAssignmentsAsync(RenderUser, It.IsAny()))
+ .ReturnsAsync(Array.Empty());
+ var factory = new Mock(MockBehavior.Strict);
+ factory.Setup(x => x.Create(TenantKeys.Single)).Returns(assignments.Object);
+ Services.AddSingleton(factory.Object);
+ var roles = new Mock(MockBehavior.Strict);
+ roles.Setup(x => x.GetByIdsAsync(It.IsAny>(), It.IsAny()))
+ .ReturnsAsync(Array.Empty());
+ var roleFactory = new Mock(MockBehavior.Strict);
+ roleFactory.Setup(x => x.Create(TenantKeys.Single)).Returns(roles.Object);
+ Services.AddSingleton(roleFactory.Object);
+ Services.AddScoped();
+ }
+
+ public void AddDiagnosticsServices()
+ {
+ Services.AddSingleton(new UAuthClientDiagnostics());
+ Services.AddScoped();
+ var chainId = SessionChainId.New();
+ var sessionId = AuthSessionId.Parse(new string('s', 32));
+ var now = DateTimeOffset.UtcNow;
+ var session = new SessionInfo(sessionId, now.AddMinutes(-1), now.AddHours(1), false);
+ var chain = new SessionChainSummary
+ {
+ ChainId = chainId, ActiveSessionId = sessionId, IsCurrentDevice = true,
+ State = SessionChainState.Active, CreatedAt = session.CreatedAt, LastSeenAt = now
+ };
+ Sessions.Setup(x => x.GetMyChainsAsync(It.IsAny()))
+ .ReturnsAsync(Success(new PagedResult(new[] { chain }, 1, 1, 20, null, false)));
+ Sessions.Setup(x => x.GetMyChainDetailAsync(chainId))
+ .ReturnsAsync(Success(new SessionChainDetail
+ {
+ ChainId = chainId, ActiveSessionId = sessionId, State = SessionChainState.Active,
+ CreatedAt = session.CreatedAt, LastSeenAt = now, Sessions = new[] { session }
+ }));
+ }
+}
diff --git a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderContext.cs b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderContext.cs
new file mode 100644
index 00000000..98733895
--- /dev/null
+++ b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderContext.cs
@@ -0,0 +1,144 @@
+using System.Reflection;
+using System.Security.Claims;
+using Bunit;
+using CodeBeam.UltimateAuth.Authorization;
+using CodeBeam.UltimateAuth.Authorization.Contracts;
+using CodeBeam.UltimateAuth.Client;
+using CodeBeam.UltimateAuth.Client.Blazor;
+using CodeBeam.UltimateAuth.Client.Blazor.Extensions;
+using CodeBeam.UltimateAuth.Client.Options;
+using CodeBeam.UltimateAuth.Client.Services;
+using CodeBeam.UltimateAuth.Core.Abstractions;
+using CodeBeam.UltimateAuth.Core.Contracts;
+using CodeBeam.UltimateAuth.Core.Domain;
+using CodeBeam.UltimateAuth.Core.MultiTenancy;
+using CodeBeam.UltimateAuth.IdentityLab.Authorization;
+using CodeBeam.UltimateAuth.IdentityLab.Domain;
+using CodeBeam.UltimateAuth.IdentityLab.Persistence;
+using CodeBeam.UltimateAuth.IdentityLab.Services;
+using CodeBeam.UltimateAuth.Server.Auth;
+using CodeBeam.UltimateAuth.Server.Options;
+using CodeBeam.UltimateAuth.Server.Services;
+using CodeBeam.UltimateAuth.Users.Contracts;
+using CodeBeam.UltimateAuth.Users.Reference;
+using Microsoft.AspNetCore.Components;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Moq;
+using MudBlazor;
+using MudBlazor.Services;
+using MudExtensions.Services;
+
+namespace CodeBeam.UltimateAuth.IdentityLab.Tests.Rendering;
+
+internal sealed partial class PageRenderContext : BunitContext
+{
+ public Mock Client { get; } = new(MockBehavior.Strict);
+ public Mock Sessions { get; } = new(MockBehavior.Strict);
+ public Mock Authorization { get; } = new(MockBehavior.Strict);
+ public Mock Identifiers { get; } = new(MockBehavior.Strict);
+ public Mock Users { get; } = new(MockBehavior.Strict);
+
+ public PageRenderContext()
+ {
+ // MudBlazor's DOM measurement/focus calls have no browser in bUnit.
+ JSInterop.Mode = JSRuntimeMode.Loose;
+ Services.AddLogging();
+ Services.AddMudServices();
+ Services.AddMudExtensions();
+ Services.AddAuthorizationCore();
+ // Keep the real internal evaluator, without installing the production client host.
+ var registrations = new ServiceCollection();
+ registrations.AddUltimateAuthClientBlazor();
+ ((IServiceCollection)Services).Add(registrations.Single(x => x.ServiceType == typeof(IUAuthAuthorizationEvaluator)));
+ Client.SetupGet(x => x.Sessions).Returns(Sessions.Object);
+ Client.SetupGet(x => x.Authorization).Returns(Authorization.Object);
+ Client.SetupGet(x => x.Users).Returns(Users.Object);
+ Client.SetupGet(x => x.Identifiers).Returns(Identifiers.Object);
+ Services.AddSingleton(Client.Object);
+ Services.AddSingleton(Options.Create(new UAuthClientOptions()));
+ Services.AddSingleton(Options.Create(new UAuthServerOptions()));
+ var device = new Mock();
+ device.Setup(x => x.GetOrCreateAsync(It.IsAny()))
+ .Returns(new ValueTask(DeviceId.Create("render-test-device-0001")));
+ Services.AddSingleton(device.Object);
+ Services.AddSingleton(Mock.Of());
+ Services.AddSingleton(Mock.Of());
+ Services.AddSingleton(Mock.Of());
+ }
+
+ public IRenderedComponent RenderPage(UAuthState? state = null) where T : IComponent
+ {
+ // Register page-specific dependencies before the first render freezes DI.
+ Render();
+ var authState = state ?? UAuthState.Anonymous();
+ return Render>>(p => p
+ .Add(x => x.Value, Task.FromResult(new Microsoft.AspNetCore.Components.Authorization.AuthenticationState(authState.ToClaimsPrincipal())))
+ .AddChildContent>(child => child
+ .Add(x => x.Value, authState)
+ .AddChildContent())).FindComponent();
+ }
+
+ public static UAuthState Authenticated(string role, params string[] permissions)
+ {
+ var state = UAuthState.Anonymous();
+ // Use the actual snapshot lifecycle; no production friend assembly or API changes.
+ typeof(UAuthState).GetMethod("ApplySnapshot", BindingFlags.Instance | BindingFlags.NonPublic)!
+ .Invoke(state, [new AuthStateSnapshot
+ {
+ Identity = new AuthIdentitySnapshot
+ {
+ UserKey = UserKey.FromString("render-user"),
+ Tenant = TenantKeys.Single,
+ SessionState = SessionState.Active,
+ UserStatus = UserStatus.Active
+ },
+ Claims = ClaimsSnapshot.From(new[] { (ClaimTypes.Role, role) }
+ .Concat(permissions.Select(permission => ("uauth:permission", permission))))
+ }, DateTimeOffset.UtcNow]);
+ return state;
+ }
+
+ public void AddLabServices()
+ {
+ // Only LabDbContext uses this isolated store. No server host, migrations or PostgreSQL.
+ var databaseName = Guid.NewGuid().ToString();
+ Services.AddDbContextFactory(o => o.UseInMemoryDatabase(databaseName));
+ Services.AddSingleton(Mock.Of());
+ Services.AddSingleton(Mock.Of());
+ Services.AddSingleton(Mock.Of());
+ Services.AddSingleton(Mock.Of());
+ Services.AddSingleton(Mock.Of());
+ Services.AddSingleton(Mock.Of());
+ Services.AddSingleton(Mock.Of());
+ Services.AddScoped();
+ Services.AddScoped();
+ Services.AddScoped();
+ Services.AddScoped();
+ Services.AddScoped();
+ Services.AddScoped();
+ Services.AddScoped();
+ }
+
+ public async Task SeedSandboxAsync(bool owner)
+ {
+ await using var db = await Services.GetRequiredService>().CreateDbContextAsync();
+ var sandbox = new Sandbox
+ {
+ Name = "Render sandbox",
+ OwnerUserKey = UserKey.FromString(owner ? "render-user" : "other-owner"),
+ Settings = new SandboxSettings { MaxUsers = 5 }
+ };
+ sandbox.Members.Add(new SandboxMember
+ {
+ UserKey = UserKey.FromString("render-user"),
+ Role = owner ? SandboxMemberRole.Owner : SandboxMemberRole.Member
+ });
+ db.Sandboxes.Add(sandbox);
+ await db.SaveChangesAsync();
+ }
+
+ public static UAuthResult Success(T value) => new() { IsSuccess = true, Status = 200, Value = value };
+ public static PagedResult Empty() => new(Array.Empty(), 0, 1, 20, null, false);
+}
diff --git a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderTests.cs b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderTests.cs
new file mode 100644
index 00000000..e0ab26a1
--- /dev/null
+++ b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderTests.cs
@@ -0,0 +1,154 @@
+using Bunit;
+using CodeBeam.UltimateAuth.Authorization.Contracts;
+using CodeBeam.UltimateAuth.Client.Blazor;
+using CodeBeam.UltimateAuth.Core.Contracts;
+using CodeBeam.UltimateAuth.IdentityLab.Components.Pages;
+using CodeBeam.UltimateAuth.IdentityLab.Components.Pages.Dashboard;
+using CodeBeam.UltimateAuth.Users.Contracts;
+using Moq;
+using MudBlazor;
+
+namespace CodeBeam.UltimateAuth.IdentityLab.Tests.Rendering;
+
+[Trait("Category", "Render")]
+public sealed class PageRenderTests
+{
+ [Fact]
+ [SmokeTestFor(typeof(LoginPage))]
+ public void Anonymous_login_renders_the_real_login_form()
+ {
+ using var context = new PageRenderContext();
+ var page = context.RenderPage();
+
+ page.WaitForAssertion(() =>
+ {
+ Assert.Contains("Welcome back.", page.Find("h1").TextContent);
+ Assert.NotNull(page.FindComponent().Find("form"));
+ Assert.Contains("Email or Username", page.Markup);
+ });
+ }
+
+ [Fact]
+ public void Authenticated_login_renders_workspace_entry_instead_of_form()
+ {
+ using var context = new PageRenderContext();
+ var page = context.RenderPage(PageRenderContext.Authenticated("Owner"));
+
+ page.WaitForAssertion(() =>
+ {
+ Assert.Contains("Enter your workspace", page.Markup);
+ Assert.Empty(page.FindComponents());
+ });
+ }
+
+ [Fact]
+ [SmokeTestFor(typeof(RegisterPage))]
+ public void Anonymous_registration_renders_without_an_existing_session()
+ {
+ using var context = new PageRenderContext();
+ context.AddLabServices();
+ var page = context.RenderPage();
+
+ page.WaitForAssertion(() =>
+ {
+ Assert.Equal("Create your sandbox.", page.Find("h1").TextContent.Trim());
+ Assert.NotNull(page.FindComponent());
+ Assert.NotEmpty(page.FindAll("input"));
+ });
+ }
+
+ [Fact]
+ [SmokeTestFor(typeof(LandingPage))]
+ public void Landing_page_renders_registration_entry()
+ {
+ using var context = new PageRenderContext();
+ var page = context.RenderPage();
+ Assert.Contains(page.FindAll("a"), link =>
+ link.GetAttribute("href") == "/register" && link.TextContent.Contains("Create Your Sandbox"));
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ [SmokeTestFor(typeof(DashboardPage))]
+ public async Task Dashboard_renders_owner_and_member_navigation(bool owner)
+ {
+ using var context = new PageRenderContext();
+ context.AddLabServices();
+ context.Users.Setup(x => x.GetMeAsync(It.IsAny()))
+ .ReturnsAsync(PageRenderContext.Success(new UserView { DisplayName = "Render Person" }));
+ context.Sessions.Setup(x => x.GetMyChainsAsync(It.IsAny()))
+ .ReturnsAsync(PageRenderContext.Success(PageRenderContext.Empty()));
+ context.Authorization.Setup(x => x.GetMyRolesAsync(It.IsAny()))
+ .ReturnsAsync(PageRenderContext.Success(new UserRolesGetResult
+ {
+ UserKey = CodeBeam.UltimateAuth.Core.Domain.UserKey.FromString("render-user"),
+ Roles = PageRenderContext.Empty()
+ }));
+ await context.SeedSandboxAsync(owner);
+
+ var page = context.RenderPage(PageRenderContext.Authenticated(owner ? "Owner" : "User"));
+ page.WaitForAssertion(() =>
+ {
+ Assert.Contains("Your identity workspace.", page.Find("h1").TextContent);
+ Assert.Contains("Render sandbox", page.Markup);
+ Assert.Contains("Render Person", page.Markup);
+ Assert.Equal(owner, page.FindAll("a").Any(x => x.GetAttribute("href") == "/dashboard/users"));
+ Assert.Equal(owner, page.FindAll("a").Any(x => x.GetAttribute("href") == "/dashboard/settings"));
+ });
+ context.Users.Verify(x => x.GetMeAsync(It.IsAny()), Times.Once);
+ context.Sessions.Verify(x => x.GetMyChainsAsync(It.IsAny()), Times.Once);
+ context.Authorization.Verify(x => x.GetMyRolesAsync(It.IsAny()), Times.Once);
+ }
+
+ [Fact]
+ [SmokeTestFor(typeof(MySessionsPage))]
+ public void Sessions_render_empty_service_result()
+ {
+ using var context = new PageRenderContext();
+ context.Sessions.Setup(x => x.GetMyChainsAsync(It.IsAny()))
+ .ReturnsAsync(PageRenderContext.Success(PageRenderContext.Empty()));
+ var page = context.RenderPage(PageRenderContext.Authenticated("User"));
+
+ page.WaitForAssertion(() => Assert.Contains("No session chains were returned.", page.Markup));
+ Assert.Equal("My Sessions", page.Find("h1").TextContent.Trim());
+ context.Sessions.Verify(x => x.GetMyChainsAsync(It.IsAny()), Times.Once);
+ }
+
+ [Fact]
+ public void Sessions_render_loading_then_explicit_service_failure()
+ {
+ using var context = new PageRenderContext();
+ var response = new TaskCompletionSource>>(
+ TaskCreationOptions.RunContinuationsAsynchronously);
+ context.Sessions.Setup(x => x.GetMyChainsAsync(It.IsAny())).Returns(response.Task);
+ var page = context.RenderPage(PageRenderContext.Authenticated("User"));
+ Assert.NotEmpty(page.FindComponents());
+
+ response.SetResult(new UAuthResult> { IsSuccess = false, Status = 503 });
+ page.WaitForAssertion(() =>
+ {
+ Assert.Contains("Unable to load session chains.", page.Markup);
+ Assert.Empty(page.FindComponents());
+ });
+ }
+
+ [Theory]
+ [InlineData(false, "No roles available.")]
+ [InlineData(true, "Authorization service is unavailable.")]
+ [SmokeTestFor(typeof(AuthorizationPage))]
+ public void Authorization_management_renders_empty_and_failure_states(bool failure, string expected)
+ {
+ using var context = new PageRenderContext();
+ var query = context.Authorization.Setup(x => x.QueryRolesAsync(It.IsAny()));
+ if (failure)
+ query.ThrowsAsync(new HttpRequestException("Simulated unavailable service"));
+ else
+ query.ReturnsAsync(PageRenderContext.Success(PageRenderContext.Empty()));
+
+ var page = context.RenderPage(PageRenderContext.Authenticated("Owner"));
+ page.WaitForAssertion(() => Assert.Contains(expected, page.Markup));
+ Assert.Equal("Authorization Management", page.Find("h1").TextContent.Trim());
+ context.Authorization.Verify(x => x.QueryRolesAsync(It.IsAny()), Times.Once);
+ }
+}
diff --git a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/RemainingPageRenderTests.cs b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/RemainingPageRenderTests.cs
new file mode 100644
index 00000000..499c2e3c
--- /dev/null
+++ b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/RemainingPageRenderTests.cs
@@ -0,0 +1,192 @@
+using Bunit;
+using CodeBeam.UltimateAuth.Core.Defaults;
+using AuthorizationLabPage = CodeBeam.UltimateAuth.IdentityLab.Components.Pages.Dashboard.AuthorizationLab;
+using CodeBeam.UltimateAuth.IdentityLab.Components.Pages;
+using CodeBeam.UltimateAuth.IdentityLab.Components.Pages.AuthorizationLab;
+using CodeBeam.UltimateAuth.IdentityLab.Components.Pages.Dashboard;
+using Microsoft.AspNetCore.Components;
+using MudBlazor;
+
+namespace CodeBeam.UltimateAuth.IdentityLab.Tests.Rendering;
+
+[Trait("Category", "Render")]
+public sealed class RemainingPageRenderTests
+{
+ [Fact, SmokeTestFor(typeof(UsersPage))]
+ public async Task Owner_can_render_sandbox_users()
+ {
+ using var context = new PageRenderContext();
+ context.AddManagementServices();
+ await context.SeedSandboxAsync(owner: true);
+ var page = AssertPage(context, "Users", "Owner");
+ page.WaitForAssertion(() => Assert.Contains("Render Person", page.Markup));
+ }
+
+ [Fact, SmokeTestFor(typeof(MyIdentityPage))]
+ public async Task User_can_render_profile_identifiers_and_credentials()
+ {
+ using var context = new PageRenderContext();
+ context.AddManagementServices();
+ context.SetupProfileAndAccess();
+ await context.SeedSandboxAsync(owner: false);
+ var page = AssertPage(context, "My Identity", "User");
+ page.WaitForAssertion(() =>
+ {
+ Assert.Contains("Render Person", page.Markup);
+ Assert.Contains(page.FindAll("h2"), heading => heading.TextContent == "Credentials & Security");
+ });
+ }
+
+ [Fact, SmokeTestFor(typeof(MyAccessPage))]
+ public async Task User_can_render_access_with_no_assigned_roles_or_permissions()
+ {
+ using var context = new PageRenderContext();
+ context.AddManagementServices();
+ context.SetupProfileAndAccess();
+ await context.SeedSandboxAsync(owner: false);
+ var page = AssertPage(context, "My Access", "User");
+ page.WaitForAssertion(() =>
+ {
+ Assert.Contains("No roles assigned", page.Markup);
+ Assert.Contains("No permissions assigned", page.Markup);
+ Assert.DoesNotContain("Unable to load sandbox members.", page.Markup);
+ });
+ }
+
+ [Fact, SmokeTestFor(typeof(SettingsPage))]
+ public async Task Owner_can_render_sandbox_settings()
+ {
+ using var context = new PageRenderContext();
+ context.AddManagementServices();
+ await context.SeedSandboxAsync(owner: true);
+ var page = AssertPage(context, "Sandbox Settings", "Owner");
+ page.WaitForAssertion(() => Assert.Contains("Render Person", page.Markup));
+ }
+
+ [Fact, SmokeTestFor(typeof(ActivityPage))]
+ public async Task User_can_render_empty_sandbox_activity()
+ {
+ using var context = new PageRenderContext();
+ context.AddLabServices();
+ await context.SeedSandboxAsync(owner: false);
+ var page = AssertPage(context, "Activity", "User");
+ page.WaitForAssertion(() => Assert.Contains("No activity recorded", page.Markup));
+ }
+
+ [Fact, SmokeTestFor(typeof(AdminPage))]
+ public void Ladmin_can_render_administration_and_repair_panel()
+ {
+ using var context = new PageRenderContext();
+ context.AddAdminServices();
+ var page = AssertPage(context, "Sandbox Management", "Ladmin");
+ page.WaitForAssertion(() =>
+ {
+ Assert.NotNull(page.FindComponent());
+ Assert.Contains("No sandboxes found.", page.Markup);
+ });
+ }
+
+ [Fact, SmokeTestFor(typeof(SessionDiagnostics))]
+ public async Task User_can_render_current_session_diagnostics()
+ {
+ await using var context = new PageRenderContext();
+ context.AddDiagnosticsServices();
+ var page = AssertPage(context, "Session Diagnostics", "User");
+ page.WaitForAssertion(() => Assert.Contains("Session active", page.Markup));
+ }
+
+ [Fact, SmokeTestFor(typeof(DemoStorePage))]
+ public async Task User_can_render_store_without_demo_role_grants()
+ {
+ using var context = new PageRenderContext();
+ context.AddDemoStoreServices();
+ await context.SeedSandboxAsync(owner: false);
+ var page = AssertPage(context, "Demo Store", "User");
+ page.WaitForAssertion(() => Assert.Contains("Your Demo Access", page.Markup));
+ }
+
+ [Fact, SmokeTestFor(typeof(AuthorizationLabPage))]
+ public void User_can_render_authorization_lab_index()
+ {
+ using var context = new PageRenderContext();
+ AssertPage(context, "Authorization Lab", "User");
+ }
+
+ [Fact, SmokeTestFor(typeof(Error))]
+ public void Error_page_renders_without_a_request_context()
+ {
+ using var context = new PageRenderContext();
+ AssertPage(context, "Error.");
+ }
+
+ [Fact, SmokeTestFor(typeof(PublicPage))]
+ public void Public_authorization_sample_renders()
+ {
+ using var context = new PageRenderContext();
+ AssertPage(context, "Public Access");
+ }
+
+ [Fact, SmokeTestFor(typeof(AuthenticatedPage))]
+ public void Authenticated_authorization_sample_renders()
+ {
+ using var context = new PageRenderContext();
+ AssertPage(context, "Authenticated Page", "User");
+ }
+
+ [Fact, SmokeTestFor(typeof(AdminRolePage))]
+ public void Admin_role_authorization_sample_renders()
+ {
+ using var context = new PageRenderContext();
+ AssertPage(context, "Admin Role", "Admin");
+ }
+
+ [Fact, SmokeTestFor(typeof(AdminPermissionPage))]
+ public void Admin_permission_authorization_sample_renders()
+ {
+ using var context = new PageRenderContext();
+ AssertPage(context, "Admin Permission", "Admin", adminPermission: true);
+ }
+
+ [Fact, SmokeTestFor(typeof(AspNetAuthorizePage))]
+ public void Aspnet_authorization_sample_renders()
+ {
+ using var context = new PageRenderContext();
+ AssertPage(context, "ASP.NET Core Authorize", "User");
+ }
+
+ [Fact, SmokeTestFor(typeof(CombinedAuthorizationPage))]
+ public void Combined_authorization_sample_renders()
+ {
+ using var context = new PageRenderContext();
+ AssertPage(context, "Combined Authorization", "Admin", adminPermission: true);
+ }
+
+ [Fact, SmokeTestFor(typeof(StateViewPage))]
+ public void State_view_authorization_sample_renders()
+ {
+ using var context = new PageRenderContext();
+ AssertPage(context, "UAuthStateView", "Admin", authorizationExamples: true);
+ }
+
+ [Fact, SmokeTestFor(typeof(MatchModesPage))]
+ public void Match_modes_authorization_sample_renders()
+ {
+ using var context = new PageRenderContext();
+ AssertPage(context, "Authorization Match Modes", "Admin", authorizationExamples: true);
+ }
+
+ private static IRenderedComponent AssertPage(PageRenderContext context, string expected, string? role = null, bool authorizationExamples = false, bool adminPermission = false)
+ where T : IComponent
+ {
+ var page = context.RenderPage(role is null ? null : PageRenderContext.Authenticated(role, adminPermission ? new[] { UAuthActions.Users.QueryAdmin } : Array.Empty()));
+ page.WaitForAssertion(() =>
+ {
+ Assert.Contains(expected, page.Markup);
+ // A heading must not mask initialization falling into an error fallback.
+ // These two sample pages deliberately show denied authorization examples.
+ if (!authorizationExamples)
+ Assert.DoesNotContain(page.FindComponents(), alert => alert.Instance.Severity == Severity.Error);
+ });
+ return page;
+ }
+}
diff --git a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/RouteSmokeCoverageTests.cs b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/RouteSmokeCoverageTests.cs
new file mode 100644
index 00000000..981cd2fa
--- /dev/null
+++ b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/RouteSmokeCoverageTests.cs
@@ -0,0 +1,59 @@
+using System.Reflection;
+using CodeBeam.UltimateAuth.IdentityLab.Components.Pages;
+using Microsoft.AspNetCore.Components;
+
+namespace CodeBeam.UltimateAuth.IdentityLab.Tests.Rendering;
+
+/// Connects a page to the executable render test that exercises it.
+[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+internal sealed class SmokeTestForAttribute(Type componentType) : Attribute
+{
+ public Type ComponentType { get; } = componentType;
+}
+
+[Trait("Category", "Render")]
+public sealed class RouteSmokeCoverageTests
+{
+ [Fact]
+ public void Every_identity_lab_route_component_has_an_executable_smoke_test()
+ {
+ // Inspect the application assembly only. External/shared components and the
+ // test assembly cannot become route candidates. A multi-route page is one
+ // component; include all its aliases in any failure message.
+ var pages = typeof(LoginPage).Assembly.GetTypes()
+ .Where(type => !type.IsAbstract && typeof(IComponent).IsAssignableFrom(type))
+ .Select(type => new
+ {
+ Type = type,
+ Routes = type.GetCustomAttributes(inherit: false)
+ .Select(route => route.Template).OrderBy(route => route).ToArray()
+ })
+ .Where(page => page.Routes.Length > 0)
+ .ToDictionary(page => page.Type, page => page.Routes);
+ Assert.NotEmpty(pages);
+
+ var mappings = typeof(PageRenderTests).Assembly.GetTypes()
+ .SelectMany(type => type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
+ .SelectMany(method => method.GetCustomAttributes()
+ .Select(attribute => (Method: method, Page: attribute.ComponentType)))
+ .ToArray();
+ Assert.NotEmpty(mappings);
+ foreach (var mapping in mappings)
+ {
+ var fact = mapping.Method.GetCustomAttribute(); // Includes TheoryAttribute.
+ Assert.True(fact is not null && fact.Skip is null,
+ $"{mapping.Method.DeclaringType?.Name}.{mapping.Method.Name} must be an enabled xUnit render test.");
+ Assert.True(pages.ContainsKey(mapping.Page),
+ $"Smoke mapping references a non-routable Identity Lab component: {mapping.Page.FullName}.");
+ }
+
+ var tested = mappings.Select(mapping => mapping.Page).ToHashSet();
+ var missing = pages.Where(page => !tested.Contains(page.Key))
+ .OrderBy(page => page.Key.FullName)
+ .Select(page => $"{page.Key.FullName}: {string.Join(", ", page.Value)}")
+ .ToArray();
+ Assert.True(missing.Length == 0,
+ "Identity Lab pages missing render smoke tests:\n" + string.Join("\n", missing) +
+ "\nAdd a real render test and annotate it with [SmokeTestFor(typeof(PageComponent))].");
+ }
+}
diff --git a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/docs/page-render-coverage.md b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/docs/page-render-coverage.md
new file mode 100644
index 00000000..7f0d71e3
--- /dev/null
+++ b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/docs/page-render-coverage.md
@@ -0,0 +1,143 @@
+# Identity Lab route smoke coverage
+
+## Baseline
+
+The initial ten render cases are preserved. The checkout was fast-forwarded from
+`45dcfcd` to the latest available local `origin/dev`, `b886b94` (critical service
+workflow regressions, #132). `HEAD...origin/dev` reports `0 0`. The merged service
+regression tests are unchanged by this implementation.
+
+`git fetch origin dev` still fails because the configured proxy at port 8080 is
+unreachable. The live remote tip could not be verified; this is based on the
+available local dev ref, not a claim of live synchronization.
+
+## Inventory
+
+There are **24 routable page components with 24 route attributes** in the supplied
+application source. All 24 have an actual render test mapping; there are no
+routable exclusions. Counts are statically checked, not executed test results.
+
+| Component | Route | Render scenario |
+| --- | --- | --- |
+| LandingPage | `/` | Existing public registration entry |
+| LoginPage | `/login` | Existing anonymous form and authenticated entry |
+| RegisterPage | `/register` | Existing anonymous form |
+| DashboardPage | `/dashboard` | Existing Owner and User navigation |
+| MySessionsPage | `/dashboard/sessions` | Existing empty, loading and failure states |
+| AuthorizationPage | `/dashboard/authorization` | Existing empty and failure states |
+| UsersPage | `/dashboard/users` | Owner, active sandbox, one member summary |
+| MyIdentityPage | `/dashboard/identity` | User, populated profile, empty identifiers, credential section |
+| MyAccessPage | `/dashboard/access` | User, sandbox membership, empty roles and permissions |
+| SettingsPage | `/dashboard/settings` | Owner, sandbox settings and resolved owner name |
+| ActivityPage | `/dashboard/activity` | User, active sandbox, no activity |
+| AdminPage | `/admin` | Ladmin with matching server role assignment, empty platform, real repair panel |
+| SessionDiagnostics | `/dashboard/session-diagnostics` | User, current chain and active session details |
+| DemoStorePage | `/dashboard/demo-store` | User, active sandbox, no demo role grants |
+| AuthorizationLab | `/dashboard/authorization-lab` | User, real navigation index |
+| Error | `/Error` | No HTTP request context |
+| PublicPage | `/dashboard/auth/public` | Anonymous sample |
+| AuthenticatedPage | `/dashboard/auth/authenticated` | User sample |
+| AdminRolePage | `/dashboard/auth/admin` | Admin sample role |
+| AdminPermissionPage | `/dashboard/auth/permission-admin` | Sample Admin plus query permission |
+| AspNetAuthorizePage | `/dashboard/auth/aspnet` | User, ASP.NET authentication cascade |
+| CombinedAuthorizationPage | `/dashboard/auth/combined` | Sample Admin plus query permission |
+| StateViewPage | `/dashboard/auth/state-view` | Real shared state-view examples |
+| MatchModesPage | `/dashboard/auth/match-modes` | Real shared match-mode examples |
+
+Credentials have no separate route; their section belongs to MyIdentityPage.
+AdminRepairPanel has no route and is exercised as an actual AdminPage child.
+Its scan/repair actions are outside smoke scope. Other non-routable children,
+layouts and dialogs are not independently inventoried.
+
+## Tests and route enforcement
+
+The original eight test methods / ten cases retain their bodies and InlineData;
+only six SmokeTestFor annotations were added to connect their pages to inventory.
+Added 18 one-scenario page tests and one route coverage guard: **28 page-render
+cases plus one inventory test**, 29 total cases under `Category=Render`.
+
+SmokeTestFor attributes live on actual enabled xUnit render methods. The guard
+reflects only the Identity Lab application assembly for RouteAttribute-bearing
+IComponent types. It checks every component, reports all route aliases for missing
+components, rejects mappings to non-routable/external types, and verifies mappings
+point to enabled Fact/Theory methods. Non-routable children and test/library
+assemblies are not route candidates. A page with multiple aliases needs one real
+component render test. There are no exception lists or arbitrary dynamic renders.
+
+The small generic assertion helper renders a statically selected real component.
+It checks stable page text and rejects error alerts. StateView/MatchModes samples
+are the narrow exception to the error-alert check because denied examples are
+intentional content. Application pages additionally assert loaded content where
+needed: member display name, profile/credentials, owner name, empty access/activity,
+admin table/repair panel, active session and evaluated demo access.
+
+## Setup
+
+Reuse the original bUnit/MudBlazor/MudExtensions fixture, real UltimateAuth
+authorization evaluator and uniquely named EF InMemory LabDbContext store.
+Additional registrations are grouped for management, admin, demo and diagnostics;
+external client/store responses are explicit. Real page and Lab service lifecycle
+methods remain active. No application host or child stubs are installed.
+
+The renderer now supplies ASP.NET AuthenticationState as well as UAuthState.
+The existing internal snapshot adapter accepts optional permission claims.
+Regular application Users do not receive admin permission claims. The sample
+routes use their declared sample Admin role/permission as appropriate.
+Diagnostics renders a real current-chain/detail response and uses async fixture
+disposal for its timer/subscription lifecycle. No new packages or database
+dependencies were added in this completion phase.
+
+## Validation
+
+Attempted from repository root:
+
+```sh
+git fetch origin dev
+dotnet build tests/CodeBeam.UltimateAuth.IdentityLab.Tests/CodeBeam.UltimateAuth.IdentityLab.Tests.csproj -c Release
+dotnet test tests/CodeBeam.UltimateAuth.IdentityLab.Tests/CodeBeam.UltimateAuth.IdentityLab.Tests.csproj -c Release --filter Category=Render
+```
+
+Fetch failed: configured proxy unreachable. Both .NET commands failed before
+execution: `dotnet: command not found`. **No build, render test, route guard or
+existing service regression test has executed here.** No measured coverage is
+reported.
+
+Completed static checks:
+
+- Compared and fast-forwarded to available local dev; no divergent commits.
+- Matched all 24 source page types/routes to the annotated actual render methods.
+- Reviewed component namespaces, cascades, injected service constructors, client
+ and store method signatures, result constructors and bUnit API usage against
+ existing repository conventions. This cannot substitute for compilation.
+- Parsed project XML and checked added-source whitespace.
+- `git diff --check` passed.
+- Full patch, including new files, passes `git apply --cached --check` against
+ an isolated index initialized from `b886b94`.
+
+On a .NET 10 CI runner, the filtered command above runs the existing behavioral
+render cases, new page smoke cases and route guard without Docker, PostgreSQL or a
+browser. Run the full Identity Lab project separately with its documented
+PostgreSQL/Docker requirements. Existing known-defect tests remain unchanged.
+
+## Limits
+
+Compilation and successful rendering remain unverified until .NET execution.
+These tests bypass routing, so they do not validate authorization enforcement by
+the router. Layout composition, dialogs, mutations, repair scans, populated large
+data sets and detailed interaction states remain outside this smoke phase. EF
+InMemory does not validate relational constraints, SQL translation, migrations or
+transactions. No percentage coverage target or estimate is used.
+
+## Files
+
+Completion phase:
+
+- Rendering/PageRenderContext.cs: identifier client, ASP.NET cascade, optional claims.
+- Rendering/PageRenderContext.ApplicationPages.cs: focused dependency/response groups.
+- Rendering/PageRenderTests.cs: mappings only; original cases preserved.
+- Rendering/RemainingPageRenderTests.cs: 18 additional real page smoke tests.
+- Rendering/RouteSmokeCoverageTests.cs: metadata mapping and route guard.
+- README.md and this report: inventory, commands and validation limits.
+
+The prior phase's project dependencies remain bunit 2.11.3 and EF InMemory 10.0.12.
+No production code, migration, CI configuration or service test was edited.
From 6e25f6cef52fa44d1c68917e2fd2b51822916b9b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?=
Date: Sun, 1 Nov 2026 00:09:03 +0300
Subject: [PATCH 2/3] Fix Tests
---
.../Rendering/PageRenderContext.cs | 4 +-
.../Rendering/PageRenderTests.cs | 30 ++++-----
.../Rendering/RemainingPageRenderTests.cs | 61 +++++++++----------
3 files changed, 47 insertions(+), 48 deletions(-)
diff --git a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderContext.cs b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderContext.cs
index 98733895..0c59e9bf 100644
--- a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderContext.cs
+++ b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderContext.cs
@@ -94,8 +94,8 @@ public static UAuthState Authenticated(string role, params string[] permissions)
SessionState = SessionState.Active,
UserStatus = UserStatus.Active
},
- Claims = ClaimsSnapshot.From(new[] { (ClaimTypes.Role, role) }
- .Concat(permissions.Select(permission => ("uauth:permission", permission))))
+ Claims = ClaimsSnapshot.From(new[] { (Type: ClaimTypes.Role, Value: role) }
+ .Concat(permissions.Select(permission => (Type: "uauth:permission", Value: permission))).ToArray())
}, DateTimeOffset.UtcNow]);
return state;
}
diff --git a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderTests.cs b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderTests.cs
index e0ab26a1..0bf5feda 100644
--- a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderTests.cs
+++ b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/PageRenderTests.cs
@@ -15,9 +15,9 @@ public sealed class PageRenderTests
{
[Fact]
[SmokeTestFor(typeof(LoginPage))]
- public void Anonymous_login_renders_the_real_login_form()
+ public async Task Anonymous_login_renders_the_real_login_form()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
var page = context.RenderPage();
page.WaitForAssertion(() =>
@@ -29,9 +29,9 @@ public void Anonymous_login_renders_the_real_login_form()
}
[Fact]
- public void Authenticated_login_renders_workspace_entry_instead_of_form()
+ public async Task Authenticated_login_renders_workspace_entry_instead_of_form()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
var page = context.RenderPage(PageRenderContext.Authenticated("Owner"));
page.WaitForAssertion(() =>
@@ -43,9 +43,9 @@ public void Authenticated_login_renders_workspace_entry_instead_of_form()
[Fact]
[SmokeTestFor(typeof(RegisterPage))]
- public void Anonymous_registration_renders_without_an_existing_session()
+ public async Task Anonymous_registration_renders_without_an_existing_session()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
context.AddLabServices();
var page = context.RenderPage();
@@ -59,9 +59,9 @@ public void Anonymous_registration_renders_without_an_existing_session()
[Fact]
[SmokeTestFor(typeof(LandingPage))]
- public void Landing_page_renders_registration_entry()
+ public async Task Landing_page_renders_registration_entry()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
var page = context.RenderPage();
Assert.Contains(page.FindAll("a"), link =>
link.GetAttribute("href") == "/register" && link.TextContent.Contains("Create Your Sandbox"));
@@ -73,7 +73,7 @@ public void Landing_page_renders_registration_entry()
[SmokeTestFor(typeof(DashboardPage))]
public async Task Dashboard_renders_owner_and_member_navigation(bool owner)
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
context.AddLabServices();
context.Users.Setup(x => x.GetMeAsync(It.IsAny()))
.ReturnsAsync(PageRenderContext.Success(new UserView { DisplayName = "Render Person" }));
@@ -103,9 +103,9 @@ public async Task Dashboard_renders_owner_and_member_navigation(bool owner)
[Fact]
[SmokeTestFor(typeof(MySessionsPage))]
- public void Sessions_render_empty_service_result()
+ public async Task Sessions_render_empty_service_result()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
context.Sessions.Setup(x => x.GetMyChainsAsync(It.IsAny()))
.ReturnsAsync(PageRenderContext.Success(PageRenderContext.Empty()));
var page = context.RenderPage(PageRenderContext.Authenticated("User"));
@@ -116,9 +116,9 @@ public void Sessions_render_empty_service_result()
}
[Fact]
- public void Sessions_render_loading_then_explicit_service_failure()
+ public async Task Sessions_render_loading_then_explicit_service_failure()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
var response = new TaskCompletionSource>>(
TaskCreationOptions.RunContinuationsAsynchronously);
context.Sessions.Setup(x => x.GetMyChainsAsync(It.IsAny())).Returns(response.Task);
@@ -137,9 +137,9 @@ public void Sessions_render_loading_then_explicit_service_failure()
[InlineData(false, "No roles available.")]
[InlineData(true, "Authorization service is unavailable.")]
[SmokeTestFor(typeof(AuthorizationPage))]
- public void Authorization_management_renders_empty_and_failure_states(bool failure, string expected)
+ public async Task Authorization_management_renders_empty_and_failure_states(bool failure, string expected)
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
var query = context.Authorization.Setup(x => x.QueryRolesAsync(It.IsAny()));
if (failure)
query.ThrowsAsync(new HttpRequestException("Simulated unavailable service"));
diff --git a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/RemainingPageRenderTests.cs b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/RemainingPageRenderTests.cs
index 499c2e3c..fab7ee53 100644
--- a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/RemainingPageRenderTests.cs
+++ b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/Rendering/RemainingPageRenderTests.cs
@@ -15,7 +15,7 @@ public sealed class RemainingPageRenderTests
[Fact, SmokeTestFor(typeof(UsersPage))]
public async Task Owner_can_render_sandbox_users()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
context.AddManagementServices();
await context.SeedSandboxAsync(owner: true);
var page = AssertPage(context, "Users", "Owner");
@@ -25,7 +25,7 @@ public async Task Owner_can_render_sandbox_users()
[Fact, SmokeTestFor(typeof(MyIdentityPage))]
public async Task User_can_render_profile_identifiers_and_credentials()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
context.AddManagementServices();
context.SetupProfileAndAccess();
await context.SeedSandboxAsync(owner: false);
@@ -40,7 +40,7 @@ public async Task User_can_render_profile_identifiers_and_credentials()
[Fact, SmokeTestFor(typeof(MyAccessPage))]
public async Task User_can_render_access_with_no_assigned_roles_or_permissions()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
context.AddManagementServices();
context.SetupProfileAndAccess();
await context.SeedSandboxAsync(owner: false);
@@ -56,7 +56,7 @@ public async Task User_can_render_access_with_no_assigned_roles_or_permissions()
[Fact, SmokeTestFor(typeof(SettingsPage))]
public async Task Owner_can_render_sandbox_settings()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
context.AddManagementServices();
await context.SeedSandboxAsync(owner: true);
var page = AssertPage(context, "Sandbox Settings", "Owner");
@@ -66,7 +66,7 @@ public async Task Owner_can_render_sandbox_settings()
[Fact, SmokeTestFor(typeof(ActivityPage))]
public async Task User_can_render_empty_sandbox_activity()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
context.AddLabServices();
await context.SeedSandboxAsync(owner: false);
var page = AssertPage(context, "Activity", "User");
@@ -74,9 +74,9 @@ public async Task User_can_render_empty_sandbox_activity()
}
[Fact, SmokeTestFor(typeof(AdminPage))]
- public void Ladmin_can_render_administration_and_repair_panel()
+ public async Task Ladmin_can_render_administration_and_repair_panel()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
context.AddAdminServices();
var page = AssertPage(context, "Sandbox Management", "Ladmin");
page.WaitForAssertion(() =>
@@ -98,7 +98,7 @@ public async Task User_can_render_current_session_diagnostics()
[Fact, SmokeTestFor(typeof(DemoStorePage))]
public async Task User_can_render_store_without_demo_role_grants()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
context.AddDemoStoreServices();
await context.SeedSandboxAsync(owner: false);
var page = AssertPage(context, "Demo Store", "User");
@@ -106,77 +106,76 @@ public async Task User_can_render_store_without_demo_role_grants()
}
[Fact, SmokeTestFor(typeof(AuthorizationLabPage))]
- public void User_can_render_authorization_lab_index()
+ public async Task User_can_render_authorization_lab_index()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
AssertPage(context, "Authorization Lab", "User");
}
[Fact, SmokeTestFor(typeof(Error))]
- public void Error_page_renders_without_a_request_context()
+ public async Task Error_page_renders_without_a_request_context()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
AssertPage(context, "Error.");
}
[Fact, SmokeTestFor(typeof(PublicPage))]
- public void Public_authorization_sample_renders()
+ public async Task Public_authorization_sample_renders()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
AssertPage(context, "Public Access");
}
[Fact, SmokeTestFor(typeof(AuthenticatedPage))]
- public void Authenticated_authorization_sample_renders()
+ public async Task Authenticated_authorization_sample_renders()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
AssertPage(context, "Authenticated Page", "User");
}
[Fact, SmokeTestFor(typeof(AdminRolePage))]
- public void Admin_role_authorization_sample_renders()
+ public async Task Admin_role_authorization_sample_renders()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
AssertPage(context, "Admin Role", "Admin");
}
[Fact, SmokeTestFor(typeof(AdminPermissionPage))]
- public void Admin_permission_authorization_sample_renders()
+ public async Task Admin_permission_authorization_sample_renders()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
AssertPage(context, "Admin Permission", "Admin", adminPermission: true);
}
[Fact, SmokeTestFor(typeof(AspNetAuthorizePage))]
- public void Aspnet_authorization_sample_renders()
+ public async Task Aspnet_authorization_sample_renders()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
AssertPage(context, "ASP.NET Core Authorize", "User");
}
[Fact, SmokeTestFor(typeof(CombinedAuthorizationPage))]
- public void Combined_authorization_sample_renders()
+ public async Task Combined_authorization_sample_renders()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
AssertPage(context, "Combined Authorization", "Admin", adminPermission: true);
}
[Fact, SmokeTestFor(typeof(StateViewPage))]
- public void State_view_authorization_sample_renders()
+ public async Task State_view_authorization_sample_renders()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
AssertPage(context, "UAuthStateView", "Admin", authorizationExamples: true);
}
[Fact, SmokeTestFor(typeof(MatchModesPage))]
- public void Match_modes_authorization_sample_renders()
+ public async Task Match_modes_authorization_sample_renders()
{
- using var context = new PageRenderContext();
+ await using var context = new PageRenderContext();
AssertPage(context, "Authorization Match Modes", "Admin", authorizationExamples: true);
}
- private static IRenderedComponent AssertPage(PageRenderContext context, string expected, string? role = null, bool authorizationExamples = false, bool adminPermission = false)
- where T : IComponent
+ private static IRenderedComponent AssertPage(PageRenderContext context, string expected, string? role = null, bool authorizationExamples = false, bool adminPermission = false) where T : IComponent
{
var page = context.RenderPage(role is null ? null : PageRenderContext.Authenticated(role, adminPermission ? new[] { UAuthActions.Users.QueryAdmin } : Array.Empty()));
page.WaitForAssertion(() =>
@@ -185,7 +184,7 @@ private static IRenderedComponent AssertPage(PageRenderContext context, st
// A heading must not mask initialization falling into an error fallback.
// These two sample pages deliberately show denied authorization examples.
if (!authorizationExamples)
- Assert.DoesNotContain(page.FindComponents(), alert => alert.Instance.Severity == Severity.Error);
+ Assert.Empty(page.FindAll(".mud-alert-error"));
});
return page;
}
From 0519926de2231a950bc1ea7d6d16a3f0444e3884 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?=
Date: Sun, 1 Nov 2026 00:38:11 +0300
Subject: [PATCH 3/3] Add Test Runsettings
---
.github/workflows/ultimateauth-ci.yml | 3 ++-
.../identity-lab.runsettings | 14 ++++++++++++++
2 files changed, 16 insertions(+), 1 deletion(-)
create mode 100644 tests/CodeBeam.UltimateAuth.IdentityLab.Tests/identity-lab.runsettings
diff --git a/.github/workflows/ultimateauth-ci.yml b/.github/workflows/ultimateauth-ci.yml
index 6058261f..0b38ddf7 100644
--- a/.github/workflows/ultimateauth-ci.yml
+++ b/.github/workflows/ultimateauth-ci.yml
@@ -55,7 +55,7 @@ jobs:
identity-lab-security:
- name: Identity Lab security (PostgreSQL)
+ name: Identity Lab security
runs-on: ubuntu-latest
timeout-minutes: 20
@@ -75,6 +75,7 @@ jobs:
--collect:"XPlat Code Coverage"
--logger "trx;LogFileName=identity-lab.trx"
--results-directory TestResults/IdentityLab
+ --settings tests/CodeBeam.UltimateAuth.IdentityLab.Tests/identity-lab.runsettings
- name: Generate Identity Lab coverage summary
if: always()
diff --git a/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/identity-lab.runsettings b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/identity-lab.runsettings
new file mode 100644
index 00000000..87dfcfab
--- /dev/null
+++ b/tests/CodeBeam.UltimateAuth.IdentityLab.Tests/identity-lab.runsettings
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ [CodeBeam.UltimateAuth.IdentityLab]*
+ **/Migrations/**/*.cs
+ cobertura,json
+
+
+
+
+