177 lines
6.5 KiB
C#
177 lines
6.5 KiB
C#
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();
|
|
});
|
|
}
|
|
}
|