test(playwright): Audit Log filter-combination + empty-state edge cases (Wave 4)

This commit is contained in:
Joseph Doherty
2026-06-07 03:55:34 -04:00
parent 0efbb66bc3
commit 79778e12b7
@@ -112,6 +112,89 @@ public class AuditLogPageTests
}
}
[SkippableFact]
public async Task FilterCombination_ChannelPlusTarget_NarrowsToMatch()
{
Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);
// Two rows differing in channel AND target. Applying the channel filter
// (ApiOutbound) AND a contains-match target filter together should narrow
// the grid to the single matching row — proving the filters AND rather
// than OR. The match row is ApiOutbound + target ".../match"; the other
// row is DbOutbound + target ".../other" and is excluded on both axes.
var runId = Guid.NewGuid().ToString("N");
var targetPrefix = $"playwright-test/wave4-audit/{runId}/";
var matchId = Guid.NewGuid();
var otherId = Guid.NewGuid();
var now = DateTime.UtcNow;
try
{
// Row 1 — the match: ApiOutbound channel, target ends in "match".
await AuditDataSeeder.InsertAuditEventAsync(
eventId: matchId,
occurredAtUtc: now,
channel: "ApiOutbound",
kind: "ApiCall",
status: "Delivered",
target: targetPrefix + "match",
httpStatus: 200,
durationMs: 42);
// Row 2 — the non-match channel: DbOutbound, target ends in "other".
await AuditDataSeeder.InsertAuditEventAsync(
eventId: otherId,
occurredAtUtc: now,
channel: "DbOutbound",
kind: "DbWrite",
status: "Delivered",
target: targetPrefix + "other",
durationMs: 17);
var page = await _fixture.NewAuthenticatedPageAsync();
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/audit/log");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
// Apply channel AND target together.
await page.Locator("[data-test='filter-channel-select']").SelectOptionAsync("ApiOutbound");
await page.FillAsync("#audit-target", targetPrefix + "match");
await page.ClickAsync("[data-test='filter-apply']");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
// The match row is visible; the other row is absent — the two filters
// AND, so a row must satisfy both to survive.
await Assertions.Expect(page.Locator($"[data-test='grid-row-{matchId}']")).ToBeVisibleAsync();
await Assertions.Expect(page.Locator($"[data-test='grid-row-{otherId}']")).ToHaveCountAsync(0);
}
finally
{
await AuditDataSeeder.DeleteByTargetPrefixAsync(targetPrefix);
}
}
[SkippableFact]
public async Task EmptyResults_AfterApply_ShowsEmptyState()
{
Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);
// A fresh random executionId GUID matches nothing — the ExecutionId
// filter is an exact match, so a never-seeded GUID is guaranteed-empty by
// construction. After Apply the grid renders zero rows and the empty-state
// message. No seeding, no teardown.
var page = await _fixture.NewAuthenticatedPageAsync();
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/audit/log");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
await page.FillAsync("#audit-execution-id", Guid.NewGuid().ToString());
await page.ClickAsync("[data-test='filter-apply']");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
// Zero grid rows AND the empty-state literal inside the grid container.
await Assertions.Expect(page.Locator("[data-test^='grid-row-']")).ToHaveCountAsync(0);
await Assertions.Expect(page.Locator("[data-test='audit-results-grid']"))
.ToContainTextAsync("No audit events match the current filter.");
}
[SkippableFact]
public async Task DrilldownDrawer_JsonPrettyPrintsRequestBody()
{