From 16056f8eb6c820b6a44be0f1505764f2fec37291 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 17 Aug 2026 06:21:12 -0400 Subject: [PATCH] =?UTF-8?q?fix(management):=20ParseDeploymentStatus=20reje?= =?UTF-8?q?cts=20ordinals=20=E2=80=94=20Enum.TryParse=20accepts=20"2"=20as?= =?UTF-8?q?=20InProgress=20despite=20the=20row-88=20names-only=20contract;?= =?UTF-8?q?=20same=20LooksNumeric=20guard=20as=20the=20audit=20strict=20pa?= =?UTF-8?q?rser=20(found=20by=20the=20Wave=206=20rows-98/99=20agent=20surv?= =?UTF-8?q?ey)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ManagementActor.cs | 8 ++++++- .../ManagementActorTests.cs | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs index 2df98948..3b145dc1 100644 --- a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs +++ b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs @@ -2745,7 +2745,13 @@ public class ManagementActor : ReceiveActor private static DeploymentStatus? ParseDeploymentStatus(string? status) { if (string.IsNullOrWhiteSpace(status)) return null; - if (Enum.TryParse(status, ignoreCase: true, out var parsed) + var trimmed = status.Trim(); + // Reject ordinals before TryParse — "2" parses to InProgress, which the + // documented names-only contract does not admit. No enum member name can + // start with a digit or a sign (same guard as AuditQueryParamParsers). + var looksNumeric = char.IsAsciiDigit(trimmed[0]) || trimmed[0] == '+' || trimmed[0] == '-'; + if (!looksNumeric + && Enum.TryParse(trimmed, ignoreCase: true, out var parsed) && Enum.IsDefined(parsed)) { return parsed; diff --git a/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs index 99b2c4ea..61dc64dd 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs @@ -1678,6 +1678,27 @@ public class ManagementActorTests : TestKit, IDisposable deployRepo.DidNotReceiveWithAnyArgs().QueryDeploymentListPageAsync(default!, default, default); } + [Theory] + [InlineData("2")] + [InlineData("+1")] + [InlineData("-1")] + public void QueryDeployments_OrdinalStatus_IsRejectedLikeAnyUnknownValue(string status) + { + // Register row 88 postscript (Wave 6): Enum.TryParse accepts ordinals, so + // `--status 2` silently resolved to InProgress despite the documented + // names-only contract. Same guard as AuditQueryParamParsers.LooksNumeric. + var deployRepo = Substitute.For(); + StubDeploymentPageQuery(deployRepo, (1, 1)); + _services.AddScoped(_ => deployRepo); + + var actor = CreateActor(); + actor.Tell(Envelope(new QueryDeploymentsCommand(Status: status), "Deployer")); + + var response = ExpectMsg(TimeSpan.FromSeconds(5)); + Assert.Contains("Success", response.Error); + deployRepo.DidNotReceiveWithAnyArgs().QueryDeploymentListPageAsync(default!, default, default); + } + [Theory] [InlineData("success")] [InlineData("SUCCESS")]