feat(ui): tri-state MES reportable override on Instance Configure (reportable-only override is a delta)

Claude-Session: https://claude.ai/code/session_01Y5ef34iUUMjmwnMpL9xgmB
This commit is contained in:
Joseph Doherty
2026-09-11 10:05:05 -04:00
parent 63ec226582
commit 735534a455
3 changed files with 196 additions and 3 deletions
@@ -516,4 +516,70 @@ public sealed class InstanceConfigureTests : IClassFixture<InstanceConfigureFixt
await CliRunner.DeleteInstanceAlarmOverrideAsync(_cfg.InstanceId, _cfg.AlarmName);
}
}
/// <summary>
/// Tri-state MES reportable round-trip. The override editor's third control is a select whose
/// blank option means "inherit the template alarm's <c>IsMesReportable</c>"; "Reportable" /
/// "Not reportable" pin it per-instance.
///
/// <para>
/// This is the regression guard for the save-time delta rule: a Save with no config-diff and no
/// priority DELETES the override, so a reportable-only choice has to count as a delta on its own
/// or it would be silently dropped. The assertion is therefore that choosing ONLY "Reportable"
/// (leaving the trigger config and priority untouched) creates the override — badge + a summary
/// naming <c>mesReportable</c> — and that switching back to Inherited removes it again.
/// </para>
///
/// <para>
/// The fixture instance is SHARED, so this clears its own override in-body and again in a
/// <c>finally</c>, exactly as <see cref="AlarmOverride_SetPriority_ThenClear_RoundTrips"/> does.
/// </para>
/// </summary>
[SkippableFact]
public async Task AlarmOverride_MesReportable_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);
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 → choose "Reportable" ONLY (no config edit, no priority) → Save.
await row.Locator("[data-test='alarm-edit-btn']").ClickAsync();
var mesSelect = page.Locator("[data-test='alarm-mes-select']");
await Assertions.Expect(mesSelect).ToBeVisibleAsync(new() { Timeout = 15_000 });
await mesSelect.SelectOptionAsync("true");
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 });
// Summary part is "mesReportable" (the negative pin renders "notMesReportable").
await Assertions.Expect(row).ToContainTextAsync("mesReportable", new() { Timeout = 15_000 });
// Reopen → back to Inherited (blank) → Save. With nothing else set that is now a
// no-delta save, which clears the override outright.
await row.Locator("[data-test='alarm-edit-btn']").ClickAsync();
await Assertions.Expect(mesSelect).ToBeVisibleAsync(new() { Timeout = 15_000 });
await mesSelect.SelectOptionAsync("");
await page.Locator("[data-test='alarm-save-override']").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
{
await CliRunner.DeleteInstanceAlarmOverrideAsync(_cfg.InstanceId, _cfg.AlarmName);
}
}
}