using System.Security.Claims; using Bunit; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using NSubstitute; using ScadaLink.Commons.Entities.Instances; using ScadaLink.Commons.Entities.Sites; using ScadaLink.Commons.Entities.Templates; using ScadaLink.Commons.Interfaces.Repositories; using ScadaLink.Commons.Interfaces.Services; using ScadaLink.Commons.Types.Enums; using ScadaLink.Communication; using ScadaLink.DeploymentManager; using ScadaLink.CentralUI.Components.Shared; using ScadaLink.TemplateEngine.Services; using TopologyPage = ScadaLink.CentralUI.Components.Pages.Deployment.Topology; namespace ScadaLink.CentralUI.Tests; /// /// bUnit rendering tests for the Topology page (Site → Area → Instance tree). /// Focuses on the behavior that's specific to this page: /// always-visible empty containers, search dimming, F2 area rename, and the /// move-dialog destination lists. /// public class TopologyPageTests : BunitContext { private readonly ITemplateEngineRepository _repo = Substitute.For(); private readonly ISiteRepository _siteRepo = Substitute.For(); private readonly IDeploymentManagerRepository _deployRepo = Substitute.For(); private readonly IFlatteningPipeline _pipeline = Substitute.For(); private readonly IAuditService _audit = Substitute.For(); public TopologyPageTests() { Services.AddSingleton(_repo); Services.AddSingleton(_siteRepo); Services.AddSingleton(_deployRepo); Services.AddSingleton(_pipeline); Services.AddSingleton(_audit); // DeploymentService has non-mockable concrete deps; instantiate them directly. var comms = new CommunicationService( Options.Create(new CommunicationOptions()), NullLogger.Instance); Services.AddSingleton(comms); Services.AddSingleton(new OperationLockManager()); Services.AddSingleton(Options.Create(new DeploymentManagerOptions { OperationLockTimeout = TimeSpan.FromSeconds(5) })); Services.AddSingleton>(NullLogger.Instance); Services.AddScoped(); Services.AddScoped(); Services.AddScoped(); AddTestAuth(); // The page injects IDialogService for delete confirmations; the host // (rendered globally in MainLayout) is not present in bUnit, but the // DI registration still has to satisfy the [Inject]. Services.AddScoped(); // Site scoping (CentralUI-002): Topology injects SiteScopeService to // filter the tree by the user's permitted sites. Services.AddScoped(); // TreeView persists expansion state via JS interop. Stub the calls so render doesn't throw. JSInterop.Setup("treeviewStorage.load", _ => true).SetResult(null); JSInterop.SetupVoid("treeviewStorage.save", _ => true); } private void AddTestAuth() { var claims = new[] { new Claim("Username", "tester"), new Claim(ClaimTypes.Role, "Deployment") }; var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth")); Services.AddSingleton(new TestAuthStateProvider(user)); Services.AddAuthorizationCore(); } private void SeedRepos( IEnumerable? sites = null, IEnumerable