public async Task ProcessEmailNotificationsAsyncTests( CircuitBreakerDetails circuitBreakerDetails, int batchAccountSize, SendNotificationResponse sendNotificationResponse, bool throwSendNotificationException = false, int halfOpenRetryMax = 5) { // Configure Calls A.CallTo(() => fakeAccountsService.GetCircuitBreakerStatusAsync()).Returns(circuitBreakerDetails); A.CallTo(() => fakeAccountsService.GetNextBatchOfEmailsAsync(A <int> ._)).Returns(GetAccountsToProcess(batchAccountSize)); if (throwSendNotificationException) { A.CallTo(() => fakeSendCitizenNotificationService.SendCitizenNotificationAsync(A <Account> ._)).Throws(() => new Exception(nameof(Exception), new Exception(nameof(Exception)))); } else { A.CallTo(() => fakeSendCitizenNotificationService.SendCitizenNotificationAsync(A <Account> ._)).Returns(sendNotificationResponse); } A.CallTo(() => fakeConfiguration.GetConfig <int>(A <string> ._)) .Returns(halfOpenRetryMax); //For this test the function call is not diasbled in the config. A.CallTo(() => fakeConfiguration.GetConfig <bool>(A <string> ._)).Returns(false); // Assign var emailProcessor = new EmailNotificationProcessor(fakeSendCitizenNotificationService, fakeApplicationLogger, fakeConfiguration, fakeAccountsService); // Act await emailProcessor.ProcessEmailNotificationsAsync(); // Assert A.CallTo(() => fakeAccountsService.GetCircuitBreakerStatusAsync()).MustHaveHappened(); if (circuitBreakerDetails.CircuitBreakerStatus != CircuitBreakerStatus.Open) { if (throwSendNotificationException) { A.CallTo(() => fakeAccountsService.InsertAuditAsync(A <AccountNotificationAudit> .That.Matches(audit => audit.NotificationProcessingStatus == NotificationProcessingStatus.Failed && !string.IsNullOrWhiteSpace(audit.Note)))) .MustHaveHappened(); A.CallTo(() => fakeAccountsService.HalfOpenCircuitBreakerAsync()).MustHaveHappened(); if (circuitBreakerDetails.HalfOpenRetryCount == halfOpenRetryMax) { A.CallTo(() => fakeAccountsService.SetBatchToCircuitGotBrokenAsync(A <IEnumerable <Account> > ._)).MustHaveHappened(); A.CallTo(() => fakeAccountsService.OpenCircuitBreakerAsync()).MustHaveHappened(); } } else { if (sendNotificationResponse.Success) { A.CallTo(() => fakeAccountsService.InsertAuditAsync(A <AccountNotificationAudit> ._)).MustHaveHappened(batchAccountSize, Times.Exactly); if (circuitBreakerDetails.CircuitBreakerStatus == CircuitBreakerStatus.HalfOpen) { A.CallTo(() => fakeAccountsService.CloseCircuitBreakerAsync()).MustHaveHappened(); } } else { if (sendNotificationResponse.RateLimitException) { A.CallTo(() => fakeAccountsService.OpenCircuitBreakerAsync()).MustHaveHappened(); A.CallTo(() => fakeAccountsService.SetBatchToCircuitGotBrokenAsync( A <IEnumerable <Account> > ._)) .MustHaveHappened(); A.CallTo(() => fakeApplicationLogger.Info(A <string> ._)).MustHaveHappened(); } else { A.CallTo(() => fakeAccountsService.InsertAuditAsync(A <AccountNotificationAudit> .That.Matches(audit => audit.NotificationProcessingStatus == NotificationProcessingStatus.Failed))) .MustHaveHappened(); } } } } else { A.CallTo(() => fakeAccountsService.GetNextBatchOfEmailsAsync(A <int> ._)).MustNotHaveHappened(); A.CallTo(() => fakeSendCitizenNotificationService.SendCitizenNotificationAsync(A <Account> ._)).MustNotHaveHappened(); } }
private async Task SendNextBatchOfEmailsAsync() { var circuitBreaker = await accountsService.GetCircuitBreakerStatusAsync(); if (circuitBreaker.CircuitBreakerStatus != CircuitBreakerStatus.Open) { var batchSize = configuration.GetConfig <int>(Constants.BatchSize); var emailBatch = await accountsService.GetNextBatchOfEmailsAsync(batchSize); var accountsToProcess = emailBatch.ToList(); applicationLogger.Trace($"About to process email notifications with a batch size of {accountsToProcess.Count}"); var halfOpenCountAllowed = configuration.GetConfig <int>(Constants.GovUkNotifyRetryCount); foreach (var account in accountsToProcess) { try { var serviceResponse = await sendCitizenNotificationService.SendCitizenNotificationAsync(account); if (serviceResponse.RateLimitException) { await accountsService.OpenCircuitBreakerAsync(); applicationLogger.Info( $"RateLimit Exception thrown now resetting the unprocessed email notifications"); await accountsService.SetBatchToCircuitGotBrokenAsync( accountsToProcess.Where(notification => !notification.Processed)); break; } await accountsService.InsertAuditAsync(new AccountNotificationAudit { Email = account.EMail, NotificationProcessingStatus = serviceResponse.Success ? NotificationProcessingStatus.Completed : NotificationProcessingStatus.Failed }); if (serviceResponse.Success && circuitBreaker.CircuitBreakerStatus == CircuitBreakerStatus.HalfOpen) { await accountsService.CloseCircuitBreakerAsync(); } account.Processed = true; } catch (Exception exception) { await accountsService.InsertAuditAsync(new AccountNotificationAudit { Email = account.EMail, NotificationProcessingStatus = NotificationProcessingStatus.Failed, Note = exception.InnerException?.Message }); await accountsService.HalfOpenCircuitBreakerAsync(); applicationLogger.ErrorJustLogIt("Exception whilst sending email notification", exception); circuitBreaker = await accountsService.GetCircuitBreakerStatusAsync(); if (circuitBreaker.CircuitBreakerStatus == CircuitBreakerStatus.HalfOpen && circuitBreaker.HalfOpenRetryCount == halfOpenCountAllowed) { await accountsService.OpenCircuitBreakerAsync(); //Set the all the accountsin the batch that did not get processed (sent ok) to CircuitGotBroken await accountsService.SetBatchToCircuitGotBrokenAsync( accountsToProcess.Where(notification => !notification.Processed)); break; } } } applicationLogger.Trace("Completed processing all accounts in the batch"); } else { applicationLogger.Info("Circuit is open so no account processed"); } }