diff --git a/src/ScadaLink.CLI/Commands/AuditCommands.cs b/src/ScadaLink.CLI/Commands/AuditCommands.cs index 9a953e3..b411b91 100644 --- a/src/ScadaLink.CLI/Commands/AuditCommands.cs +++ b/src/ScadaLink.CLI/Commands/AuditCommands.cs @@ -26,11 +26,16 @@ public static class AuditCommands { var sinceOption = new Option("--since") { Description = "Start time: relative (1h, 24h, 7d) or ISO-8601" }; var untilOption = new Option("--until") { Description = "End time: relative (1h, 24h, 7d) or ISO-8601" }; - var channelOption = new Option("--channel") { Description = "Filter by channel (OutboundApi, OutboundDb, Notification, InboundApi)" }; - var kindOption = new Option("--kind") { Description = "Filter by event kind" }; - var statusOption = new Option("--status") { Description = "Filter by status (single value)" }; + var channelOption = new Option("--channel") { Description = "Filter by channel (ApiOutbound, DbOutbound, Notification, ApiInbound)" }; + channelOption.AcceptOnlyFromAmong("ApiOutbound", "DbOutbound", "Notification", "ApiInbound"); + var kindOption = new Option("--kind") { Description = "Filter by event kind (ApiCall, ApiCallCached, DbWrite, DbWriteCached, NotifySend, NotifyDeliver, InboundRequest, InboundAuthFailure, CachedSubmit, CachedResolve)" }; + kindOption.AcceptOnlyFromAmong( + "ApiCall", "ApiCallCached", "DbWrite", "DbWriteCached", "NotifySend", + "NotifyDeliver", "InboundRequest", "InboundAuthFailure", "CachedSubmit", "CachedResolve"); + var statusOption = new Option("--status") { Description = "Filter by status (Submitted, Forwarded, Attempted, Delivered, Failed, Parked, Discarded, Skipped)" }; + statusOption.AcceptOnlyFromAmong( + "Submitted", "Forwarded", "Attempted", "Delivered", "Failed", "Parked", "Discarded", "Skipped"); var siteOption = new Option("--site") { Description = "Filter by source site ID" }; - var instanceOption = new Option("--instance") { Description = "Filter by instance" }; var targetOption = new Option("--target") { Description = "Filter by target (external system, DB connection, notification list)" }; var actorOption = new Option("--actor") { Description = "Filter by actor" }; var correlationIdOption = new Option("--correlation-id") { Description = "Filter by correlation ID" }; @@ -46,7 +51,6 @@ public static class AuditCommands cmd.Add(kindOption); cmd.Add(statusOption); cmd.Add(siteOption); - cmd.Add(instanceOption); cmd.Add(targetOption); cmd.Add(actorOption); cmd.Add(correlationIdOption); @@ -74,7 +78,6 @@ public static class AuditCommands Kind = result.GetValue(kindOption), Status = result.GetValue(statusOption), Site = result.GetValue(siteOption), - Instance = result.GetValue(instanceOption), Target = result.GetValue(targetOption), Actor = result.GetValue(actorOption), CorrelationId = result.GetValue(correlationIdOption), diff --git a/src/ScadaLink.CLI/Commands/AuditQueryHelpers.cs b/src/ScadaLink.CLI/Commands/AuditQueryHelpers.cs index 0066dbf..f18f971 100644 --- a/src/ScadaLink.CLI/Commands/AuditQueryHelpers.cs +++ b/src/ScadaLink.CLI/Commands/AuditQueryHelpers.cs @@ -18,7 +18,6 @@ public sealed class AuditQueryArgs public string? Kind { get; set; } public string? Status { get; set; } public string? Site { get; set; } - public string? Instance { get; set; } public string? Target { get; set; } public string? Actor { get; set; } public string? CorrelationId { get; set; } @@ -102,7 +101,6 @@ public static class AuditQueryHelpers Add("status", args.ErrorsOnly ? "Failed" : args.Status); Add("sourceSiteId", args.Site); - Add("instance", args.Instance); Add("target", args.Target); Add("actor", args.Actor); Add("correlationId", args.CorrelationId); diff --git a/src/ScadaLink.CLI/README.md b/src/ScadaLink.CLI/README.md index 8c76d4c..eed1ccf 100644 --- a/src/ScadaLink.CLI/README.md +++ b/src/ScadaLink.CLI/README.md @@ -1078,11 +1078,10 @@ scadalink --url audit query [options] |--------|----------|---------|-------------| | `--since` | no | — | Start time: relative (`1h`, `24h`, `7d`) or ISO-8601 | | `--until` | no | — | End time: relative (`1h`, `24h`, `7d`) or ISO-8601 | -| `--channel` | no | — | Filter by channel (`OutboundApi`, `OutboundDb`, `Notification`, `InboundApi`) | -| `--kind` | no | — | Filter by event kind | -| `--status` | no | — | Filter by status (single value) | +| `--channel` | no | — | Filter by channel (`ApiOutbound`, `DbOutbound`, `Notification`, `ApiInbound`) | +| `--kind` | no | — | Filter by event kind (`ApiCall`, `ApiCallCached`, `DbWrite`, `DbWriteCached`, `NotifySend`, `NotifyDeliver`, `InboundRequest`, `InboundAuthFailure`, `CachedSubmit`, `CachedResolve`) | +| `--status` | no | — | Filter by status (`Submitted`, `Forwarded`, `Attempted`, `Delivered`, `Failed`, `Parked`, `Discarded`, `Skipped`) | | `--site` | no | — | Filter by source site ID | -| `--instance` | no | — | Filter by instance | | `--target` | no | — | Filter by target (external system, DB connection, notification list) | | `--actor` | no | — | Filter by actor | | `--correlation-id` | no | — | Filter by correlation ID | diff --git a/tests/ScadaLink.CLI.Tests/Commands/AuditQueryCommandTests.cs b/tests/ScadaLink.CLI.Tests/Commands/AuditQueryCommandTests.cs index 130c8c7..b3840c5 100644 --- a/tests/ScadaLink.CLI.Tests/Commands/AuditQueryCommandTests.cs +++ b/tests/ScadaLink.CLI.Tests/Commands/AuditQueryCommandTests.cs @@ -58,11 +58,10 @@ public class AuditQueryCommandTests { Since = "1h", Until = "2026-05-20T12:00:00Z", - Channel = "OutboundApi", - Kind = "CachedCall", + Channel = "ApiOutbound", + Kind = "ApiCallCached", Status = "Delivered", Site = "site-1", - Instance = "pump-7", Target = "weather-api", Actor = "multi-role", CorrelationId = "abc-123", @@ -73,11 +72,12 @@ public class AuditQueryCommandTests var qs = AuditQueryHelpers.BuildQueryString(args, now, afterOccurredAtUtc: null, afterEventId: null); var parsed = HttpUtility.ParseQueryString(qs.TrimStart('?')); - Assert.Equal("OutboundApi", parsed["channel"]); - Assert.Equal("CachedCall", parsed["kind"]); + Assert.Equal("ApiOutbound", parsed["channel"]); + Assert.Equal("ApiCallCached", parsed["kind"]); Assert.Equal("Delivered", parsed["status"]); Assert.Equal("site-1", parsed["sourceSiteId"]); - Assert.Equal("pump-7", parsed["instance"]); + // --instance was dropped: AuditLogQueryFilter has no instance column. + Assert.Null(parsed["instance"]); Assert.Equal("weather-api", parsed["target"]); Assert.Equal("multi-role", parsed["actor"]); Assert.Equal("abc-123", parsed["correlationId"]); @@ -243,4 +243,43 @@ public class AuditQueryCommandTests var parse = root.Parse(new[] { "audit", "query", "--format", "table" }); Assert.Empty(parse.Errors); } + + // ---- Enum-name validation (fast-fail) ---------------------------------- + + [Fact] + public void Query_ChannelWithRealEnumName_IsAccepted() + { + var root = AuditCommandTestHarness.BuildRoot(); + var parse = root.Parse(new[] { "audit", "query", "--channel", "ApiOutbound" }); + Assert.Empty(parse.Errors); + } + + [Fact] + public void Query_ChannelWithInvalidName_FailsFast_NonZeroExit() + { + // "OutboundApi" is the old (non-existent) name; the real enum is "ApiOutbound". + var root = AuditCommandTestHarness.BuildRoot(); + var (exit, _, err) = AuditCommandTestHarness.Invoke(root, "audit", "query", "--channel", "OutboundApi"); + Assert.NotEqual(0, exit); + Assert.NotEqual("", err); + Assert.Contains("OutboundApi", err); + } + + [Fact] + public void Query_KindWithInvalidName_FailsFast_NonZeroExit() + { + var root = AuditCommandTestHarness.BuildRoot(); + var (exit, _, err) = AuditCommandTestHarness.Invoke(root, "audit", "query", "--kind", "CachedCall"); + Assert.NotEqual(0, exit); + Assert.NotEqual("", err); + } + + [Fact] + public void Query_StatusWithInvalidName_FailsFast_NonZeroExit() + { + var root = AuditCommandTestHarness.BuildRoot(); + var (exit, _, err) = AuditCommandTestHarness.Invoke(root, "audit", "query", "--status", "Bogus"); + Assert.NotEqual(0, exit); + Assert.NotEqual("", err); + } }