using System.Net; using System.Net.Sockets; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using ZB.MOM.WW.OtOpcUa.Configuration; using ZB.MOM.WW.OtOpcUa.Configuration.Entities; using ZB.MOM.WW.OtOpcUa.Configuration.Enums; namespace ZB.MOM.WW.OtOpcUa.Admin.E2ETests; /// /// Stands up the Admin Blazor Server host on a free TCP port with the live SQL Server /// context swapped for an EF Core InMemory DbContext + the LDAP cookie auth swapped for /// . Playwright connects to . /// InMemory is sufficient because UnsService's drag-drop path exercises EF operations, /// not raw SQL. /// /// /// We deliberately build a directly rather than going through /// WebApplicationFactory<Program> — the factory's TestServer transport doesn't /// coexist cleanly with Kestrel-on-a-real-port, and Playwright needs a real loopback HTTP /// endpoint to hit. This mirrors the Program.cs entry-points for everything else. /// public sealed class AdminWebAppFactory : IAsyncDisposable { private WebApplication? _app; public string BaseUrl { get; private set; } = ""; public long SeededGenerationId { get; private set; } public string SeededClusterId { get; } = "e2e-cluster"; public async Task StartAsync() { var port = GetFreeTcpPort(); BaseUrl = $"http://127.0.0.1:{port}"; var builder = WebApplication.CreateBuilder(Array.Empty()); builder.WebHost.UseUrls(BaseUrl); // --- Mirror the Admin composition in Program.cs, but with the InMemory DB + test // auth swaps instead of SQL Server + LDAP cookie auth. builder.Services.AddRazorComponents().AddInteractiveServerComponents(); builder.Services.AddHttpContextAccessor(); builder.Services.AddSignalR(); builder.Services.AddAntiforgery(); builder.Services.AddAuthentication(TestAuthHandler.SchemeName) .AddScheme(TestAuthHandler.SchemeName, _ => { }); builder.Services.AddAuthorizationBuilder() .AddPolicy("CanEdit", p => p.RequireRole(Admin.Services.AdminRoles.ConfigEditor, Admin.Services.AdminRoles.FleetAdmin)) .AddPolicy("CanPublish", p => p.RequireRole(Admin.Services.AdminRoles.FleetAdmin)); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddDbContext(opt => opt.UseInMemoryDatabase($"e2e-{Guid.NewGuid():N}")); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); _app = builder.Build(); _app.UseStaticFiles(); _app.UseRouting(); _app.UseAuthentication(); _app.UseAuthorization(); _app.UseAntiforgery(); _app.MapRazorComponents().AddInteractiveServerRenderMode(); // Seed the draft BEFORE starting the host so Playwright sees a ready page on first nav. using (var scope = _app.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); SeededGenerationId = Seed(db, SeededClusterId); } await _app.StartAsync(); } public async ValueTask DisposeAsync() { if (_app is not null) { await _app.StopAsync(); await _app.DisposeAsync(); } } private static long Seed(OtOpcUaConfigDbContext db, string clusterId) { var cluster = new ServerCluster { ClusterId = clusterId, Name = "e2e", Enterprise = "zb", Site = "lab", RedundancyMode = RedundancyMode.None, NodeCount = 1, CreatedBy = "e2e", }; var gen = new ConfigGeneration { ClusterId = clusterId, Status = GenerationStatus.Draft, CreatedBy = "e2e", }; db.ServerClusters.Add(cluster); db.ConfigGenerations.Add(gen); db.SaveChanges(); db.UnsAreas.AddRange( new UnsArea { UnsAreaId = "area-a", ClusterId = clusterId, Name = "warsaw", GenerationId = gen.GenerationId }, new UnsArea { UnsAreaId = "area-b", ClusterId = clusterId, Name = "berlin", GenerationId = gen.GenerationId }); db.UnsLines.Add(new UnsLine { UnsLineId = "line-a1", UnsAreaId = "area-a", Name = "oven-line", GenerationId = gen.GenerationId, }); db.SaveChanges(); return gen.GenerationId; } private static int GetFreeTcpPort() { var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); var port = ((IPEndPoint)listener.LocalEndpoint).Port; listener.Stop(); return port; } }