From 5c190885da99b10eb9b2e9f7782e110c7aaf25ea Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 7 Jun 2026 03:31:28 -0400 Subject: [PATCH] test(playwright): generalize NotificationDataSeeder for status/created-at (Wave 4 prep) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add InsertNotificationAsync with explicit status/createdAt parameters so tests can seed back-dated Retrying rows that satisfy the IsStuck derived property (Status ∈ {Pending,Retrying} && CreatedAt < now − 10 min). Refactor InsertParkedNotificationAsync to delegate to it, preserving its exact public signature and producing identical SQL for existing callers. --- .../Notifications/NotificationDataSeeder.cs | 78 +++++++++++++++---- 1 file changed, 64 insertions(+), 14 deletions(-) diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Notifications/NotificationDataSeeder.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Notifications/NotificationDataSeeder.cs index a5fb0488..c951d558 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Notifications/NotificationDataSeeder.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Notifications/NotificationDataSeeder.cs @@ -38,27 +38,50 @@ internal static class NotificationDataSeeder public static string ConnectionString => PlaywrightDbConnection.ConnectionString; /// - /// Inserts a single Parked row into the central Notifications table. + /// Inserts one notification row with explicit and + /// (for stuck/age edge-case tests). + /// + /// /// Populates every NOT NULL column (NotificationId, Type, ListName, Subject, Body, - /// Status, RetryCount, SourceSiteId, SiteEnqueuedAt, CreatedAt) and stamps the row with - /// the unique so the test can filter to it and the - /// teardown can delete by it. Status is fixed to Parked — the only status - /// from which the page exposes Retry/Discard. Timestamps are pinned to "now" so the - /// page's default unconstrained query window sees the row. + /// Status, RetryCount, SourceSiteId, SiteEnqueuedAt, CreatedAt) and stamps the row + /// with the unique so the test can filter to it and + /// the teardown can delete by it. SiteEnqueuedAt is set equal to + /// . All nullable provenance columns (SourceNode, + /// OriginExecutionId, …) are left to default to NULL, which the page renders as an + /// em-dash. + /// + /// + /// + /// To seed a genuinely stuck row set to Retrying (or + /// Pending) and to more than 10 minutes in the past — + /// IsStuck is derived as Status ∈ {Pending, Retrying} && CreatedAt < + /// now − 10 min. + /// /// /// GUID primary key (stored as its 36-char string form). /// Unique per-run marker stored in ListName. /// Subject text (searchable via the page's subject keyword box). + /// + /// Status value stored as varchar (HasConversion<string>()): e.g. Pending, + /// Retrying, Parked, Delivered, Discarded. + /// + /// + /// Timestamp written to both CreatedAt and SiteEnqueuedAt. Pass a + /// back-dated value (e.g. DateTimeOffset.UtcNow.AddMinutes(-15)) to produce a + /// stuck row. + /// /// Originating site identifier (e.g. site-a). /// Retry count to display on the row. /// Optional last-error text shown beneath the subject. /// Cancellation token. - public static async Task InsertParkedNotificationAsync( + public static async Task InsertNotificationAsync( Guid notificationId, string listNameMarker, string subject, - string sourceSite, - int retryCount = 3, + string status, + DateTimeOffset createdAt, + string sourceSite = "site-a", + int retryCount = 0, string? lastError = "SMTP 451 transient failure (seeded)", CancellationToken ct = default) { @@ -74,8 +97,6 @@ VALUES (@id, @type, @listName, @subject, @body, @status, @retryCount, @lastError, @sourceSite, @siteEnqueuedAt, @createdAt);"; - var now = DateTimeOffset.UtcNow; - await using var connection = new SqlConnection(ConnectionString); await connection.OpenAsync(ct); await using var cmd = connection.CreateCommand(); @@ -85,16 +106,45 @@ VALUES cmd.Parameters.AddWithValue("@listName", listNameMarker); cmd.Parameters.AddWithValue("@subject", subject); cmd.Parameters.AddWithValue("@body", "Seeded notification body for Playwright E2E."); - cmd.Parameters.AddWithValue("@status", "Parked"); + cmd.Parameters.AddWithValue("@status", status); cmd.Parameters.AddWithValue("@retryCount", retryCount); cmd.Parameters.AddWithValue("@lastError", (object?)lastError ?? DBNull.Value); cmd.Parameters.AddWithValue("@sourceSite", sourceSite); - cmd.Parameters.AddWithValue("@siteEnqueuedAt", now); - cmd.Parameters.AddWithValue("@createdAt", now); + cmd.Parameters.AddWithValue("@siteEnqueuedAt", createdAt); + cmd.Parameters.AddWithValue("@createdAt", createdAt); await cmd.ExecuteNonQueryAsync(ct); } + /// + /// Inserts a single Parked row into the central Notifications table. + /// Populates every NOT NULL column (NotificationId, Type, ListName, Subject, Body, + /// Status, RetryCount, SourceSiteId, SiteEnqueuedAt, CreatedAt) and stamps the row with + /// the unique so the test can filter to it and the + /// teardown can delete by it. Status is fixed to Parked — the only status + /// from which the page exposes Retry/Discard. Timestamps are pinned to "now" so the + /// page's default unconstrained query window sees the row. + /// + /// GUID primary key (stored as its 36-char string form). + /// Unique per-run marker stored in ListName. + /// Subject text (searchable via the page's subject keyword box). + /// Originating site identifier (e.g. site-a). + /// Retry count to display on the row. + /// Optional last-error text shown beneath the subject. + /// Cancellation token. + public static Task InsertParkedNotificationAsync( + Guid notificationId, + string listNameMarker, + string subject, + string sourceSite, + int retryCount = 3, + string? lastError = "SMTP 451 transient failure (seeded)", + CancellationToken ct = default) + => InsertNotificationAsync( + notificationId, listNameMarker, subject, + status: "Parked", createdAt: DateTimeOffset.UtcNow, + sourceSite: sourceSite, retryCount: retryCount, lastError: lastError, ct: ct); + /// /// Best-effort cleanup. Deletes every Notifications row whose ListName /// equals . Swallows all errors — the marker carries a