feat(audit): multi-value filters across ManagementService, CLI and Central UI

This commit is contained in:
Joseph Doherty
2026-05-21 05:27:17 -04:00
parent 37c7a0e5ac
commit 2a76be1f94
11 changed files with 523 additions and 146 deletions

View File

@@ -77,12 +77,30 @@ public class AuditFilterBarTests : BunitContext
cut.Find("[data-test=\"filter-apply\"]").Click();
Assert.NotNull(captured);
// Task 8: the filter dimension is multi-value now; ToFilter still collapses
// the chip selection to a single-element list (Task 9 widens that).
Assert.Equal(new[] { AuditChannel.ApiOutbound }, captured!.Channels);
Assert.Equal("Plant-A-OPC", captured.Target);
}
[Fact]
public void Apply_WithMultipleChannelChips_PassesAllSelectedChannels()
{
// Task 9: ToFilter no longer collapses the chip multi-select — every
// selected channel chip reaches the filter's Channels list.
AuditLogQueryFilter? captured = null;
var cut = Render<AuditFilterBar>(p => p
.Add(c => c.OnFilterChanged, EventCallback.Factory.Create<AuditLogQueryFilter>(this, f => captured = f)));
cut.Find("[data-test=\"chip-channel-ApiOutbound\"]").Click();
cut.Find("[data-test=\"chip-channel-Notification\"]").Click();
cut.Find("[data-test=\"filter-apply\"]").Click();
Assert.NotNull(captured);
Assert.NotNull(captured!.Channels);
Assert.Equal(2, captured.Channels!.Count);
Assert.Contains(AuditChannel.ApiOutbound, captured.Channels);
Assert.Contains(AuditChannel.Notification, captured.Channels);
}
[Fact]
public void Channel_Narrows_Kind_Options_When_Selected()
{
@@ -119,8 +137,12 @@ public class AuditFilterBarTests : BunitContext
cut.Find("[data-test=\"filter-apply\"]").Click();
Assert.NotNull(captured);
// Single-value collapse contract (Task 8): Failed leads the non-success set.
Assert.Equal(new[] { AuditStatus.Failed }, captured!.Statuses);
// Task 9: Errors-only targets the full non-success set {Failed, Parked, Discarded}.
Assert.NotNull(captured!.Statuses);
Assert.Equal(3, captured.Statuses!.Count);
Assert.Contains(AuditStatus.Failed, captured.Statuses);
Assert.Contains(AuditStatus.Parked, captured.Statuses);
Assert.Contains(AuditStatus.Discarded, captured.Statuses);
// Now pin an explicit Status chip — Errors-only must yield (chip wins).
cut.Find("[data-test=\"chip-status-Delivered\"]").Click();
@@ -129,6 +151,26 @@ public class AuditFilterBarTests : BunitContext
Assert.Equal(new[] { AuditStatus.Delivered }, captured!.Statuses);
}
[Fact]
public void Apply_WithMultipleStatusChips_PassesAllSelectedStatuses()
{
// Task 9: multiple explicit Status chips all reach the filter — and they
// win over the Errors-only default.
AuditLogQueryFilter? captured = null;
var cut = Render<AuditFilterBar>(p => p
.Add(c => c.OnFilterChanged, EventCallback.Factory.Create<AuditLogQueryFilter>(this, f => captured = f)));
cut.Find("[data-test=\"chip-status-Delivered\"]").Click();
cut.Find("[data-test=\"chip-status-Failed\"]").Click();
cut.Find("[data-test=\"filter-apply\"]").Click();
Assert.NotNull(captured);
Assert.NotNull(captured!.Statuses);
Assert.Equal(2, captured.Statuses!.Count);
Assert.Contains(AuditStatus.Delivered, captured.Statuses);
Assert.Contains(AuditStatus.Failed, captured.Statuses);
}
[Fact]
public void TimeRange_LastHour_PopulatesFromUtc_ApproxOneHourAgo()
{

View File

@@ -74,4 +74,22 @@ public class AuditLogPageExportUrlTests
Assert.Single(query);
Assert.Equal("Notification", query["channel"]);
}
[Fact]
public void BuildExportUrl_MultiValueDimensions_EmitRepeatedParams()
{
// Task 9: each multi-value dimension emits one repeated query-string key
// per selected value so the export endpoint's ParseFilter sees them all.
var filter = new AuditLogQueryFilter(
Channels: new[] { AuditChannel.ApiOutbound, AuditChannel.DbOutbound },
Statuses: new[] { AuditStatus.Failed, AuditStatus.Parked },
SourceSiteIds: new[] { "plant-a", "plant-b" });
var url = AuditLogPage.BuildExportUrl(filter);
var query = QueryHelpers.ParseQuery(new Uri("http://x" + url).Query);
Assert.Equal(new[] { "ApiOutbound", "DbOutbound" }, query["channel"].ToArray());
Assert.Equal(new[] { "Failed", "Parked" }, query["status"].ToArray());
Assert.Equal(new[] { "plant-a", "plant-b" }, query["site"].ToArray());
}
}