refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
using Bunit;
|
||||
using Bunit.TestDoubles;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Health;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Components.Health;
|
||||
|
||||
/// <summary>
|
||||
/// bUnit tests for <see cref="AuditKpiTiles"/> (#23 M7 Bundle E / M7-T13). The
|
||||
/// component renders three Bootstrap-card tiles — Volume, Error Rate, Backlog —
|
||||
/// from a single <see cref="AuditLogKpiSnapshot"/>. The tests pin:
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <item>Three-tile render contract (data-test attributes for stable selectors).</item>
|
||||
/// <item>Error-rate maths: <c>ErrorEventsLastHour / TotalEventsLastHour</c> with
|
||||
/// safe zero-events handling (no DivideByZero, displays "0.0%").</item>
|
||||
/// <item>Unavailable snapshot renders em dashes plus the error message.</item>
|
||||
/// <item>Tile clicks navigate to the correct pre-filtered Audit Log URL.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class AuditKpiTilesTests : BunitContext
|
||||
{
|
||||
private static AuditLogKpiSnapshot MakeSnapshot(long total, long errors, long backlog) =>
|
||||
new(total, errors, backlog, new DateTime(2026, 5, 20, 10, 0, 0, DateTimeKind.Utc));
|
||||
|
||||
[Fact]
|
||||
public void Renders_ThreeTiles_FromSnapshot()
|
||||
{
|
||||
var cut = Render<AuditKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(total: 120, errors: 3, backlog: 7))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
// Three stable data-test selectors — these are the contract for both
|
||||
// tests and any future Playwright sweep.
|
||||
Assert.Contains("data-test=\"audit-kpi-volume\"", cut.Markup);
|
||||
Assert.Contains("data-test=\"audit-kpi-error-rate\"", cut.Markup);
|
||||
Assert.Contains("data-test=\"audit-kpi-backlog\"", cut.Markup);
|
||||
|
||||
// Tile values render the snapshot's counters.
|
||||
Assert.Contains("120", cut.Markup); // volume
|
||||
Assert.Contains("7", cut.Markup); // backlog
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ErrorRate_Computed_From_Total_AndErrors()
|
||||
{
|
||||
// 5 errors out of 100 → 5.0%.
|
||||
var cut = Render<AuditKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(total: 100, errors: 5, backlog: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
Assert.Contains("5.0%", cut.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZeroEvents_DoesNotDivideByZero_RendersZeroPercent()
|
||||
{
|
||||
// Total = 0 → naïve division would throw or yield NaN. The tile must
|
||||
// render "0.0%" instead (zero events means zero errors too — a real
|
||||
// signal, not an unavailability marker).
|
||||
var cut = Render<AuditKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(total: 0, errors: 0, backlog: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
Assert.Contains("0.0%", cut.Markup);
|
||||
// And the volume tile shows "0", not an em dash — the snapshot itself
|
||||
// is available; the system was just quiet for the hour.
|
||||
Assert.Contains("data-test=\"audit-kpi-volume\"", cut.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnavailableSnapshot_RendersEmDashes_AndErrorMessage()
|
||||
{
|
||||
var cut = Render<AuditKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, (AuditLogKpiSnapshot?)null)
|
||||
.Add(c => c.IsAvailable, false)
|
||||
.Add(c => c.ErrorMessage, "DB connection refused"));
|
||||
|
||||
// All three tiles show em dashes — em dash (U+2014) "—" must appear.
|
||||
Assert.Contains("—", cut.Markup);
|
||||
// Inline error message renders below.
|
||||
Assert.Contains("Audit KPIs unavailable", cut.Markup);
|
||||
Assert.Contains("DB connection refused", cut.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ErrorRateTile_Click_NavigatesToAuditLog_WithFailedStatusFilter()
|
||||
{
|
||||
var cut = Render<AuditKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(total: 50, errors: 3, backlog: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
// bUnit's BunitNavigationManager records the last URI a Navigation.NavigateTo call hit.
|
||||
var nav = (BunitNavigationManager)Services.GetRequiredService<NavigationManager>();
|
||||
|
||||
var tile = cut.Find("[data-test=\"audit-kpi-error-rate\"]");
|
||||
tile.Click();
|
||||
|
||||
// Spec: error-rate tile drills into ?status=Failed.
|
||||
Assert.Contains("/audit/log?status=Failed", nav.Uri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VolumeTile_Click_NavigatesToUnfilteredAuditLog()
|
||||
{
|
||||
var cut = Render<AuditKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(total: 50, errors: 3, backlog: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var nav = (BunitNavigationManager)Services.GetRequiredService<NavigationManager>();
|
||||
var tile = cut.Find("[data-test=\"audit-kpi-volume\"]");
|
||||
tile.Click();
|
||||
|
||||
// Unfiltered /audit/log — no query string.
|
||||
Assert.EndsWith("/audit/log", nav.Uri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BacklogTile_Click_NavigatesToAuditLog()
|
||||
{
|
||||
var cut = Render<AuditKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(total: 50, errors: 0, backlog: 12))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var nav = (BunitNavigationManager)Services.GetRequiredService<NavigationManager>();
|
||||
var tile = cut.Find("[data-test=\"audit-kpi-backlog\"]");
|
||||
tile.Click();
|
||||
|
||||
Assert.EndsWith("/audit/log", nav.Uri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NonzeroErrorRate_GetsWarningBorder_NotDangerBelowTenPercent()
|
||||
{
|
||||
// 5% is < 10% → warning border, not danger.
|
||||
var cut = Render<AuditKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(total: 100, errors: 5, backlog: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var tile = cut.Find("[data-test=\"audit-kpi-error-rate\"]");
|
||||
Assert.Contains("border-warning", tile.GetAttribute("class") ?? string.Empty);
|
||||
Assert.DoesNotContain("border-danger", tile.GetAttribute("class") ?? string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HighErrorRate_GetsDangerBorder()
|
||||
{
|
||||
// 25% is > 10% → danger border.
|
||||
var cut = Render<AuditKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(total: 100, errors: 25, backlog: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var tile = cut.Find("[data-test=\"audit-kpi-error-rate\"]");
|
||||
Assert.Contains("border-danger", tile.GetAttribute("class") ?? string.Empty);
|
||||
}
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
using Bunit;
|
||||
using Bunit.TestDoubles;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Health;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Components.Health;
|
||||
|
||||
/// <summary>
|
||||
/// bUnit tests for <see cref="SiteCallKpiTiles"/> (Site Call Audit #22, Task 7).
|
||||
/// The component renders three Bootstrap-card tiles — Buffered, Stuck, Parked —
|
||||
/// from a single <see cref="SiteCallKpiResponse"/> snapshot. The tests pin:
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <item>Three-tile render contract (data-test attributes for stable selectors).</item>
|
||||
/// <item>Tile values render the snapshot's counters.</item>
|
||||
/// <item>Threshold borders fire correctly — danger on Parked > 0, warning
|
||||
/// on Stuck > 0, none when those counts are zero, none on Buffered.</item>
|
||||
/// <item>Unavailable snapshot renders em dashes plus the error message.</item>
|
||||
/// <item>Tile clicks navigate to the correct pre-filtered Site Calls report URL.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class SiteCallKpiTilesTests : BunitContext
|
||||
{
|
||||
private static SiteCallKpiResponse MakeSnapshot(int buffered, int parked, int stuck) =>
|
||||
new(
|
||||
CorrelationId: "k",
|
||||
Success: true,
|
||||
ErrorMessage: null,
|
||||
BufferedCount: buffered,
|
||||
ParkedCount: parked,
|
||||
FailedLastInterval: 0,
|
||||
DeliveredLastInterval: 0,
|
||||
OldestPendingAge: null,
|
||||
StuckCount: stuck);
|
||||
|
||||
[Fact]
|
||||
public void Renders_ThreeTiles_FromSnapshot()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 120, parked: 3, stuck: 7))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
// Three stable data-test selectors — the contract for both these tests
|
||||
// and any future Playwright sweep.
|
||||
Assert.Contains("data-test=\"site-call-kpi-buffered\"", cut.Markup);
|
||||
Assert.Contains("data-test=\"site-call-kpi-stuck\"", cut.Markup);
|
||||
Assert.Contains("data-test=\"site-call-kpi-parked\"", cut.Markup);
|
||||
|
||||
// Tile values render the snapshot's counters.
|
||||
Assert.Contains(">120<", cut.Markup); // buffered
|
||||
Assert.Contains(">7<", cut.Markup); // stuck
|
||||
Assert.Contains(">3<", cut.Markup); // parked
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnavailableSnapshot_RendersEmDashes_AndErrorMessage()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, (SiteCallKpiResponse?)null)
|
||||
.Add(c => c.IsAvailable, false)
|
||||
.Add(c => c.ErrorMessage, "site call repository unavailable"));
|
||||
|
||||
// All three tiles show em dashes — em dash (U+2014) "—" must appear.
|
||||
Assert.Contains("—", cut.Markup);
|
||||
// Inline error message renders below.
|
||||
Assert.Contains("Site Call KPIs unavailable", cut.Markup);
|
||||
Assert.Contains("site call repository unavailable", cut.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParkedTile_GetsDangerBorder_WhenParkedAboveZero()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 0, parked: 4, stuck: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-parked\"]");
|
||||
Assert.Contains("border-danger", tile.GetAttribute("class") ?? string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParkedTile_NoDangerBorder_WhenParkedZero()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 9, parked: 0, stuck: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-parked\"]");
|
||||
Assert.DoesNotContain("border-danger", tile.GetAttribute("class") ?? string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StuckTile_GetsWarningBorder_WhenStuckAboveZero()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 0, parked: 0, stuck: 6))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-stuck\"]");
|
||||
Assert.Contains("border-warning", tile.GetAttribute("class") ?? string.Empty);
|
||||
// Warning, not danger — Stuck is the softer signal.
|
||||
Assert.DoesNotContain("border-danger", tile.GetAttribute("class") ?? string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StuckTile_NoWarningBorder_WhenStuckZero()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 9, parked: 0, stuck: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-stuck\"]");
|
||||
Assert.DoesNotContain("border-warning", tile.GetAttribute("class") ?? string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BufferedTile_HasNoThresholdBorder_EvenWithHighCount()
|
||||
{
|
||||
// A non-zero buffer is normal operation — the Buffered tile is a plain
|
||||
// count tile and never gets a danger/warning border.
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 5000, parked: 0, stuck: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-buffered\"]");
|
||||
var cls = tile.GetAttribute("class") ?? string.Empty;
|
||||
Assert.DoesNotContain("border-danger", cls);
|
||||
Assert.DoesNotContain("border-warning", cls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BufferedTile_Click_NavigatesToUnfilteredSiteCallsReport()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 50, parked: 0, stuck: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var nav = (BunitNavigationManager)Services.GetRequiredService<NavigationManager>();
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-buffered\"]");
|
||||
tile.Click();
|
||||
|
||||
// Unfiltered /site-calls/report — no query string.
|
||||
Assert.EndsWith("/site-calls/report", nav.Uri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StuckTile_Click_NavigatesToSiteCallsReport_WithStuckFilter()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 0, parked: 0, stuck: 6))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var nav = (BunitNavigationManager)Services.GetRequiredService<NavigationManager>();
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-stuck\"]");
|
||||
tile.Click();
|
||||
|
||||
// Spec: Stuck tile drills into the report's "stuck only" filter.
|
||||
Assert.Contains("/site-calls/report?stuck=true", nav.Uri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParkedTile_Click_NavigatesToSiteCallsReport_WithParkedStatusFilter()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 0, parked: 4, stuck: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var nav = (BunitNavigationManager)Services.GetRequiredService<NavigationManager>();
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-parked\"]");
|
||||
tile.Click();
|
||||
|
||||
// Spec: Parked tile drills into ?status=Parked.
|
||||
Assert.Contains("/site-calls/report?status=Parked", nav.Uri);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user