diff --git a/src/ScadaLink.CentralUI/Components/Layout/NavMenu.razor b/src/ScadaLink.CentralUI/Components/Layout/NavMenu.razor
index 96652e6..78fb4f2 100644
--- a/src/ScadaLink.CentralUI/Components/Layout/NavMenu.razor
+++ b/src/ScadaLink.CentralUI/Components/Layout/NavMenu.razor
@@ -24,9 +24,6 @@
API Keys
-
- SMTP Configuration
-
@@ -65,6 +62,35 @@
+ @* Notifications — mixed-role section; each item gated by its own policy.
+ The header is ungated: every authenticated user holds at least one of
+ Admin/Design/Deployment, so it always has a visible child. *@
+
+
+
+
+ SMTP Configuration
+
+
+
+
+
+
+ Notification Lists
+
+
+
+
+
+
+ Notification Report
+
+
+ Notification KPIs
+
+
+
+
@* Monitoring — Health Dashboard is all-roles; Event Logs and
Parked Messages are Deployment-role only (Component-CentralUI). *@
@@ -79,9 +105,6 @@
Parked Messages
-
- Notification Outbox
-
diff --git a/tests/ScadaLink.CentralUI.Tests/Layout/NavMenuTests.cs b/tests/ScadaLink.CentralUI.Tests/Layout/NavMenuTests.cs
new file mode 100644
index 0000000..c416456
--- /dev/null
+++ b/tests/ScadaLink.CentralUI.Tests/Layout/NavMenuTests.cs
@@ -0,0 +1,95 @@
+using System.Security.Claims;
+using Bunit;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Authorization;
+using Microsoft.AspNetCore.Components.Rendering;
+using Microsoft.Extensions.DependencyInjection;
+using ScadaLink.Security;
+using NavMenu = ScadaLink.CentralUI.Components.Layout.NavMenu;
+
+namespace ScadaLink.CentralUI.Tests.Layout;
+
+///
+/// bUnit rendering tests for the sidebar . They verify the
+/// new Notifications section: its items are gated per-policy, and the old
+/// /admin/smtp and /monitoring/notification-outbox routes are gone.
+/// The AuthorizeView Policy=... blocks evaluate the real policies, which
+/// require a claim of type ("Role"),
+/// so the test principal carries claims of that exact type.
+///
+public class NavMenuTests : BunitContext
+{
+ ///
+ /// Renders under a principal holding the given roles.
+ /// 's top-level AuthorizeView requires the
+ /// cascading , so it is rendered inside a
+ /// ; the real policies are
+ /// registered so the per-item AuthorizeView Policy=... blocks are
+ /// genuinely evaluated.
+ ///
+ private IRenderedComponent RenderWithRoles(params string[] roles)
+ {
+ var claims = new List { new("Username", "tester") };
+ claims.AddRange(roles.Select(r => new Claim(JwtTokenService.RoleClaimType, r)));
+
+ var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth"));
+ Services.AddSingleton(new TestAuthStateProvider(user));
+ Services.AddAuthorizationCore();
+ AuthorizationPolicies.AddScadaLinkAuthorization(Services);
+ // BunitContext pre-registers a placeholder IAuthorizationService that
+ // throws when AuthorizeView evaluates a policy. Force the real service
+ // so the per-item policy gating is genuinely exercised.
+ Services.AddSingleton();
+
+ var host = Render(parameters => parameters
+ .Add(p => p.ChildContent, (RenderFragment)(builder =>
+ {
+ builder.OpenComponent(0);
+ builder.CloseComponent();
+ })));
+
+ return host.FindComponent();
+ }
+
+ [Fact]
+ public void NotificationsSection_ShowsAllItems_ForMultiRoleUser()
+ {
+ var cut = RenderWithRoles("Admin", "Design", "Deployment");
+
+ cut.WaitForAssertion(() =>
+ {
+ Assert.Contains("Notifications", cut.Markup);
+ Assert.Contains("/notifications/smtp", cut.Markup);
+ Assert.Contains("/notifications/lists", cut.Markup);
+ Assert.Contains("/notifications/report", cut.Markup);
+ Assert.Contains("/notifications/kpis", cut.Markup);
+ });
+ }
+
+ [Fact]
+ public void NotificationsSection_AdminOnlyUser_SeesOnlySmtp()
+ {
+ var cut = RenderWithRoles("Admin");
+
+ cut.WaitForAssertion(() =>
+ {
+ Assert.Contains("/notifications/smtp", cut.Markup);
+ Assert.DoesNotContain("/notifications/report", cut.Markup);
+ Assert.DoesNotContain("/notifications/lists", cut.Markup);
+ Assert.DoesNotContain("/notifications/kpis", cut.Markup);
+ });
+ }
+
+ [Fact]
+ public void OldRoutes_AreNoLongerLinked()
+ {
+ var cut = RenderWithRoles("Admin", "Design", "Deployment");
+
+ cut.WaitForAssertion(() =>
+ {
+ Assert.DoesNotContain("/admin/smtp", cut.Markup);
+ Assert.DoesNotContain("/monitoring/notification-outbox", cut.Markup);
+ });
+ }
+}