From 93ecbbd410a67c280c5c47a222f5360ef3b1e94f Mon Sep 17 00:00:00 2001 From: Alexandre Brandizzi Date: Wed, 16 Sep 2026 20:25:45 -0300 Subject: [PATCH] test(dashboard): guard Stats wire object for KPI counts (SH-353) The added service test asserts DashboardStatsDTO, which already carried ScheduledTomorrow, PendingUplifts and AvetaPending, so it does not guard the regression that was actually shipped: the DashboardController Stats anonymous response dropping those keys and leaving the tiles empty. Add DashboardControllerTests.GetStats_SerialisesKpiCountsOnWireObject, which drives the controller and asserts the three keys are present on the serialised wire object, so re-dropping any of them fails a test. Also reword the pending-uplifts comment in DashboardServiceTests: the Pending-only-vs-ChangesRequested exclusion is the behaviour the code ships today, an open product call, not a settled review decision. --- .../DashboardControllerTests.cs | 54 +++++++++++++++++++ .../DashboardServiceTests.cs | 6 ++- 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 Api.SeaHavenIndustries.Tests/DashboardControllerTests.cs diff --git a/Api.SeaHavenIndustries.Tests/DashboardControllerTests.cs b/Api.SeaHavenIndustries.Tests/DashboardControllerTests.cs new file mode 100644 index 0000000..4ef3b58 --- /dev/null +++ b/Api.SeaHavenIndustries.Tests/DashboardControllerTests.cs @@ -0,0 +1,54 @@ +using System.Security.Claims; +using Api.SeaHavenIndustries.Controllers; +using FluentAssertions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Moq; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; +using Xunit; + +namespace Api.SeaHavenIndustries.Tests; + +public class DashboardControllerTests +{ + private static object Prop(object source, string name) => + source.GetType().GetProperty(name)!.GetValue(source)!; + + private static DashboardController NewController(Mock service) => + new(service.Object) + { + ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext() + } + }; + + // Guards the Stats wire object, not the service DTO. GetKpiCountsAsync and + // DashboardStatsDTO already carried these three counts; the regression this + // pins is the controller's anonymous response silently dropping them, which + // is what left the tiles with nothing to read. + [Fact] + public async Task GetStats_SerialisesKpiCountsOnWireObject() + { + var service = new Mock(); + service.Setup(s => s.GetStatsAsync( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(new DashboardStatsDTO + { + ScheduledTomorrow = 3, + PendingUplifts = 5, + AvetaPending = 7 + }); + + var result = await NewController(service).GetStats( + new DashboardStatsQueryDTO(), CancellationToken.None); + + var body = result.Should().BeOfType().Subject.Value!; + ((int)Prop(body, "scheduledTomorrow")).Should().Be(3); + ((int)Prop(body, "pendingUplifts")).Should().Be(5); + ((int)Prop(body, "avetaPending")).Should().Be(7); + } +} diff --git a/Api.SeaHavenIndustries.Tests/DashboardServiceTests.cs b/Api.SeaHavenIndustries.Tests/DashboardServiceTests.cs index 1b2afb4..1d4e8ec 100644 --- a/Api.SeaHavenIndustries.Tests/DashboardServiceTests.cs +++ b/Api.SeaHavenIndustries.Tests/DashboardServiceTests.cs @@ -131,8 +131,10 @@ public class DashboardServiceTests ctx.SaveChanges(); // Pending-uplifts tile: only Status == "Pending" is an outstanding uplift - // awaiting an approval decision. A "ChangesRequested" request has been sent - // back to the vendor and must NOT be counted (SH-353 review decision). + // awaiting an approval decision. This test pins the behaviour the code + // ships today: a "ChangesRequested" request has been sent back to the + // vendor, so it is NOT counted. Whether pendingUplifts should also include + // ChangesRequested is an open product call, not a settled review decision. ctx.Set().AddRange( new DispatchUpliftRequest { DispatchId = dispatch.Id, Status = "Pending", IsDeleted = false }, new DispatchUpliftRequest { DispatchId = dispatch.Id, Status = "ChangesRequested", IsDeleted = false });