using LCU.API.Internal.DocumentIngestor.Configurations; using LCU.API.Internal.DocumentIngestor.Services; using LCU.API.Internal.DocumentIngestor.Services.IngestionOperationServices; using LCU.API.Internal.DocumentIngestor.Services.MemberNotificationInfoDeterminingServices; using LCU.API.Internal.DocumentIngestor.Services.MicroApiServices; using LCU.Library.Internal.NotificationsModels.Models; using LCU.Library.DocumentType.Models.Requests; using LCU.Library.DocumentType.Models.Responses.External; using LCU.Library.Internal.DocumentIngestorModels.Entities; using LCU.Library.Internal.DocumentIngestorModels.Enums; using LCU.Library.Internal.DocumentIngestorModels.Models; using LCU.Library.Internal.LcuMassTransitModels; using Moq; using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using LCU.API.Internal.DocumentIngestor.Services.QueueServices; namespace LCU.API.Internal.DocumentIngestorTests.Services.IngestionOperationServices; public class MemberDocumentNotifierServiceTest { private MemberDocumentNotifierService _memberDocumentNotifierService; private Mock _mockMemberNotificationInfoService; private Mock _mockDocumentTypeRetrieverService; private Mock _mockDocumentProcessingInfoService; private Mock _mockNotificationApiService; private Mock _mockMemberNotificationInfoDeterminingService; private Mock _mockQueueMemberNotificationCompletionService; private ApiConfiguration _apiConfiguration; private ConnectionConfiguration _connectionConfiguration; private ReceiveEndpointConfigurations _receiveEndpointConfigurations; private DigitalBankingConfiguration _digitalBankingConfiguration; [SetUp] public void Setup() { _apiConfiguration = new ApiConfiguration { MaxMemberNotificationAttempts = 3, MaliciousFileSearchPaddingHours = 1, TimeZone = "America/Chicago", NotificationEmailTemplate = @"../../../../LCU.API.Internal.DocumentIngestor/Templates/NotificationEmailTemplate.html", NotificationEmailRowTemplate = @"../../../../LCU.API.Internal.DocumentIngestor/Templates/NotificationEmailRowTemplate.html", FeatureFlags = new FeatureFlags() { RestrictNotificationRecipients = false }, BatchSkipAmount = 10000, Notifications = new Notifications { AllowedRecipientExpression = "*" } }; _connectionConfiguration = new ConnectionConfiguration() { HostUrl = "Test", UserName = "Test", Password = "Test" }; _receiveEndpointConfigurations = new ReceiveEndpointConfigurations() { MemberNotificationCompletionEndpoint = new ReceiveEndpointConfiguration() { ReceiveEndpoint = "Test", ReceiveEndpointType = "Exchange", PrefetchLimit = 1 } }; _digitalBankingConfiguration = new DigitalBankingConfiguration() { DigitalBankingUrl = "https://digitalbanking.landmarkcu.com/eDocs", MobileBankingApiKey = "TEST", MobileBankingBaseUrl = "https://qa-redirect-api.qa.applications.landmarkcu.com/index.html", MobileBankingLinkId = "a4e648ff-247a-42a8-b153-4b65d4933d1c" }; _mockMemberNotificationInfoService = new Mock(); _mockDocumentTypeRetrieverService = new Mock(); _mockDocumentProcessingInfoService = new Mock(); _mockNotificationApiService = new Mock(); _mockMemberNotificationInfoDeterminingService = new Mock(); _mockQueueMemberNotificationCompletionService = new Mock(); _memberDocumentNotifierService = new MemberDocumentNotifierService(_mockMemberNotificationInfoService.Object, _mockDocumentTypeRetrieverService.Object, _mockDocumentProcessingInfoService.Object, _mockNotificationApiService.Object, _mockMemberNotificationInfoDeterminingService.Object, _mockQueueMemberNotificationCompletionService.Object, _apiConfiguration, _connectionConfiguration, _receiveEndpointConfigurations, _digitalBankingConfiguration); } [Test] public async Task PrepareAndSendNotifications_Success() { var request = new MemberDocumentNotificationRequest() { RunCriticalFailureNotifications = false }; var memberNotificationInfos = GenerateMemberNotificationInfos(); var documentProcessingInfos = GenerateDocumentProcessingInfos(); var documentCodeDetailInfos = GenerateDocumentCodeDetailInfos(); var deliveryMethod = (memberNotificationInfos[2].EmailAddress, DeliveryMethod.Electronic); List maliciousDocuments = null; _mockMemberNotificationInfoService .Setup(x => x.GetSendableMemberNotificationInfosAsync(It.IsAny(), _apiConfiguration.MaxMemberNotificationAttempts)) .ReturnsAsync(memberNotificationInfos); _mockDocumentProcessingInfoService.Setup(x => x.GetListByIdAsync(It.IsAny>())).ReturnsAsync(documentProcessingInfos); _mockMemberNotificationInfoDeterminingService.Setup(x => x.DetermineDeliveryMethod(It.IsAny())).ReturnsAsync(deliveryMethod); _mockDocumentTypeRetrieverService.Setup(x => x.GetListOfMaliciousDocuments(It.IsAny())).ReturnsAsync(maliciousDocuments); _mockDocumentTypeRetrieverService.SetupSequence(x => x.GetDocumentCodeDetailByBusinessName(It.IsAny())) .ReturnsAsync(documentCodeDetailInfos[0]) .ReturnsAsync(documentCodeDetailInfos[1]) .ReturnsAsync(documentCodeDetailInfos[2]); var result = await _memberDocumentNotifierService.PrepareAndSendNotifications(request); Assert.NotNull(result); Assert.IsTrue(result.IsSuccessful); Assert.IsEmpty(result.Messages); Assert.Zero(result.FailureCount); _mockMemberNotificationInfoService.Verify(x => x.GetSendableMemberNotificationInfosAsync(It.IsAny(), It.IsAny()), Times.Once); _mockDocumentProcessingInfoService.Verify(x => x.GetListByIdAsync(It.IsAny>()), Times.Once); _mockMemberNotificationInfoDeterminingService.Verify(x => x.DetermineDeliveryMethod(It.IsAny()), Times.Once); _mockDocumentTypeRetrieverService.Verify(x => x.GetListOfMaliciousDocuments(It.IsAny()), Times.Once); _mockDocumentTypeRetrieverService.Verify(x => x.GetDocumentCodeDetailByBusinessName(It.IsAny()), Times.Exactly(3)); _mockNotificationApiService.Verify(x => x.SendEmailAsync(It.IsAny()), Times.Exactly(2)); } [Test] public async Task PrepareAndSendNotifications_Success_NoMemberNotificationsToSend() { var request = new MemberDocumentNotificationRequest() { RunCriticalFailureNotifications = false }; var memberNotificationInfos = new List(); _mockMemberNotificationInfoService .Setup(x => x.GetSendableMemberNotificationInfosAsync(It.IsAny(), _apiConfiguration.MaxMemberNotificationAttempts)) .ReturnsAsync(memberNotificationInfos); var result = await _memberDocumentNotifierService.PrepareAndSendNotifications(request); Assert.NotNull(result); Assert.IsTrue(result.IsSuccessful); Assert.AreEqual(result.Messages.Count, 1); Assert.Zero(result.FailureCount); _mockMemberNotificationInfoService.Verify(x => x.GetSendableMemberNotificationInfosAsync(It.IsAny(), It.IsAny()), Times.Once); _mockDocumentProcessingInfoService.Verify(x => x.GetListByIdAsync(It.IsAny>()), Times.Never); _mockMemberNotificationInfoDeterminingService.Verify(x => x.DetermineDeliveryMethod(It.IsAny()), Times.Never); _mockDocumentTypeRetrieverService.Verify(x => x.GetListOfMaliciousDocuments(It.IsAny()), Times.Never); _mockDocumentTypeRetrieverService.Verify(x => x.GetDocumentCodeDetailByBusinessName(It.IsAny()), Times.Never); _mockNotificationApiService.Verify(x => x.SendEmailAsync(It.IsAny()), Times.Never); } [Test] public async Task PrepareAndSendNotifications_FailureCount() { var request = new MemberDocumentNotificationRequest() { RunCriticalFailureNotifications = false }; var memberNotificationInfos = GenerateMemberNotificationInfos(); var documentProcessingInfos = GenerateDocumentProcessingInfos(); _mockMemberNotificationInfoService .Setup(x => x.GetSendableMemberNotificationInfosAsync(It.IsAny(), _apiConfiguration.MaxMemberNotificationAttempts)) .ReturnsAsync(memberNotificationInfos); _mockDocumentProcessingInfoService.Setup(x => x.GetListByIdAsync(It.IsAny>())).ReturnsAsync(documentProcessingInfos); _mockDocumentProcessingInfoService.Setup(x => x.GetListByIdAsync(It.IsAny>())).ThrowsAsync(new Exception("Failure")); var result = await _memberDocumentNotifierService.PrepareAndSendNotifications(request); Assert.NotNull(result); Assert.IsTrue(result.IsFailure); Assert.AreEqual(result.Messages.Count, 1); Assert.AreEqual(result.FailureCount, 1); _mockMemberNotificationInfoService.Verify(x => x.GetSendableMemberNotificationInfosAsync(It.IsAny(), It.IsAny()), Times.Once); _mockDocumentProcessingInfoService.Verify(x => x.GetListByIdAsync(It.IsAny>()), Times.Once); _mockMemberNotificationInfoDeterminingService.Verify(x => x.DetermineDeliveryMethod(It.IsAny()), Times.Never); _mockDocumentTypeRetrieverService.Verify(x => x.GetListOfMaliciousDocuments(It.IsAny()), Times.Never); _mockDocumentTypeRetrieverService.Verify(x => x.GetDocumentCodeDetailByBusinessName(It.IsAny()), Times.Never); _mockNotificationApiService.Verify(x => x.SendEmailAsync(It.IsAny()), Times.Never); } [Test] public async Task PrepareAndSendNotifications_Success_MaliciousFile() { var request = new MemberDocumentNotificationRequest() { RunCriticalFailureNotifications = false }; var memberNotificationInfos = GenerateMemberNotificationInfos(); var documentProcessingInfos = GenerateDocumentProcessingInfos(); var documentCodeDetailInfos = GenerateDocumentCodeDetailInfos(); var deliveryMethod = (memberNotificationInfos[2].EmailAddress, DeliveryMethod.Electronic); var maliciousId = Guid.NewGuid(); var maliciousDocuments = new List() { new() { DocumentId = maliciousId } }; documentProcessingInfos[1].DocumentId = maliciousId; _mockMemberNotificationInfoService .Setup(x => x.GetSendableMemberNotificationInfosAsync(It.IsAny(), _apiConfiguration.MaxMemberNotificationAttempts)) .ReturnsAsync(memberNotificationInfos); _mockDocumentProcessingInfoService.Setup(x => x.GetListByIdAsync(It.IsAny>())).ReturnsAsync(documentProcessingInfos); _mockMemberNotificationInfoDeterminingService.Setup(x => x.DetermineDeliveryMethod(It.IsAny())).ReturnsAsync(deliveryMethod); _mockDocumentTypeRetrieverService.Setup(x => x.GetListOfMaliciousDocuments(It.IsAny())).ReturnsAsync(maliciousDocuments); _mockDocumentTypeRetrieverService.SetupSequence(x => x.GetDocumentCodeDetailByBusinessName(It.IsAny())) .ReturnsAsync(documentCodeDetailInfos[0]) .ReturnsAsync(documentCodeDetailInfos[2]); var result = await _memberDocumentNotifierService.PrepareAndSendNotifications(request); Assert.NotNull(result); Assert.IsTrue(result.IsSuccessful); Assert.IsEmpty(result.Messages); Assert.Zero(result.FailureCount); _mockMemberNotificationInfoService.Verify(x => x.GetSendableMemberNotificationInfosAsync(It.IsAny(), It.IsAny()), Times.Once); _mockDocumentProcessingInfoService.Verify(x => x.GetListByIdAsync(It.IsAny>()), Times.Once); _mockDocumentTypeRetrieverService.Verify(x => x.GetListOfMaliciousDocuments(It.IsAny()), Times.Once); _mockDocumentTypeRetrieverService.Verify(x => x.GetDocumentCodeDetailByBusinessName(It.IsAny()), Times.Exactly(2)); _mockNotificationApiService.Verify(x => x.SendEmailAsync(It.IsAny()), Times.Exactly(2)); } [Test] public async Task PrepareAndSendNotifications_RestrictNotificationRecipientsOff_SendsToAnyEmail() { var disallowedRecipient = MemberNotificationFactory(); _apiConfiguration.FeatureFlags.RestrictNotificationRecipients = false; _apiConfiguration.Notifications.AllowedRecipientExpression = $"(?!{disallowedRecipient.EmailAddress})\\A"; var request = new MemberDocumentNotificationRequest { RunCriticalFailureNotifications = false }; var memberNotificationInfos = Enumerable.Repeat(() => MemberNotificationFactory(), 3) .Select(f => f()) .Concat(new[] { disallowedRecipient }) .ToArray(); var documentProcessingInfos = Enumerable.Repeat> ( i => DocumentProcessingInfoFactory(documentProcessingInfoId: memberNotificationInfos[i].DocumentProcessingInfoId), memberNotificationInfos.Length ) .Select((f, i) => f(i)) .ToArray(); var documentCodeDetails = Enumerable.Repeat> ( i => DocumentCodeDetailInfoFactory(documentBusinessName: documentProcessingInfos[i].DocumentBusinessName), documentProcessingInfos.Length ) .Select((f, i) => f(i)) .ToDictionary(dcd => dcd.DocumentBusinessName); var sentEmails = new List(); _mockMemberNotificationInfoService .Setup(mnis => mnis.GetSendableMemberNotificationInfosAsync(It.IsAny(), _apiConfiguration.MaxMemberNotificationAttempts)) .ReturnsAsync(memberNotificationInfos); _mockDocumentProcessingInfoService .Setup(dpis => dpis.GetListByIdAsync(It.IsAny>())) .ReturnsAsync(documentProcessingInfos); _mockDocumentTypeRetrieverService .Setup(dtrs => dtrs.GetListOfMaliciousDocuments(It.IsAny())) .ReturnsAsync((IEnumerable)null); _mockDocumentTypeRetrieverService .Setup(dtrs => dtrs.GetDocumentCodeDetailByBusinessName(It.IsAny())) .Returns(async businessName => documentCodeDetails.TryGetValue(businessName, out var documentCodeDetail) ? documentCodeDetail : null); _mockNotificationApiService .Setup(nas => nas.SendEmailAsync(It.IsAny())) .Callback(sentEmails.Add); var result = await _memberDocumentNotifierService.PrepareAndSendNotifications(request); Assert.IsFalse(result.IsFailure); Assert.IsTrue(result.IsSuccessful); Assert.AreEqual(0, result.FailureCount); Assert.AreEqual(memberNotificationInfos.Length, sentEmails.Select(se => se.Recipient).Distinct().Count()); } [Test] public async Task PrepareAndSendNotifications_RestrictNotificationRecipientsOnAndAnyUnmatched_DoesNotSendToUnmatchedEmails() { var disallowedRecipient = MemberNotificationFactory(); _apiConfiguration.FeatureFlags.RestrictNotificationRecipients = true; _apiConfiguration.Notifications.AllowedRecipientExpression = $"(?!{disallowedRecipient.EmailAddress})\\A"; var request = new MemberDocumentNotificationRequest { RunCriticalFailureNotifications = false }; var memberNotificationInfos = Enumerable.Repeat(() => MemberNotificationFactory(), 3) .Select(f => f()) .Concat(new[] { disallowedRecipient }) .ToArray(); var documentProcessingInfos = Enumerable.Repeat> ( i => DocumentProcessingInfoFactory(documentProcessingInfoId: memberNotificationInfos[i].DocumentProcessingInfoId), memberNotificationInfos.Length ) .Select((f, i) => f(i)) .ToArray(); var documentCodeDetails = Enumerable.Repeat> ( i => DocumentCodeDetailInfoFactory(documentBusinessName: documentProcessingInfos[i].DocumentBusinessName), documentProcessingInfos.Length ) .Select((f, i) => f(i)) .ToDictionary(dcd => dcd.DocumentBusinessName); var sentEmails = new List(); _mockMemberNotificationInfoService .Setup(mnis => mnis.GetSendableMemberNotificationInfosAsync(It.IsAny(), _apiConfiguration.MaxMemberNotificationAttempts)) .ReturnsAsync(memberNotificationInfos); _mockDocumentProcessingInfoService .Setup(dpis => dpis.GetListByIdAsync(It.IsAny>())) .ReturnsAsync(documentProcessingInfos); _mockDocumentTypeRetrieverService .Setup(dtrs => dtrs.GetListOfMaliciousDocuments(It.IsAny())) .ReturnsAsync((IEnumerable)null); _mockDocumentTypeRetrieverService .Setup(dtrs => dtrs.GetDocumentCodeDetailByBusinessName(It.IsAny())) .Returns(async businessName => documentCodeDetails.TryGetValue(businessName, out var documentCodeDetail) ? documentCodeDetail : null); _mockNotificationApiService .Setup(nas => nas.SendEmailAsync(It.IsAny())) .Callback(sentEmails.Add); var result = await _memberDocumentNotifierService.PrepareAndSendNotifications(request); Assert.IsTrue(result.IsFailure); Assert.IsFalse(result.IsSuccessful); Assert.AreEqual(1, result.FailureCount); Assert.AreEqual(0, sentEmails.Count(se => se.Recipient.Contains(disallowedRecipient.EmailAddress))); } [Test] public async Task PrepareAndSendNotifications_RestrictNotificationRecipientsOnAndAllMatched_SendsToMatchedEmails() { var disallowedRecipient = MemberNotificationFactory(); _apiConfiguration.FeatureFlags.RestrictNotificationRecipients = true; _apiConfiguration.Notifications.AllowedRecipientExpression = $"(?!{disallowedRecipient.EmailAddress})\\A"; var request = new MemberDocumentNotificationRequest { RunCriticalFailureNotifications = false }; var allowedMemberNotificationInfos = Enumerable.Repeat(() => MemberNotificationFactory(), 3) .Select(f => f()) .ToArray(); var memberNotificationInfos = allowedMemberNotificationInfos .Concat(new[] { disallowedRecipient }) .ToArray(); var documentProcessingInfos = Enumerable.Repeat> ( i => DocumentProcessingInfoFactory(documentProcessingInfoId: memberNotificationInfos[i].DocumentProcessingInfoId), memberNotificationInfos.Length ) .Select((f, i) => f(i)) .ToArray(); var documentCodeDetails = Enumerable.Repeat> ( i => DocumentCodeDetailInfoFactory(documentBusinessName: documentProcessingInfos[i].DocumentBusinessName), documentProcessingInfos.Length ) .Select((f, i) => f(i)) .ToDictionary(dcd => dcd.DocumentBusinessName); var sentEmails = new List(); _mockMemberNotificationInfoService .Setup(mnis => mnis.GetSendableMemberNotificationInfosAsync(It.IsAny(), _apiConfiguration.MaxMemberNotificationAttempts)) .ReturnsAsync(memberNotificationInfos); _mockDocumentProcessingInfoService .Setup(dpis => dpis.GetListByIdAsync(It.IsAny>())) .ReturnsAsync(documentProcessingInfos); _mockDocumentTypeRetrieverService .Setup(dtrs => dtrs.GetListOfMaliciousDocuments(It.IsAny())) .ReturnsAsync((IEnumerable)null); _mockDocumentTypeRetrieverService .Setup(dtrs => dtrs.GetDocumentCodeDetailByBusinessName(It.IsAny())) .Returns(async businessName => documentCodeDetails.TryGetValue(businessName, out var documentCodeDetail) ? documentCodeDetail : null); _mockNotificationApiService .Setup(nas => nas.SendEmailAsync(It.IsAny())) .Callback(sentEmails.Add); await _memberDocumentNotifierService.PrepareAndSendNotifications(request); var allowedEmails = allowedMemberNotificationInfos.Select(mni => mni.EmailAddress); Assert.AreEqual(allowedMemberNotificationInfos.Length, sentEmails.Count(se => se.Recipient.Any(r => allowedEmails.Contains(r)))); } [Test] public async Task PrepareAndSendNotifications_Failure_NullEmails() { var request = new MemberDocumentNotificationRequest() { RunCriticalFailureNotifications = false }; var memberNotificationInfos = GenerateMemberNotificationInfos(); memberNotificationInfos[0].EmailAddress = ""; memberNotificationInfos[1].EmailAddress = ""; memberNotificationInfos[2].EmailAddress = ""; var documentProcessingInfos = GenerateDocumentProcessingInfos(); var documentCodeDetailInfos = GenerateDocumentCodeDetailInfos(); var deliveryMethod = (memberNotificationInfos[2].EmailAddress, DeliveryMethod.Electronic); var maliciousId = Guid.NewGuid(); List maliciousDocuments = null; _mockMemberNotificationInfoService .Setup(x => x.GetSendableMemberNotificationInfosAsync(It.IsAny(), _apiConfiguration.MaxMemberNotificationAttempts)) .ReturnsAsync(memberNotificationInfos); _mockDocumentProcessingInfoService.Setup(x => x.GetListByIdAsync(It.IsAny>())).ReturnsAsync(documentProcessingInfos); _mockMemberNotificationInfoDeterminingService.Setup(x => x.DetermineDeliveryMethod(It.IsAny())).ReturnsAsync(deliveryMethod); _mockDocumentTypeRetrieverService.Setup(x => x.GetListOfMaliciousDocuments(It.IsAny())).ReturnsAsync(maliciousDocuments); var result = await _memberDocumentNotifierService.PrepareAndSendNotifications(request); Assert.NotNull(result); Assert.IsTrue(result.IsSuccessful); Assert.AreEqual(result.Messages.Count, 0); Assert.AreEqual(result.FailureCount, 0); _mockMemberNotificationInfoService.Verify(x => x.GetSendableMemberNotificationInfosAsync(It.IsAny(), It.IsAny()), Times.Once); _mockDocumentProcessingInfoService.Verify(x => x.GetListByIdAsync(It.IsAny>()), Times.Once); _mockDocumentTypeRetrieverService.Verify(x => x.GetListOfMaliciousDocuments(It.IsAny()), Times.Once); _mockDocumentTypeRetrieverService.Verify(x => x.GetDocumentCodeDetailByBusinessName(It.IsAny()), Times.Never); _mockQueueMemberNotificationCompletionService.Verify(x => x.QueueMemberNotificationCompletionRequest(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Exactly(2)); _mockNotificationApiService.Verify(x => x.SendEmailAsync(It.IsAny()), Times.Never); } [Test] public void PrepareAndSendNotifications_ArgumentNullException() { MemberDocumentNotificationRequest request = null; Assert.ThrowsAsync(async () => await _memberDocumentNotifierService.HandleMemberDocumentNotification(request)); _mockMemberNotificationInfoService.Verify(x => x.GetSendableMemberNotificationInfosAsync(It.IsAny(), It.IsAny()), Times.Never); _mockMemberNotificationInfoDeterminingService.Verify(x => x.DetermineDeliveryMethod(It.IsAny()), Times.Never); _mockDocumentProcessingInfoService.Verify(x => x.GetListByIdAsync(It.IsAny>()), Times.Never); _mockNotificationApiService.Verify(x => x.SendEmailAsync(It.IsAny()), Times.Never); } [Test] public async Task PrepareAndSendNotifications_BreakLargeChunk_Success() { var request = new MemberDocumentNotificationRequest() { RunCriticalFailureNotifications = false }; var repeatAmount = 15000; var randomInt = new Random(); var memberNotificationInfos = Enumerable.Repeat(1, repeatAmount) .Select(_ => GenerateSingleMemberNotificationInfo(randomInt.Next(1, repeatAmount))).ToList(); var documentProcessingInfos = Enumerable.Repeat(1, repeatAmount) .Select(_ => GenerateSingleDocumentProcessingInfo(Guid.NewGuid())).ToList(); var documentCodeDetailInfo = GenerateDocumentCodeDetailInfos().FirstOrDefault(); List maliciousDocuments = null; _mockMemberNotificationInfoService .Setup(x => x.GetSendableMemberNotificationInfosAsync(It.IsAny(), _apiConfiguration.MaxMemberNotificationAttempts)) .ReturnsAsync(memberNotificationInfos); _mockDocumentProcessingInfoService.SetupSequence(x => x.GetListByIdAsync(It.IsAny>())) .ReturnsAsync(documentProcessingInfos.Skip(0).Take(_apiConfiguration.BatchSkipAmount).ToList()) .ReturnsAsync(documentProcessingInfos.Skip(_apiConfiguration.BatchSkipAmount).Take(_apiConfiguration.BatchSkipAmount).ToList()); _mockDocumentTypeRetrieverService.Setup(x => x.GetListOfMaliciousDocuments(It.IsAny())).ReturnsAsync(maliciousDocuments); _mockDocumentTypeRetrieverService.Setup(x => x.GetDocumentCodeDetailByBusinessName(It.IsAny())) .ReturnsAsync(() => documentCodeDetailInfo); var result = await _memberDocumentNotifierService.PrepareAndSendNotifications(request); Assert.NotNull(result); Assert.IsTrue(result.IsSuccessful); Assert.IsEmpty(result.Messages); Assert.Zero(result.FailureCount); _mockMemberNotificationInfoService.Verify(x => x.GetSendableMemberNotificationInfosAsync(It.IsAny(), It.IsAny()), Times.Once); _mockMemberNotificationInfoService.Verify(x => x.UpdateRange(It.IsAny>()), Times.Exactly(2)); _mockDocumentProcessingInfoService.Verify(x => x.GetListByIdAsync(It.IsAny>()), Times.Exactly(2)); _mockMemberNotificationInfoDeterminingService.Verify(x => x.DetermineDeliveryMethod(It.IsAny()), Times.Never); _mockDocumentTypeRetrieverService.Verify(x => x.GetListOfMaliciousDocuments(It.IsAny()), Times.Once); _mockDocumentTypeRetrieverService.Verify(x => x.GetDocumentCodeDetailByBusinessName(It.IsAny()), Times.Exactly(repeatAmount)); _mockNotificationApiService.Verify(x => x.SendEmailAsync(It.IsAny()), Times.Once); } const string letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvqxyz"; private static readonly Random Random = new(DateTime.Now.Millisecond); private static string RandomString => new(Enumerable.Repeat(() => letters[Random.Next(letters.Length)], 10).Select(c => c()).ToArray()); private static Random IdSeries = new(); private static MemberNotificationInfo MemberNotificationFactory ( string? emailAddress = null, string? agreeUserId = null, int? documentProcessingInfoId = null, NotificationStatusTypes? notificationStatusType = null ) => new() { MemberNotificationInfoId = IdSeries.Next(), AgreeUserId = agreeUserId ?? $"{IdSeries.Next()}{IdSeries.Next()}{IdSeries.Next()}", EmailAddress = emailAddress ?? $"{RandomString}@{RandomString}.com", EntityName = Guid.NewGuid().ToString(), DeliveryMethod = DeliveryMethod.Electronic, DocumentProcessingInfoId = documentProcessingInfoId ?? IdSeries.Next(), Attempts = 0, NotificationStatusType = notificationStatusType ?? NotificationStatusTypes.Pending, SentTimeStamp = DateTime.UtcNow, UploadDate = DateTime.UtcNow }; private static List GenerateMemberNotificationInfos() { return new List { MemberNotificationFactory ( agreeUserId: "123", emailAddress: "test@test.com", documentProcessingInfoId: 5, notificationStatusType: NotificationStatusTypes.Pending ), MemberNotificationFactory ( agreeUserId: "123", emailAddress: "test@test.com", documentProcessingInfoId: 6, notificationStatusType: NotificationStatusTypes.Pending ), MemberNotificationFactory ( agreeUserId: "789", emailAddress: "test3@test.com", documentProcessingInfoId: 7, notificationStatusType: NotificationStatusTypes.Failure ) }; } private static DocumentProcessingInfo DocumentProcessingInfoFactory ( int? documentProcessingInfoId = null ) => new() { DocumentId = new Guid(), DocumentProcessingInfoId = documentProcessingInfoId ?? IdSeries.Next(), DocumentFileName = Guid.NewGuid().ToString(), DocumentBusinessName = Guid.NewGuid().ToString() }; private static List GenerateDocumentProcessingInfos() { return new List() { DocumentProcessingInfoFactory ( documentProcessingInfoId: 5 ), DocumentProcessingInfoFactory ( documentProcessingInfoId: 6 ), DocumentProcessingInfoFactory ( documentProcessingInfoId: 7 ) }; } private static DocumentCodeDetailInfo DocumentCodeDetailInfoFactory ( string? documentBusinessName = null ) => new() { DocumentBusinessName = documentBusinessName ?? Guid.NewGuid().ToString(), DocumentMemberFacingName = Guid.NewGuid().ToString() }; private static List GenerateDocumentCodeDetailInfos() { return new List { DocumentCodeDetailInfoFactory ( documentBusinessName: "5498-SA HSA Contributions (Tax)" ), DocumentCodeDetailInfoFactory ( documentBusinessName: "1099-MISC" ), DocumentCodeDetailInfoFactory ( documentBusinessName: "5498 (Retirement Plan Contributions) (Tax)" ) }; } public MemberNotificationInfo GenerateSingleMemberNotificationInfo(int id) { return new MemberNotificationInfo() { MemberNotificationInfoId = id, AgreeUserId = "123", EmailAddress = "test@test.com", EntityName = "Fred", DeliveryMethod = DeliveryMethod.Electronic, DocumentProcessingInfoId = 5, Attempts = 1, NotificationStatusType = NotificationStatusTypes.Pending, SentTimeStamp = DateTime.UtcNow, UploadDate = DateTime.UtcNow }; } public DocumentProcessingInfo GenerateSingleDocumentProcessingInfo(Guid id) { return new DocumentProcessingInfo() { DocumentId = id, DocumentProcessingInfoId = 5, DocumentFileName = "FileName1", DocumentBusinessName = "5498-SA HSA Contributions (Tax)" }; } }