feat(audit): multi-value filters across ManagementService, CLI and Central UI
This commit is contained in:
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user