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.Interfaces.Security; using ZB.MOM.WW.ScadaBridge.Security; using ApiKeys = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Admin.ApiKeys; namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Admin; /// /// Inbound-API key re-arch (C3): the API Keys list page now reads keys from the /// seam (string KeyId + method-scopes) instead of the SQL /// Server ApiKey entity. There is no retrievable hash, so the old masked Key-Hash column is gone. /// public class ApiKeysListPageTests : BunitContext { private readonly IInboundApiKeyAdmin _admin = Substitute.For(); public ApiKeysListPageTests() { Services.AddSingleton(_admin); Services.AddSingleton(Substitute.For()); var claims = new[] { new Claim(JwtTokenService.UsernameClaimType, "admin"), new Claim(JwtTokenService.RoleClaimType, "Administrator"), }; var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth")); Services.AddSingleton(new TestAuthStateProvider(user)); Services.AddAuthorizationCore(); AuthorizationPolicies.AddScadaBridgeAuthorization(Services); } private static InboundApiKeyInfo Key( string keyId, string name, bool enabled, params string[] methods) => new(keyId, name, enabled, methods, DateTimeOffset.UnixEpoch, null); [Fact] public void List_RendersKeysFromSeam_WithNoKeyHashColumn() { _admin.ListAsync(Arg.Any()) .Returns(Task.FromResult>(new[] { Key("aaaaaaaaaaaa1111", "Orders-Integration", true, "PlaceOrder", "GetStatus"), Key("bbbbbbbbbbbb2222", "Disabled-Key", false, "Ping"), })); var cut = Render(); cut.WaitForAssertion(() => { // Both key names render. Assert.Contains("Orders-Integration", cut.Markup); Assert.Contains("Disabled-Key", cut.Markup); // The disabled key carries the Disabled badge. Assert.Contains("Disabled", cut.Markup); // No Key-Hash column header. var headers = cut.FindAll("th").Select(h => h.TextContent.Trim()).ToList(); Assert.DoesNotContain(headers, h => h.Contains("Hash", StringComparison.OrdinalIgnoreCase)); Assert.Contains(headers, h => h == "Key ID"); Assert.Contains(headers, h => h == "Methods"); // KeyId renders truncated (first 12 chars + ellipsis), not the full value. Assert.Contains("aaaaaaaaaaaa…", cut.Markup); // The Methods column shows the per-key scope count (2 for the first key). var cells = cut.FindAll("td").Select(c => c.TextContent.Trim()).ToList(); Assert.Contains("2", cells); }); } [Fact] public async Task ToggleKey_CallsSetEnabledOnSeam() { _admin.ListAsync(Arg.Any()) .Returns(Task.FromResult>(new[] { Key("aaaaaaaaaaaa1111", "Orders-Integration", true, "PlaceOrder"), })); var cut = Render(); // The dropdown's first item toggles enabled state (currently enabled -> Disable). var disableButton = cut.WaitForElement("button.dropdown-item"); await disableButton.ClickAsync(new()); await _admin.Received(1).SetEnabledAsync("aaaaaaaaaaaa1111", false, Arg.Any()); } }