205 lines
8.0 KiB
C#
205 lines
8.0 KiB
C#
using System.Security.Claims;
|
|
using Bunit;
|
|
using Microsoft.AspNetCore.Components.Authorization;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using NSubstitute;
|
|
using ZB.MOM.WW.ScadaBridge.CentralUI.Services;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
|
using ZB.MOM.WW.ScadaBridge.Security;
|
|
using NotificationListForm = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Notifications.NotificationListForm;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Pages;
|
|
|
|
/// <summary>
|
|
/// bUnit tests for the adapter-gated Type selector + per-type recipient input on the
|
|
/// Notification List create/edit form (SMS Notifications, S7).
|
|
/// </summary>
|
|
public class NotificationListFormTests : BunitContext
|
|
{
|
|
private readonly INotificationRepository _repo = Substitute.For<INotificationRepository>();
|
|
|
|
public NotificationListFormTests()
|
|
{
|
|
Services.AddSingleton(_repo);
|
|
// The Type selector derives its options from INotificationChannelCatalog
|
|
// (S7). Register a substitute exposing both channels so the selector renders
|
|
// Email + SMS without pulling the real adapter graph into the test.
|
|
var catalog = Substitute.For<INotificationChannelCatalog>();
|
|
catalog.SupportedChannels.Returns(
|
|
new[] { NotificationType.Email, NotificationType.Sms });
|
|
Services.AddSingleton(catalog);
|
|
AddTestAuth();
|
|
}
|
|
|
|
private void AddTestAuth()
|
|
{
|
|
var claims = new[]
|
|
{
|
|
new Claim(JwtTokenService.UsernameClaimType, "tester"),
|
|
new Claim(JwtTokenService.RoleClaimType, "Designer"),
|
|
};
|
|
var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth"));
|
|
Services.AddSingleton<AuthenticationStateProvider>(new TestAuthStateProvider(user));
|
|
Services.AddAuthorizationCore();
|
|
}
|
|
|
|
[Fact]
|
|
public void TypeSelector_RendersRegisteredChannels()
|
|
{
|
|
var cut = Render<NotificationListForm>();
|
|
|
|
cut.WaitForAssertion(() =>
|
|
{
|
|
var optionTexts = cut.FindAll("select.form-select option")
|
|
.Select(o => o.TextContent.Trim())
|
|
.ToList();
|
|
Assert.Contains("Email", optionTexts);
|
|
Assert.Contains("SMS", optionTexts);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void TypeSelector_IsEnabled_OnCreate()
|
|
{
|
|
var cut = Render<NotificationListForm>();
|
|
|
|
cut.WaitForAssertion(() =>
|
|
{
|
|
var select = cut.Find("select.form-select");
|
|
Assert.False(select.HasAttribute("disabled"));
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void SelectingEmail_ShowsEmailInput_NotPhone()
|
|
{
|
|
// Edit mode renders the recipient form (recipients only show once a list exists).
|
|
_repo.GetNotificationListByIdAsync(1)
|
|
.Returns(Task.FromResult<NotificationList?>(new NotificationList("Ops") { Id = 1, Type = NotificationType.Email }));
|
|
_repo.GetRecipientsByListIdAsync(1)
|
|
.Returns(Task.FromResult<IReadOnlyList<NotificationRecipient>>(new List<NotificationRecipient>()));
|
|
|
|
var cut = Render<NotificationListForm>(p => p.Add(c => c.Id, 1));
|
|
|
|
cut.WaitForAssertion(() =>
|
|
{
|
|
Assert.NotNull(cut.Find("input[type=email]"));
|
|
Assert.Empty(cut.FindAll("input[type=tel]"));
|
|
// The recipients table column header reflects the list type.
|
|
Assert.Contains("Email", cut.FindAll("th").Select(t => t.TextContent.Trim()));
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void SelectingSms_ShowsPhoneInput_NotEmail()
|
|
{
|
|
_repo.GetNotificationListByIdAsync(2)
|
|
.Returns(Task.FromResult<NotificationList?>(new NotificationList("On-Call") { Id = 2, Type = NotificationType.Sms }));
|
|
_repo.GetRecipientsByListIdAsync(2)
|
|
.Returns(Task.FromResult<IReadOnlyList<NotificationRecipient>>(new List<NotificationRecipient>()));
|
|
|
|
var cut = Render<NotificationListForm>(p => p.Add(c => c.Id, 2));
|
|
|
|
cut.WaitForAssertion(() =>
|
|
{
|
|
Assert.NotNull(cut.Find("input[type=tel]"));
|
|
Assert.Empty(cut.FindAll("input[type=email]"));
|
|
Assert.Contains("Phone", cut.FindAll("th").Select(t => t.TextContent.Trim()));
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void TypeSelector_IsDisabled_OnEdit()
|
|
{
|
|
_repo.GetNotificationListByIdAsync(3)
|
|
.Returns(Task.FromResult<NotificationList?>(new NotificationList("Ops") { Id = 3, Type = NotificationType.Email }));
|
|
_repo.GetRecipientsByListIdAsync(3)
|
|
.Returns(Task.FromResult<IReadOnlyList<NotificationRecipient>>(new List<NotificationRecipient>()));
|
|
|
|
var cut = Render<NotificationListForm>(p => p.Add(c => c.Id, 3));
|
|
|
|
cut.WaitForAssertion(() =>
|
|
{
|
|
var select = cut.Find("select.form-select");
|
|
Assert.True(select.HasAttribute("disabled"));
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void CreatingSmsList_PersistsTypeSms()
|
|
{
|
|
NotificationList? captured = null;
|
|
_repo.AddNotificationListAsync(Arg.Do<NotificationList>(l => captured = l))
|
|
.Returns(Task.CompletedTask);
|
|
|
|
var cut = Render<NotificationListForm>();
|
|
|
|
cut.Find("input[type=text]").Change("On-Call");
|
|
// Switch the Type selector to SMS, then save.
|
|
cut.Find("select.form-select").Change(NotificationType.Sms.ToString());
|
|
cut.FindAll("button").First(b => b.TextContent.Trim() == "Save").Click();
|
|
|
|
cut.WaitForAssertion(() =>
|
|
{
|
|
Assert.NotNull(captured);
|
|
Assert.Equal(NotificationType.Sms, captured!.Type);
|
|
Assert.Equal("On-Call", captured.Name);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void AddingSmsRecipient_CreatesForSmsRecipient_WithPhoneSet()
|
|
{
|
|
NotificationRecipient? captured = null;
|
|
_repo.GetNotificationListByIdAsync(4)
|
|
.Returns(Task.FromResult<NotificationList?>(new NotificationList("On-Call") { Id = 4, Type = NotificationType.Sms }));
|
|
_repo.GetRecipientsByListIdAsync(4)
|
|
.Returns(Task.FromResult<IReadOnlyList<NotificationRecipient>>(new List<NotificationRecipient>()));
|
|
_repo.AddRecipientAsync(Arg.Do<NotificationRecipient>(r => captured = r))
|
|
.Returns(Task.CompletedTask);
|
|
|
|
var cut = Render<NotificationListForm>(p => p.Add(c => c.Id, 4));
|
|
|
|
cut.WaitForState(() => cut.FindAll("input[type=tel]").Count > 0);
|
|
|
|
// The first text input is the list Name; the recipient Name input is the last.
|
|
cut.FindAll("input[type=text]").Last().Change("Jane");
|
|
cut.Find("input[type=tel]").Change("+15551234567");
|
|
cut.FindAll("button").First(b => b.TextContent.Trim() == "Add").Click();
|
|
|
|
cut.WaitForAssertion(() =>
|
|
{
|
|
Assert.NotNull(captured);
|
|
Assert.Equal("+15551234567", captured!.PhoneNumber);
|
|
Assert.Null(captured.EmailAddress);
|
|
Assert.Equal(4, captured.NotificationListId);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void AddingSmsRecipient_RejectsInvalidPhone()
|
|
{
|
|
_repo.GetNotificationListByIdAsync(5)
|
|
.Returns(Task.FromResult<NotificationList?>(new NotificationList("On-Call") { Id = 5, Type = NotificationType.Sms }));
|
|
_repo.GetRecipientsByListIdAsync(5)
|
|
.Returns(Task.FromResult<IReadOnlyList<NotificationRecipient>>(new List<NotificationRecipient>()));
|
|
|
|
var cut = Render<NotificationListForm>(p => p.Add(c => c.Id, 5));
|
|
|
|
cut.WaitForState(() => cut.FindAll("input[type=tel]").Count > 0);
|
|
|
|
// The first text input is the list Name; the recipient Name input is the last.
|
|
cut.FindAll("input[type=text]").Last().Change("Jane");
|
|
cut.Find("input[type=tel]").Change("not-a-number");
|
|
cut.FindAll("button").First(b => b.TextContent.Trim() == "Add").Click();
|
|
|
|
cut.WaitForAssertion(() =>
|
|
{
|
|
Assert.Contains("valid phone number", cut.Markup);
|
|
_repo.DidNotReceive().AddRecipientAsync(Arg.Any<NotificationRecipient>());
|
|
});
|
|
}
|
|
}
|