diff --git a/src/ScadaLink.CentralUI/Components/Pages/Notifications/NotificationReport.razor b/src/ScadaLink.CentralUI/Components/Pages/Notifications/NotificationReport.razor
index 1a66d3d..b083824 100644
--- a/src/ScadaLink.CentralUI/Components/Pages/Notifications/NotificationReport.razor
+++ b/src/ScadaLink.CentralUI/Components/Pages/Notifications/NotificationReport.razor
@@ -163,6 +163,14 @@
+ @* Bundle D (#23 M7-T10) drill-in: NotificationId is the audit
+ CorrelationId, so the link deep-links into the central Audit
+ Log pre-filtered to this notification's lifecycle events. *@
+
+ View audit history
+
@if (n.Status == "Parked")
{
}
- else
- {
- —
- }
}
diff --git a/tests/ScadaLink.CentralUI.Tests/Pages/NotificationReportPageTests.cs b/tests/ScadaLink.CentralUI.Tests/Pages/NotificationReportPageTests.cs
index 81cef44..239d9f1 100644
--- a/tests/ScadaLink.CentralUI.Tests/Pages/NotificationReportPageTests.cs
+++ b/tests/ScadaLink.CentralUI.Tests/Pages/NotificationReportPageTests.cs
@@ -177,6 +177,62 @@ public class NotificationReportPageTests : BunitContext
Assert.Contains("outbox query backend unavailable", cut.Markup));
}
+ // ─────────────────────────────────────────────────────────────────────────
+ // Bundle D drill-in (#23 M7-T10) — every notification row carries a
+ // "View audit history" link to /audit/log?correlationId={NotificationId}.
+ // ─────────────────────────────────────────────────────────────────────────
+
+ [Fact]
+ public void NotificationRow_ViewAuditHistory_Link_HasCorrectHref()
+ {
+ var cut = Render();
+
+ cut.WaitForAssertion(() =>
+ {
+ // Both rows (Parked + Delivered) must surface the link — the drill-in
+ // is row-scope, not status-scope. We pin the parked row's href to the
+ // canonical correlationId-deep-link shape.
+ var parkedRow = cut.FindAll("tbody tr")
+ .First(r => r.TextContent.Contains("Pump fault at Plant-A"));
+ var link = parkedRow.QuerySelector("a[data-test^=\"audit-link-\"]");
+ Assert.NotNull(link);
+ Assert.Equal(
+ "/audit/log?correlationId=notif-aaaaaaaa-1111",
+ link!.GetAttribute("href"));
+ Assert.Contains("View audit history", link.TextContent);
+
+ var deliveredRow = cut.FindAll("tbody tr")
+ .First(r => r.TextContent.Contains("Daily summary"));
+ var deliveredLink = deliveredRow.QuerySelector("a[data-test^=\"audit-link-\"]");
+ Assert.NotNull(deliveredLink);
+ Assert.Equal(
+ "/audit/log?correlationId=notif-bbbbbbbb-2222",
+ deliveredLink!.GetAttribute("href"));
+ });
+ }
+
+ [Fact]
+ public void Click_NavigatesTo_AuditLog_WithCorrelationId()
+ {
+ // The drill-in is a plain — browser-native navigation, not a
+ // Blazor onclick handler — so this test verifies the rendered anchor's
+ // attributes are exactly what a browser would follow: href, role, and
+ // human-visible text. (Triggering bUnit's .Click() on a bare anchor
+ // raises MissingEventHandlerException because there is no onclick
+ // handler to invoke; the navigation contract lives in the markup.)
+ var cut = Render();
+
+ cut.WaitForState(() => cut.Markup.Contains("Pump fault at Plant-A"));
+
+ var parkedRow = cut.FindAll("tbody tr")
+ .First(r => r.TextContent.Contains("Pump fault at Plant-A"));
+ var link = parkedRow.QuerySelector("a[data-test^=\"audit-link-\"]")!;
+
+ Assert.Equal("a", link.TagName, ignoreCase: true);
+ Assert.Equal("/audit/log?correlationId=notif-aaaaaaaa-1111", link.GetAttribute("href"));
+ Assert.Contains("View audit history", link.TextContent);
+ }
+
protected override void Dispose(bool disposing)
{
if (disposing)