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")]