using Microsoft.Extensions.DependencyInjection; using NSubstitute; using ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services; using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management; using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; using ZB.MOM.WW.ScadaBridge.Security; namespace ZB.MOM.WW.ScadaBridge.ManagementService.Tests; public class NotificationRecipientPreservationTests { [Theory] [InlineData(NotificationType.Email)] [InlineData(NotificationType.Sms)] public async Task DedicatedEdits_PreserveUnrelatedRecipientIdentityAndName(NotificationType channel) { var list = new NotificationList("Original") { Id = 7, Type = channel }; var original = channel == NotificationType.Email ? NotificationRecipient.ForEmail("Existing Operator", "original@example.com") : NotificationRecipient.ForSms("Existing Operator", "+15551234567"); original.Id = 41; original.NotificationListId = list.Id; list.Recipients.Add(original); var originalEmail = original.EmailAddress; var originalPhone = original.PhoneNumber; var repo = Substitute.For(); repo.GetNotificationListByIdAsync(7, Arg.Any()).Returns(list); repo.GetRecipientsByListIdAsync(7, Arg.Any()) .Returns(_ => list.Recipients.ToList()); repo.GetRecipientByIdAsync(Arg.Any(), Arg.Any()) .Returns(call => list.Recipients.SingleOrDefault(r => r.Id == call.Arg())); // Model repository inserts/deletes in one persisted collection. Any accidental // whole-list replacement would remove or renumber the original row and fail below. repo.When(r => r.AddRecipientAsync(Arg.Any(), Arg.Any())) .Do(call => { var row = call.Arg(); row.Id = 42; list.Recipients.Add(row); }); repo.When(r => r.DeleteRecipientAsync(Arg.Any(), Arg.Any())) .Do(call => list.Recipients.Remove(list.Recipients.Single(r => r.Id == call.Arg()))); using var services = new ServiceCollection().AddTestConfigurationTransaction() .AddSingleton(repo).AddSingleton(Substitute.For()).BuildServiceProvider(); var user = new AuthenticatedUser("designer", "designer", new[] { Roles.Designer }, Array.Empty()); await ManagementActor.ExecuteConfigurationCommandAsync(services, new RenameNotificationListCommand(7, "Renamed"), user); Assert.Equal("Renamed", list.Name); AssertOriginalUnchanged(); Assert.Single(list.Recipients); await repo.DidNotReceiveWithAnyArgs().AddRecipientAsync(default!, default); await repo.DidNotReceiveWithAnyArgs().DeleteRecipientAsync(default, default); await ManagementActor.ExecuteConfigurationCommandAsync(services, new AddNotificationRecipientCommand(7, "New Operator", channel == NotificationType.Email ? "new@example.com" : null, channel == NotificationType.Sms ? "+15557654321" : null), user); AssertOriginalUnchanged(); Assert.Equal(2, list.Recipients.Count); var added = Assert.Single(list.Recipients, r => r.Id != original.Id); Assert.Equal(42, added.Id); Assert.Equal("New Operator", added.Name); Assert.Equal(7, added.NotificationListId); await repo.DidNotReceiveWithAnyArgs().DeleteRecipientAsync(default, default); await ManagementActor.ExecuteConfigurationCommandAsync(services, new DeleteNotificationRecipientCommand(42), user); AssertOriginalUnchanged(); Assert.Single(list.Recipients); await repo.Received(1).AddRecipientAsync(Arg.Any(), Arg.Any()); await repo.Received(1).DeleteRecipientAsync(42, Arg.Any()); await repo.DidNotReceive().DeleteRecipientAsync(41, Arg.Any()); await repo.DidNotReceiveWithAnyArgs().UpdateRecipientAsync(default!, default); await repo.Received(3).SaveChangesAsync(Arg.Any()); void AssertOriginalUnchanged() { var retained = Assert.Single(list.Recipients, r => r.Id == 41); Assert.Same(original, retained); Assert.Equal("Existing Operator", retained.Name); Assert.Equal(7, retained.NotificationListId); Assert.Equal(originalEmail, retained.EmailAddress); Assert.Equal(originalPhone, retained.PhoneNumber); } } }