using System.Security.Claims; using Bunit; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared; using ZB.MOM.WW.ScadaBridge.Commons.Entities.ExternalSystems; using ZB.MOM.WW.ScadaBridge.Commons.Entities.InboundApi; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories; using ZB.MOM.WW.ScadaBridge.Security; using ExternalSystems = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Design.ExternalSystems; namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Design; /// /// Security regression guard: the Integration Definitions list (Database Connections /// tab) must NEVER render a connection string — it carries credentials. Connection /// strings are shown only on the create/edit form. This test seeds a connection whose /// string contains a sentinel password and asserts it is absent from the rendered /// markup of the list (so it never reaches the browser DOM, even for an admin/designer). /// public class ExternalSystemsConnectionStringHidingTests : BunitContext { private readonly IExternalSystemRepository _repo = Substitute.For(); private readonly IInboundApiRepository _inbound = Substitute.For(); private readonly IDialogService _dialog = Substitute.For(); public ExternalSystemsConnectionStringHidingTests() { Services.AddSingleton(_repo); Services.AddSingleton(_inbound); Services.AddSingleton(_dialog); var claims = new[] { new Claim(JwtTokenService.UsernameClaimType, "tester"), new Claim(JwtTokenService.RoleClaimType, "Designer"), }; var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth")); Services.AddSingleton(new TestAuthStateProvider(user)); Services.AddAuthorizationCore(); AuthorizationPolicies.AddScadaBridgeAuthorization(Services); } [Fact] public void DbConnectionsTab_DoesNotRenderConnectionString() { const string secret = "Server=db.internal;User Id=sa;Password=SuperSecret#123;"; _repo.GetAllExternalSystemsAsync(Arg.Any()) .Returns((IReadOnlyList)new List()); _repo.GetAllDatabaseConnectionsAsync(Arg.Any()) .Returns((IReadOnlyList)new List { new("AppDb", secret) { Id = 1 }, }); _inbound.GetAllApiMethodsAsync(Arg.Any()) .Returns((IReadOnlyList)new List()); var cut = Render(); // Wait for the async load to complete (tabs render only once _loading == false), // then activate the Database Connections tab so its cards render. cut.WaitForAssertion(() => Assert.Contains(cut.FindAll("button.nav-link"), b => b.TextContent.Contains("Database Connections"))); cut.FindAll("button.nav-link").First(b => b.TextContent.Contains("Database Connections")).Click(); cut.WaitForAssertion(() => Assert.Contains("AppDb", cut.Markup)); // The card renders (name visible) but the credential-bearing connection string must not. Assert.DoesNotContain(secret, cut.Markup); Assert.DoesNotContain("SuperSecret#123", cut.Markup); } }