fix(cli): correct audit query channel/kind/status enum names + drop dead --instance flag (#23 M8)

This commit is contained in:
Joseph Doherty
2026-05-20 22:13:26 -04:00
parent 36d58e8988
commit ff004e2e48
4 changed files with 57 additions and 18 deletions

View File

@@ -26,11 +26,16 @@ public static class AuditCommands
{ {
var sinceOption = new Option<string?>("--since") { Description = "Start time: relative (1h, 24h, 7d) or ISO-8601" }; var sinceOption = new Option<string?>("--since") { Description = "Start time: relative (1h, 24h, 7d) or ISO-8601" };
var untilOption = new Option<string?>("--until") { Description = "End time: relative (1h, 24h, 7d) or ISO-8601" }; var untilOption = new Option<string?>("--until") { Description = "End time: relative (1h, 24h, 7d) or ISO-8601" };
var channelOption = new Option<string?>("--channel") { Description = "Filter by channel (OutboundApi, OutboundDb, Notification, InboundApi)" }; var channelOption = new Option<string?>("--channel") { Description = "Filter by channel (ApiOutbound, DbOutbound, Notification, ApiInbound)" };
var kindOption = new Option<string?>("--kind") { Description = "Filter by event kind" }; channelOption.AcceptOnlyFromAmong("ApiOutbound", "DbOutbound", "Notification", "ApiInbound");
var statusOption = new Option<string?>("--status") { Description = "Filter by status (single value)" }; var kindOption = new Option<string?>("--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<string?>("--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<string?>("--site") { Description = "Filter by source site ID" }; var siteOption = new Option<string?>("--site") { Description = "Filter by source site ID" };
var instanceOption = new Option<string?>("--instance") { Description = "Filter by instance" };
var targetOption = new Option<string?>("--target") { Description = "Filter by target (external system, DB connection, notification list)" }; var targetOption = new Option<string?>("--target") { Description = "Filter by target (external system, DB connection, notification list)" };
var actorOption = new Option<string?>("--actor") { Description = "Filter by actor" }; var actorOption = new Option<string?>("--actor") { Description = "Filter by actor" };
var correlationIdOption = new Option<string?>("--correlation-id") { Description = "Filter by correlation ID" }; var correlationIdOption = new Option<string?>("--correlation-id") { Description = "Filter by correlation ID" };
@@ -46,7 +51,6 @@ public static class AuditCommands
cmd.Add(kindOption); cmd.Add(kindOption);
cmd.Add(statusOption); cmd.Add(statusOption);
cmd.Add(siteOption); cmd.Add(siteOption);
cmd.Add(instanceOption);
cmd.Add(targetOption); cmd.Add(targetOption);
cmd.Add(actorOption); cmd.Add(actorOption);
cmd.Add(correlationIdOption); cmd.Add(correlationIdOption);
@@ -74,7 +78,6 @@ public static class AuditCommands
Kind = result.GetValue(kindOption), Kind = result.GetValue(kindOption),
Status = result.GetValue(statusOption), Status = result.GetValue(statusOption),
Site = result.GetValue(siteOption), Site = result.GetValue(siteOption),
Instance = result.GetValue(instanceOption),
Target = result.GetValue(targetOption), Target = result.GetValue(targetOption),
Actor = result.GetValue(actorOption), Actor = result.GetValue(actorOption),
CorrelationId = result.GetValue(correlationIdOption), CorrelationId = result.GetValue(correlationIdOption),

View File

@@ -18,7 +18,6 @@ public sealed class AuditQueryArgs
public string? Kind { get; set; } public string? Kind { get; set; }
public string? Status { get; set; } public string? Status { get; set; }
public string? Site { get; set; } public string? Site { get; set; }
public string? Instance { get; set; }
public string? Target { get; set; } public string? Target { get; set; }
public string? Actor { get; set; } public string? Actor { get; set; }
public string? CorrelationId { get; set; } public string? CorrelationId { get; set; }
@@ -102,7 +101,6 @@ public static class AuditQueryHelpers
Add("status", args.ErrorsOnly ? "Failed" : args.Status); Add("status", args.ErrorsOnly ? "Failed" : args.Status);
Add("sourceSiteId", args.Site); Add("sourceSiteId", args.Site);
Add("instance", args.Instance);
Add("target", args.Target); Add("target", args.Target);
Add("actor", args.Actor); Add("actor", args.Actor);
Add("correlationId", args.CorrelationId); Add("correlationId", args.CorrelationId);

View File

@@ -1078,11 +1078,10 @@ scadalink --url <url> audit query [options]
|--------|----------|---------|-------------| |--------|----------|---------|-------------|
| `--since` | no | — | Start time: relative (`1h`, `24h`, `7d`) or ISO-8601 | | `--since` | no | — | Start time: relative (`1h`, `24h`, `7d`) or ISO-8601 |
| `--until` | no | — | End 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`) | | `--channel` | no | — | Filter by channel (`ApiOutbound`, `DbOutbound`, `Notification`, `ApiInbound`) |
| `--kind` | no | — | Filter by event kind | | `--kind` | no | — | Filter by event kind (`ApiCall`, `ApiCallCached`, `DbWrite`, `DbWriteCached`, `NotifySend`, `NotifyDeliver`, `InboundRequest`, `InboundAuthFailure`, `CachedSubmit`, `CachedResolve`) |
| `--status` | no | — | Filter by status (single value) | | `--status` | no | — | Filter by status (`Submitted`, `Forwarded`, `Attempted`, `Delivered`, `Failed`, `Parked`, `Discarded`, `Skipped`) |
| `--site` | no | — | Filter by source site ID | | `--site` | no | — | Filter by source site ID |
| `--instance` | no | — | Filter by instance |
| `--target` | no | — | Filter by target (external system, DB connection, notification list) | | `--target` | no | — | Filter by target (external system, DB connection, notification list) |
| `--actor` | no | — | Filter by actor | | `--actor` | no | — | Filter by actor |
| `--correlation-id` | no | — | Filter by correlation ID | | `--correlation-id` | no | — | Filter by correlation ID |

View File

@@ -58,11 +58,10 @@ public class AuditQueryCommandTests
{ {
Since = "1h", Since = "1h",
Until = "2026-05-20T12:00:00Z", Until = "2026-05-20T12:00:00Z",
Channel = "OutboundApi", Channel = "ApiOutbound",
Kind = "CachedCall", Kind = "ApiCallCached",
Status = "Delivered", Status = "Delivered",
Site = "site-1", Site = "site-1",
Instance = "pump-7",
Target = "weather-api", Target = "weather-api",
Actor = "multi-role", Actor = "multi-role",
CorrelationId = "abc-123", CorrelationId = "abc-123",
@@ -73,11 +72,12 @@ public class AuditQueryCommandTests
var qs = AuditQueryHelpers.BuildQueryString(args, now, afterOccurredAtUtc: null, afterEventId: null); var qs = AuditQueryHelpers.BuildQueryString(args, now, afterOccurredAtUtc: null, afterEventId: null);
var parsed = HttpUtility.ParseQueryString(qs.TrimStart('?')); var parsed = HttpUtility.ParseQueryString(qs.TrimStart('?'));
Assert.Equal("OutboundApi", parsed["channel"]); Assert.Equal("ApiOutbound", parsed["channel"]);
Assert.Equal("CachedCall", parsed["kind"]); Assert.Equal("ApiCallCached", parsed["kind"]);
Assert.Equal("Delivered", parsed["status"]); Assert.Equal("Delivered", parsed["status"]);
Assert.Equal("site-1", parsed["sourceSiteId"]); 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("weather-api", parsed["target"]);
Assert.Equal("multi-role", parsed["actor"]); Assert.Equal("multi-role", parsed["actor"]);
Assert.Equal("abc-123", parsed["correlationId"]); Assert.Equal("abc-123", parsed["correlationId"]);
@@ -243,4 +243,43 @@ public class AuditQueryCommandTests
var parse = root.Parse(new[] { "audit", "query", "--format", "table" }); var parse = root.Parse(new[] { "audit", "query", "--format", "table" });
Assert.Empty(parse.Errors); 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);
}
} }