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