diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/SmtpConfigurationPageTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/SmtpConfigurationPageTests.cs index 2fb67658..8593dc73 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/SmtpConfigurationPageTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/SmtpConfigurationPageTests.cs @@ -37,6 +37,55 @@ public class SmtpConfigurationPageTests : BunitContext Credentials = "user:pass", }; + private static SmtpConfiguration OAuth2Sample() => + new("smtp.example.com", "OAuth2", "noreply@example.com") + { + Id = 1, + Port = 587, + TlsMode = "StartTLS", + Credentials = "client-secret", + }; + + private static SmtpConfiguration EwsSample() => + new("https://mail.example.com/ews/exchange.asmx", "Basic", "noreply@example.com") + { + Id = 2, + Port = 587, + TlsMode = "StartTLS", + Transport = "Ews", + Credentials = @"domain\user:pass", + }; + + private IRenderedComponent RenderWith( + INotificationRepository repo, params SmtpConfiguration[] configs) + { + repo.GetAllSmtpConfigurationsAsync() + .Returns(Task.FromResult>(configs.ToList())); + Services.AddSingleton(repo); + WireAuth(); + + var cut = Render(); + cut.WaitForState(() => cut.Markup.Contains(configs[0].Host)); + return cut; + } + + private static void ClickEdit(IRenderedComponent cut) + => cut.FindAll("button").First(b => b.TextContent.Contains("Edit")).Click(); + + private static void ClickSave(IRenderedComponent cut) + => cut.FindAll("button").First(b => b.TextContent.Contains("Save")).Click(); + + /// Locates a select by an option it offers — the page has no element ids. + private static AngleSharp.Dom.IElement SelectWithOption( + IRenderedComponent cut, string optionText) + => cut.FindAll("select") + .Single(s => s.QuerySelectorAll("option").Any(o => o.TextContent == optionText)); + + private static bool HasSelectWithOption( + IRenderedComponent cut, string optionText) + => cut.FindAll("select") + .Any(s => s.QuerySelectorAll("option").Any(o => o.TextContent == optionText)); + [Fact] public void EditForm_RendersTlsModeSelectWithAllThreeModes() { @@ -110,4 +159,105 @@ public class SmtpConfigurationPageTests : BunitContext repo.Received().SaveChangesAsync(); }); } + + // ======================================================================== + // Transport selector (EWS) — form state machine + // ======================================================================== + + [Fact] + public void EditForm_WithEwsTransport_HidesPortTlsAndAuthTypeAndForcesBasicAuth() + { + var repo = Substitute.For(); + var cut = RenderWith(repo, OAuth2Sample()); + + ClickEdit(cut); + SelectWithOption(cut, "Ews").Change("Ews"); + + // Port input, TLS select and Auth Type select are all gone under EWS. + Assert.Empty(cut.FindAll("input[type=number]")); + Assert.False(HasSelectWithOption(cut, "StartTLS")); + Assert.False(HasSelectWithOption(cut, "OAuth2")); + Assert.Contains("EWS URL", cut.Markup); + + // The forced auth type is only observable through what gets persisted: the row + // loaded as OAuth2 must save as Basic because EWS supports Basic only. + cut.Find("input[type=text]").Change("https://mail.example.com/ews/exchange.asmx"); + ClickSave(cut); + + cut.WaitForAssertion(() => + repo.Received().UpdateSmtpConfigurationAsync(Arg.Is( + c => c.Transport == "Ews" + && c.AuthType == "Basic" + && c.Host == "https://mail.example.com/ews/exchange.asmx"))); + } + + [Fact] + public void EditForm_SwitchingEwsBackToSmtp_RestoresAuthTypeAndReshowsPortAndTls() + { + var repo = Substitute.For(); + var cut = RenderWith(repo, OAuth2Sample()); + + ClickEdit(cut); + SelectWithOption(cut, "Ews").Change("Ews"); + SelectWithOption(cut, "Ews").Change("Smtp"); + + // Port and TLS controls are back... + Assert.Single(cut.FindAll("input[type=number]")); + Assert.True(HasSelectWithOption(cut, "StartTLS")); + Assert.True(HasSelectWithOption(cut, "OAuth2")); + + // ...and the round trip left the pre-switch values (OAuth2 / 587 / StartTLS) intact. + ClickSave(cut); + + cut.WaitForAssertion(() => + repo.Received().UpdateSmtpConfigurationAsync(Arg.Is( + c => c.Transport == "Smtp" + && c.AuthType == "OAuth2" + && c.Port == 587 + && c.TlsMode == "StartTLS"))); + } + + [Fact] + public void Save_WithEwsTransportAndNonHttpsHost_ShowsFormErrorAndDoesNotPersist() + { + var repo = Substitute.For(); + var cut = RenderWith(repo, OAuth2Sample()); + + ClickEdit(cut); + SelectWithOption(cut, "Ews").Change("Ews"); + cut.Find("input[type=text]").Change("http://mail.example.com/ews/exchange.asmx"); + ClickSave(cut); + + cut.WaitForAssertion(() => Assert.Contains("absolute https://", cut.Markup)); + repo.DidNotReceive().UpdateSmtpConfigurationAsync(Arg.Any()); + repo.DidNotReceive().SaveChangesAsync(); + } + + [Fact] + public void ReadOnlyView_ShowsTransportBadgeAndHidesPortAndTlsForEwsRows() + { + var ewsCut = RenderWith(Substitute.For(), EwsSample()); + + ewsCut.WaitForAssertion(() => + { + Assert.Contains("Ews", ewsCut.Markup); + Assert.Contains("EWS URL", ewsCut.Markup); + Assert.DoesNotContain("TLS Mode", ewsCut.Markup); + // The EWS URL is rendered bare — no ":port" suffix as the SMTP host row has. + Assert.DoesNotContain("exchange.asmx:587", ewsCut.Markup); + }); + } + + [Fact] + public void ReadOnlyView_LegacyRowWithNullTransport_ShowsSmtpBadgeAndKeepsPortAndTls() + { + var cut = RenderWith(Substitute.For(), Sample()); + + cut.WaitForAssertion(() => + { + Assert.Contains("Smtp", cut.Markup); + Assert.Contains("smtp.example.com:587", cut.Markup); + Assert.Contains("TLS Mode", cut.Markup); + }); + } }