diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/InstanceConfigureTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/InstanceConfigureTests.cs index 8dd8faea..92ee8ad7 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/InstanceConfigureTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/InstanceConfigureTests.cs @@ -166,5 +166,82 @@ public sealed class InstanceConfigureTests : IClassFixture + /// Alarm-override round-trip on the InstanceConfigure page's Alarm Overrides card: Edit the + /// fixture's non-locked HiLo alarm (_cfg.AlarmName = "HiHi"), set a priority override, Save → + /// one toast + an "overridden" badge, then verify the InstanceAlarmOverride actually persisted + /// via a CLI instance get read-back (not just the toast/badge), then Clear → badge gone + + /// override removed (re-verified via read-back). + /// + /// + /// Read-back path: the instance get document surfaces an alarmOverrides array whose + /// elements are { id, instanceId, alarmCanonicalName, priorityLevelOverride } (camelCase, + /// empirically verified against the dev cluster — same instance-document path the + /// test uses for attributeOverrides). For a direct + /// (non-composed) alarm, alarmCanonicalName equals the alarm name. + /// + /// + /// + /// Setting only the PRIORITY field is the reliable "create override" delta — a Save with no + /// config-diff AND empty priority deletes the override instead. The fixture instance is SHARED, so + /// the test clears its own override in-body (badge gone + read-back empty) and again in a + /// finally, leaving the instance override-free as found. + /// + /// + [SkippableFact] + public async Task AlarmOverride_SetPriority_ThenClear_RoundTrips() + { + Skip.IfNot(_cfg.Available, ClusterAvailability.SkipReason); + + var page = await _fixture.NewAuthenticatedPageAsync("multi-role", "password"); + try + { + await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/deployment/instances/{_cfg.InstanceId}/configure"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + // The Alarm Overrides card renders one row per non-locked template alarm; web-first wait + // for the fixture alarm's row so we never race the post-load re-render. No override yet. + var row = page.Locator($"[data-test='alarm-override-row-{_cfg.AlarmName}']"); + await Assertions.Expect(row).ToBeVisibleAsync(new() { Timeout = 15_000 }); + await Assertions.Expect(row.Locator("[data-test='alarm-override-badge']")).ToHaveCountAsync(0); + + // Edit → set a priority override → Save. FillAsync fires the input event, so the priority + // input's @bind:event="oninput" commits before the Save click (no extra change dispatch). + await row.Locator("[data-test='alarm-edit-btn']").ClickAsync(); + var priorityInput = page.Locator("[data-test='alarm-priority-input']"); + await Assertions.Expect(priorityInput).ToBeVisibleAsync(new() { Timeout = 15_000 }); + await priorityInput.FillAsync("750"); + await page.Locator("[data-test='alarm-save-override']").ClickAsync(); + + await Assertions.Expect(page.Locator(".toast")).ToHaveCountAsync(1, new() { Timeout = 15_000 }); + await Assertions.Expect(row.Locator("[data-test='alarm-override-badge']")) + .ToBeVisibleAsync(new() { Timeout = 15_000 }); + + // Verify the InstanceAlarmOverride persisted via CLI read-back (not just the toast/badge). + using (var doc = await CliRunner.GetInstanceDocumentAsync(_cfg.InstanceId)) + { + var overrides = doc.RootElement.GetProperty("alarmOverrides"); + Assert.Contains(overrides.EnumerateArray(), o => + o.GetProperty("alarmCanonicalName").GetString() == _cfg.AlarmName + && o.GetProperty("priorityLevelOverride").GetInt32() == 750); + } + + // Clear is immediate (no confirm): the badge disappears and the override is removed. + await row.Locator("[data-test='alarm-clear-btn']").ClickAsync(); + await Assertions.Expect(row.Locator("[data-test='alarm-override-badge']")) + .ToHaveCountAsync(0, new() { Timeout = 15_000 }); + using (var doc = await CliRunner.GetInstanceDocumentAsync(_cfg.InstanceId)) + { + var overrides = doc.RootElement.GetProperty("alarmOverrides"); + Assert.DoesNotContain(overrides.EnumerateArray(), o => + o.GetProperty("alarmCanonicalName").GetString() == _cfg.AlarmName); + } + } + finally + { + // Belt-and-braces: leave the shared fixture instance override-free even if an assertion + // above threw after the Save (best-effort; never masks the test's own failure). + await CliRunner.DeleteInstanceAlarmOverrideAsync(_cfg.InstanceId, _cfg.AlarmName); + } + } }