feat(sms): SMS configuration Central UI page + nav (S9)
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
using System.Security.Claims;
|
||||
using ZB.MOM.WW.ScadaBridge.Security;
|
||||
using Bunit;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NSubstitute;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
||||
using SmsConfigurationPage = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Notifications.SmsConfiguration;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Pages;
|
||||
|
||||
/// <summary>
|
||||
/// bUnit rendering tests for the SMS Configuration page — verifies the read-only list,
|
||||
/// that the stored Auth Token value is never rendered (only a presence indicator),
|
||||
/// repository-direct save on create/edit, and the preserve-if-blank secret handling.
|
||||
/// </summary>
|
||||
public class SmsConfigurationPageTests : BunitContext
|
||||
{
|
||||
private void WireAuth()
|
||||
{
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(JwtTokenService.UsernameClaimType, "tester"),
|
||||
new Claim(JwtTokenService.RoleClaimType, "Administrator"),
|
||||
};
|
||||
var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth"));
|
||||
Services.AddSingleton<AuthenticationStateProvider>(new TestAuthStateProvider(user));
|
||||
Services.AddAuthorizationCore();
|
||||
}
|
||||
|
||||
private const string SecretToken = "super-secret-auth-token";
|
||||
|
||||
private static SmsConfiguration Sample() =>
|
||||
new("ACtest_account_sid", "+15551234567")
|
||||
{
|
||||
Id = 1,
|
||||
MessagingServiceSid = "MGtest_messaging_service",
|
||||
ApiBaseUrl = "https://api.example.com",
|
||||
AuthToken = SecretToken,
|
||||
ConnectionTimeoutSeconds = 30,
|
||||
MaxRetries = 10,
|
||||
RetryDelay = TimeSpan.FromMinutes(1),
|
||||
};
|
||||
|
||||
private static INotificationRepository RepoWith(params SmsConfiguration[] configs)
|
||||
{
|
||||
var repo = Substitute.For<INotificationRepository>();
|
||||
repo.GetAllSmsConfigurationsAsync()
|
||||
.Returns(Task.FromResult<IReadOnlyList<SmsConfiguration>>(configs.ToList()));
|
||||
return repo;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadOnlyView_ShowsConfigRow_ButNeverRendersAuthTokenValue()
|
||||
{
|
||||
var repo = RepoWith(Sample());
|
||||
Services.AddSingleton(repo);
|
||||
WireAuth();
|
||||
|
||||
var cut = Render<SmsConfigurationPage>();
|
||||
|
||||
cut.WaitForAssertion(() =>
|
||||
{
|
||||
// Config row fields render.
|
||||
Assert.Contains("ACtest_account_sid", cut.Markup);
|
||||
Assert.Contains("+15551234567", cut.Markup);
|
||||
Assert.Contains("MGtest_messaging_service", cut.Markup);
|
||||
// Auth Token shows a presence indicator only — never the value.
|
||||
Assert.Contains("Auth Token", cut.Markup);
|
||||
Assert.Contains("(stored)", cut.Markup);
|
||||
Assert.DoesNotContain(SecretToken, cut.Markup);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EditForm_DoesNotPrefillAuthToken_ButPrefillsMessagingServiceSid()
|
||||
{
|
||||
var repo = RepoWith(Sample());
|
||||
Services.AddSingleton(repo);
|
||||
WireAuth();
|
||||
|
||||
var cut = Render<SmsConfigurationPage>();
|
||||
cut.WaitForState(() => cut.Markup.Contains("ACtest_account_sid"));
|
||||
|
||||
cut.FindAll("button").First(b => b.TextContent.Contains("Edit")).Click();
|
||||
|
||||
cut.WaitForAssertion(() =>
|
||||
{
|
||||
// The secret is never placed into the form markup (no input pre-fill).
|
||||
Assert.DoesNotContain(SecretToken, cut.Markup);
|
||||
// Non-secret fields, including MessagingServiceSid, are pre-filled.
|
||||
var inputs = cut.FindAll("input");
|
||||
Assert.Contains(inputs, i => i.GetAttribute("value") == "MGtest_messaging_service");
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SavingNewConfig_CallsAddAndSaveChanges()
|
||||
{
|
||||
var repo = RepoWith();
|
||||
Services.AddSingleton(repo);
|
||||
WireAuth();
|
||||
|
||||
var cut = Render<SmsConfigurationPage>();
|
||||
cut.WaitForState(() => cut.Markup.Contains("No SMS configuration set."));
|
||||
|
||||
cut.FindAll("button").First(b => b.TextContent.Contains("Add SMS configuration")).Click();
|
||||
|
||||
// Re-query between each Change(): two-way binding re-renders the form and
|
||||
// invalidates previously found element references.
|
||||
cut.FindAll("input[type=text]")[0].Change("ACnew_account"); // Account SID
|
||||
cut.FindAll("input[type=text]")[1].Change("+15559876543"); // From Number
|
||||
cut.FindAll("input[type=password]").First().Change("new-token");
|
||||
|
||||
cut.FindAll("button").First(b => b.TextContent.Contains("Save")).Click();
|
||||
|
||||
cut.WaitForAssertion(() =>
|
||||
{
|
||||
repo.Received().AddSmsConfigurationAsync(
|
||||
Arg.Is<SmsConfiguration>(c =>
|
||||
c.AccountSid == "ACnew_account" &&
|
||||
c.FromNumber == "+15559876543" &&
|
||||
c.AuthToken == "new-token"));
|
||||
repo.Received().SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SavingEdit_WithBlankAuthToken_PreservesExistingToken()
|
||||
{
|
||||
var config = Sample();
|
||||
var repo = RepoWith(config);
|
||||
Services.AddSingleton(repo);
|
||||
WireAuth();
|
||||
|
||||
var cut = Render<SmsConfigurationPage>();
|
||||
cut.WaitForState(() => cut.Markup.Contains("ACtest_account_sid"));
|
||||
|
||||
cut.FindAll("button").First(b => b.TextContent.Contains("Edit")).Click();
|
||||
// Leave the (blank) Auth Token input untouched, then save.
|
||||
cut.FindAll("button").First(b => b.TextContent.Contains("Save")).Click();
|
||||
|
||||
cut.WaitForAssertion(() =>
|
||||
{
|
||||
repo.Received().UpdateSmsConfigurationAsync(
|
||||
Arg.Is<SmsConfiguration>(c =>
|
||||
c.AuthToken == SecretToken &&
|
||||
c.MessagingServiceSid == "MGtest_messaging_service"));
|
||||
repo.Received().SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SavingEdit_WithNewAuthToken_OverwritesToken()
|
||||
{
|
||||
var config = Sample();
|
||||
var repo = RepoWith(config);
|
||||
Services.AddSingleton(repo);
|
||||
WireAuth();
|
||||
|
||||
var cut = Render<SmsConfigurationPage>();
|
||||
cut.WaitForState(() => cut.Markup.Contains("ACtest_account_sid"));
|
||||
|
||||
cut.FindAll("button").First(b => b.TextContent.Contains("Edit")).Click();
|
||||
cut.FindAll("input[type=password]").First().Change("rotated-token");
|
||||
cut.FindAll("button").First(b => b.TextContent.Contains("Save")).Click();
|
||||
|
||||
cut.WaitForAssertion(() =>
|
||||
{
|
||||
repo.Received().UpdateSmsConfigurationAsync(
|
||||
Arg.Is<SmsConfiguration>(c => c.AuthToken == "rotated-token"));
|
||||
repo.Received().SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user