From f5535ad5c1a77b13b213cadaa2965c26fc59e59c Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 7 Jun 2026 04:02:01 -0400 Subject: [PATCH] test(playwright): Audit Log non-API-no-cURL + drawer-close edge cases (Wave 4) --- .../Audit/AuditLogPageTests.cs | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Audit/AuditLogPageTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Audit/AuditLogPageTests.cs index ab4ff50b..f2b34c3b 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Audit/AuditLogPageTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Audit/AuditLogPageTests.cs @@ -765,4 +765,116 @@ public class AuditLogPageTests // The audit results grid never rendered for the unauthorized user. Assert.Equal(0, await page.Locator("[data-test='audit-results-grid']").CountAsync()); } + + [SkippableFact] + public async Task NonApiRow_Drawer_OmitsCopyAsCurl() + { + Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason); + + // The Copy-as-cURL action is gated on AuditEventDetail.IsApiChannel, which + // returns true only for ApiOutbound/ApiInbound. A DbOutbound row's drawer + // must therefore render WITHOUT the cURL button — the negative of + // CopyAsCurlButton_IsVisibleAndClickableForApiInbound. We seed one + // DbOutbound row, open its drawer, and assert the drawer is open AND the + // cURL button is absent. + var runId = Guid.NewGuid().ToString("N"); + var targetPrefix = $"playwright-test/wave4-audit2/{runId}/"; + var dbId = Guid.NewGuid(); + var now = DateTime.UtcNow; + + try + { + await AuditDataSeeder.InsertAuditEventAsync( + eventId: dbId, + occurredAtUtc: now, + channel: "DbOutbound", + kind: "DbWrite", + status: "Delivered", + target: targetPrefix + "dbrow", + durationMs: 17); + + var page = await _fixture.NewAuthenticatedPageAsync(); + await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/audit/log"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + // Filter to the seeded row (contains-match), then Apply to populate the + // grid — it stays empty until the user filters. + await page.FillAsync("#audit-target", targetPrefix + "dbrow"); + await page.ClickAsync("[data-test='filter-apply']"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + var row = page.Locator($"[data-test='grid-row-{dbId}']"); + await Assertions.Expect(row).ToBeVisibleAsync(); + await row.ClickAsync(); + + // The drawer opens for the DbOutbound row, but the cURL action — gated + // on IsApiChannel — is absent. + await Assertions.Expect(page.Locator("[data-test='audit-drilldown-drawer']")).ToBeVisibleAsync(); + await Assertions.Expect(page.Locator("[data-test='copy-as-curl']")).ToHaveCountAsync(0); + } + finally + { + await AuditDataSeeder.DeleteByTargetPrefixAsync(targetPrefix); + } + } + + [SkippableFact] + public async Task Drawer_CloseControls_DismissTheDrawer() + { + Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason); + + // The drawer offers two wired close paths — the header X + // ([data-test='drawer-close']) and the footer Close button + // ([data-test='drawer-close-footer']), both bound to HandleClose. We open + // the drawer, dismiss via the X, re-open, then dismiss via the footer, + // asserting the drawer is gone (count 0) after each. + // Escape-to-close is NOT wired on AuditDrilldownDrawer (only + // ExecutionDetailModal has a keydown handler); covering the X and + // footer-Close paths that ARE wired. + var runId = Guid.NewGuid().ToString("N"); + var targetPrefix = $"playwright-test/wave4-audit2c/{runId}/"; + var apiId = Guid.NewGuid(); + var now = DateTime.UtcNow; + + try + { + await AuditDataSeeder.InsertAuditEventAsync( + eventId: apiId, + occurredAtUtc: now, + channel: "ApiOutbound", + kind: "ApiCall", + status: "Delivered", + target: targetPrefix + "row", + httpStatus: 200, + durationMs: 42); + + var page = await _fixture.NewAuthenticatedPageAsync(); + await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/audit/log"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + await page.FillAsync("#audit-target", targetPrefix + "row"); + await page.ClickAsync("[data-test='filter-apply']"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + var row = page.Locator($"[data-test='grid-row-{apiId}']"); + var drawer = page.Locator("[data-test='audit-drilldown-drawer']"); + + // Open, then dismiss via the header X. + await Assertions.Expect(row).ToBeVisibleAsync(); + await row.ClickAsync(); + await Assertions.Expect(drawer).ToBeVisibleAsync(); + await page.ClickAsync("[data-test='drawer-close']"); + await Assertions.Expect(drawer).ToHaveCountAsync(0); + + // Re-open, then dismiss via the footer Close button. + await row.ClickAsync(); + await Assertions.Expect(drawer).ToBeVisibleAsync(); + await page.ClickAsync("[data-test='drawer-close-footer']"); + await Assertions.Expect(drawer).ToHaveCountAsync(0); + } + finally + { + await AuditDataSeeder.DeleteByTargetPrefixAsync(targetPrefix); + } + } }