diff --git a/LANCommander.Server.UI.Tests/Tests/FirstTimeSetupAdministratorTests.cs b/LANCommander.Server.UI.Tests/Tests/FirstTimeSetupAdministratorTests.cs
new file mode 100644
index 00000000..eb1a1301
--- /dev/null
+++ b/LANCommander.Server.UI.Tests/Tests/FirstTimeSetupAdministratorTests.cs
@@ -0,0 +1,141 @@
+using LANCommander.Server.Data;
+using LANCommander.Server.Data.Models;
+using LANCommander.Server.Services;
+using LANCommander.Server.Settings.Enums;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace LANCommander.Server.UI.Tests.Tests;
+
+///
+/// Fixture for the first-time-setup administrator flow. Stands up the real server DI container
+/// backed by a file-based SQLite database (via ) but, unlike
+/// BUnitServerFixture, seeds NO administrator role or user. The provider is marked as
+/// configured so queries the database and reports the
+/// server as "installed but awaiting an admin" — exactly the state the Administrator wizard step
+/// runs in.
+///
+public class FirstTimeSetupFixture : IAsyncLifetime
+{
+ public UITestApplicationFactory Factory { get; private set; } = null!;
+
+ public Task InitializeAsync()
+ {
+ Factory = new UITestApplicationFactory();
+ // Trigger the factory to build the host and create the SQLite schema.
+ _ = Factory.Services;
+
+ // Mark the provider as configured (but seed no admin) so the server behaves like a freshly
+ // installed instance sitting on the final "create administrator" step.
+ DatabaseContext.Provider = DatabaseProvider.SQLite;
+
+ return Task.CompletedTask;
+ }
+
+ public async Task DisposeAsync()
+ {
+ DatabaseContext.Provider = DatabaseProvider.Unknown;
+ await Factory.DisposeAsync();
+ }
+}
+
+///
+/// Isolates the first-time-setup administrator tests in their own collection so the seeded admin
+/// they create does not contend with the configured/bUnit collections over the static
+/// and shared database.
+///
+[CollectionDefinition("FirstTimeSetupAdministrator")]
+public class FirstTimeSetupAdministratorCollection : ICollectionFixture
+{
+}
+
+///
+/// Integration tests for the final first-time-setup step (Administrator.razor): creating the
+/// initial administrator account. These exercise the exact service calls the page makes against a
+/// real SQLite-backed DI container, guarding the install flow that a user can only ever run once.
+///
+/// Regression: the admin is created through 's password-policy
+/// bypass path, which writes the user directly via DbContext. That path previously left
+/// NormalizedUserName null, so ASP.NET Identity's FindByNameAsync (used by
+/// ) could not locate the just-created user and the wizard
+/// failed with "Value cannot be null. (Parameter 'user')".
+///
+[Collection("FirstTimeSetupAdministrator")]
+public class FirstTimeSetupAdministratorTests
+{
+ private readonly FirstTimeSetupFixture _fixture;
+
+ public FirstTimeSetupAdministratorTests(FirstTimeSetupFixture fixture)
+ {
+ _fixture = fixture;
+ }
+
+ [Fact]
+ public async Task CreateAdministrator_FollowingWizardFlow_ProducesUsableAdminAndCompletesSetup()
+ {
+ const string username = "setupadmin";
+ const string password = "SetupAdmin123!";
+
+ using var scope = _fixture.Factory.RealServices.CreateScope();
+ var setupService = scope.ServiceProvider.GetRequiredService();
+ var roleService = scope.ServiceProvider.GetRequiredService();
+ var userService = scope.ServiceProvider.GetRequiredService();
+
+ // A freshly installed server with no administrator yet is not considered set up.
+ Assert.False(await setupService.IsSetupInitialized());
+
+ // Mirror Administrator.razor: ensure the Administrator role exists, then create the admin
+ // user through the password-policy bypass path and assign the role.
+ var role = await roleService.GetAsync(RoleService.AdministratorRoleName)
+ ?? await roleService.AddAsync(new Role { Name = RoleService.AdministratorRoleName });
+ Assert.NotNull(role);
+
+ var user = new User
+ {
+ UserName = username,
+ Approved = true,
+ ApprovedOn = DateTime.UtcNow,
+ };
+
+ await userService.AddAsync(user, bypassPasswordPolicy: true, password);
+
+ // The regression point: before NormalizedUserName was populated in the bypass path, the
+ // FindByNameAsync inside AddToRolesAsync returned null and this threw
+ // ArgumentNullException ("Value cannot be null. (Parameter 'user')").
+ await userService.AddToRolesAsync(user.UserName, new[] { RoleService.AdministratorRoleName });
+
+ // The new admin is findable by name and carries the administrator role.
+ var persisted = await userService.GetAsync(username);
+ Assert.NotNull(persisted);
+ Assert.True(await userService.IsInRoleAsync(persisted, RoleService.AdministratorRoleName));
+
+ var administrators = await roleService.GetUsersAsync(RoleService.AdministratorRoleName);
+ Assert.Contains(administrators, u => u.UserName == username);
+
+ // The password set through the bypass path actually authenticates.
+ Assert.True(await userService.CheckPassword(username, password));
+
+ // With an administrator present, setup now reports as complete.
+ Assert.True(await setupService.IsSetupInitialized());
+ }
+
+ [Fact]
+ public async Task AddAsync_WithPasswordPolicyBypass_PopulatesNormalizedUserName()
+ {
+ const string username = "normalizationcheck";
+
+ using var scope = _fixture.Factory.RealServices.CreateScope();
+ var userService = scope.ServiceProvider.GetRequiredService();
+ var contextFactory = scope.ServiceProvider.GetRequiredService>();
+
+ var user = new User { UserName = username };
+
+ await userService.AddAsync(user, bypassPasswordPolicy: true, "Whatever123!");
+
+ // The directly-inserted row must carry a normalized user name, otherwise Identity's
+ // FindByNameAsync (which queries NormalizedUserName) cannot locate the user.
+ await using var db = await contextFactory.CreateDbContextAsync();
+ var stored = await db.Users.AsNoTracking().SingleAsync(u => u.UserName == username);
+ Assert.Equal(username.ToUpperInvariant(), stored.NormalizedUserName);
+ }
+}