Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/DebugViewInstancePickerTests.cs
T
Joseph Doherty a0292d3063 fix(central-ui): Debug View picker — disabled-while-connected test; drop no-op handler; static label
Task 5 review follow-ups:

- New DebugViewInstancePickerTests.WhileConnected_ThePickerIsDisabled pins that
  a live connection locks the picker: both the input and the chevron carry
  `disabled`. It marks the page connected by reflection, the way the other
  DebugView page tests do (DebugViewAlarmTableTests) — driving the real gRPC
  stream is not available to a bUnit render.
- OnInstanceSelectionChanged() was already a no-op and its comment described the
  @bind that the picker replaced; deleted, and OnInstancePicked now just records
  the id (XML-documented).
- The ItemLabel lambda is hoisted to a private static InstanceLabel(Instance),
  passed as a method group.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 09:48:13 -04:00

177 lines
6.9 KiB
C#

using System.Reflection;
using System.Security.Claims;
using Bunit;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
using ZB.MOM.WW.ScadaBridge.Communication;
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
using DebugViewPage = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Deployment.DebugView;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Deployment;
/// <summary>
/// bUnit tests for the Debug View's instance selector after it moved from a flat
/// <c>&lt;select&gt;</c> to the shared <c>InstancePicker</c> area-tree combobox
/// (design 2026-09-08). Pins that the page hands the picker the site's areas and
/// its Enabled-only instance list, that drilling down the tree selects an instance,
/// that the selection still enables <em>Connect</em>, and that a live connection
/// locks the picker down.
/// </summary>
public class DebugViewInstancePickerTests : BunitContext
{
private const int SiteId = 1;
private void RegisterServices()
{
// DebugView touches localStorage on render; let bUnit answer loosely.
JSInterop.Mode = JSRuntimeMode.Loose;
var repo = Substitute.For<ITemplateEngineRepository>();
repo.GetInstancesBySiteIdAsync(SiteId).Returns(new List<Instance>
{
new("Sim01") { Id = 7, SiteId = SiteId, AreaId = 2, State = InstanceState.Enabled },
new("Off01") { Id = 8, SiteId = SiteId, State = InstanceState.Disabled },
});
repo.GetAreasBySiteIdAsync(SiteId).Returns(new List<Area>
{
new("Line1") { Id = 1, SiteId = SiteId },
new("Cell A") { Id = 2, SiteId = SiteId, ParentAreaId = 1 },
});
Services.AddSingleton(repo);
var siteRepo = Substitute.For<ISiteRepository>();
siteRepo.GetAllSitesAsync().Returns(new List<Site>
{
new("Site One", "site-a") { Id = SiteId },
});
Services.AddSingleton(siteRepo);
var comms = new CommunicationService(
Options.Create(new CommunicationOptions()),
NullLogger<CommunicationService>.Instance);
Services.AddSingleton(comms);
var grpcFactory = new SiteStreamGrpcClientFactory(NullLoggerFactory.Instance);
// An empty throwaway provider — these tests never call StartStreamAsync,
// so the provider is unused. (Services.BuildServiceProvider() would leak
// an undisposed provider.)
var debugStream = new DebugStreamService(
comms, new ServiceCollection().BuildServiceProvider(), grpcFactory,
NullLogger<DebugStreamService>.Instance);
Services.AddSingleton(debugStream);
// No SiteId claim => system-wide, so site 1 passes the scope re-check.
var identity = new ClaimsIdentity(
new[] { new Claim(ClaimTypes.Name, "deployer") }, "TestCookie");
var authState = new AuthenticationState(new ClaimsPrincipal(identity));
var stubAuth = new StubAuthStateProvider(authState);
Services.AddSingleton<AuthenticationStateProvider>(stubAuth);
Services.AddScoped(_ => new SiteScopeService(stubAuth));
}
private sealed class StubAuthStateProvider : AuthenticationStateProvider
{
private readonly AuthenticationState _state;
public StubAuthStateProvider(AuthenticationState state) => _state = state;
public override Task<AuthenticationState> GetAuthenticationStateAsync()
=> Task.FromResult(_state);
}
/// <summary>Renders the page, selects the seeded site and opens the picker panel.</summary>
private IRenderedComponent<DebugViewPage> RenderWithOpenPicker()
{
RegisterServices();
var cut = Render<DebugViewPage>();
cut.Find("[data-test='debug-site-select']").Change(SiteId.ToString());
cut.Find("[data-test='debug-instance-toggle']").Click();
return cut;
}
private static void ClickRow(IRenderedComponent<DebugViewPage> cut, string text) =>
cut.FindAll("[data-test='debug-instance-row']")
.First(r => r.TextContent.Trim() == text)
.Click();
[Fact]
public void InstanceSelector_IsThePicker_NotAFlatSelect()
{
RegisterServices();
var cut = Render<DebugViewPage>();
Assert.Single(cut.FindAll("[data-test='debug-instance-picker']"));
Assert.Empty(cut.FindAll("select[data-test='debug-instance-select']"));
}
[Fact]
public void OpeningThePicker_ShowsOnlyTheRootArea()
{
var cut = RenderWithOpenPicker();
var rows = cut.FindAll("[data-test='debug-instance-row']");
Assert.Single(rows);
Assert.Equal("Line1", rows[0].TextContent.Trim());
Assert.False(rows[0].HasAttribute("data-instance-id"));
}
[Fact]
public void DrillingDown_RevealsTheEnabledInstanceOnly()
{
var cut = RenderWithOpenPicker();
ClickRow(cut, "Line1");
ClickRow(cut, "Cell A");
var instanceRow = cut.Find("[data-instance-id='7']");
Assert.Equal("Sim01 (Enabled)", instanceRow.TextContent.Trim());
// Off01 is Disabled, so the page filters it out before the picker sees it.
Assert.DoesNotContain(
cut.FindAll("[data-test='debug-instance-row']"),
r => r.TextContent.Contains("Off01"));
}
[Fact]
public void PickingAnInstance_FillsTheField_AndEnablesConnect()
{
var cut = RenderWithOpenPicker();
ClickRow(cut, "Line1");
ClickRow(cut, "Cell A");
cut.Find("[data-instance-id='7']").Click();
Assert.Equal(
"Sim01 (Enabled)",
cut.Find("[data-test='debug-instance-input']").GetAttribute("value"));
var connect = cut.FindAll("button.btn.btn-primary.btn-sm")
.First(b => b.TextContent.Contains("Connect"));
Assert.False(connect.HasAttribute("disabled"));
}
[Fact]
public void WhileConnected_ThePickerIsDisabled()
{
RegisterServices();
var cut = Render<DebugViewPage>();
cut.Find("[data-test='debug-site-select']").Change(SiteId.ToString());
// Mark the page connected the way the other DebugView page tests do —
// driving the real gRPC stream is not available to a bUnit render.
cut.InvokeAsync(() => typeof(DebugViewPage)
.GetField("_connected", BindingFlags.Instance | BindingFlags.NonPublic)!
.SetValue(cut.Instance, true));
cut.Render();
Assert.True(cut.Find("[data-test='debug-instance-input']").HasAttribute("disabled"));
Assert.True(cut.Find("[data-test='debug-instance-toggle']").HasAttribute("disabled"));
}
}