diff --git a/FabApprovalWorkerService/Services/ECNService.cs b/FabApprovalWorkerService/Services/ECNService.cs index dafa272..be814b3 100644 --- a/FabApprovalWorkerService/Services/ECNService.cs +++ b/FabApprovalWorkerService/Services/ECNService.cs @@ -7,6 +7,7 @@ namespace FabApprovalWorkerService.Services; public interface IECNService { Task> GetExpiringTECNs(); Task> GetExpiredTECNs(); + Task> GetTECNNotificationUserEmails(); } public class ECNService : IECNService { @@ -75,4 +76,24 @@ public class ECNService : IECNService { throw; } } + + public async Task> GetTECNNotificationUserEmails() { + try { + _logger.LogInformation("Attempting to get TECN notification user emails"); + + string sql = "select u.Email from TECNNotificationsUsers t join Users u on t.UserId = u.UserID;"; + + IEnumerable tecnNotificationUserEmails = (await _dalService.QueryAsync(sql)).ToList(); + + _logger.LogInformation($"Found {tecnNotificationUserEmails.Count()} TECN notification user emails"); + + return tecnNotificationUserEmails; + } catch (Exception ex) { + StringBuilder errMsgBuilder = new(); + errMsgBuilder.Append("An exception occurred when attempting to get TECN notification user emails. "); + errMsgBuilder.Append($"Exception: {ex.Message}"); + _logger.LogError(errMsgBuilder.ToString()); + throw; + } + } } diff --git a/FabApprovalWorkerService/Services/UserService.cs b/FabApprovalWorkerService/Services/UserService.cs index 12f085c..b5a53f7 100644 --- a/FabApprovalWorkerService/Services/UserService.cs +++ b/FabApprovalWorkerService/Services/UserService.cs @@ -1,7 +1,5 @@ using FabApprovalWorkerService.Models; -using NLog.Filters; - using System.Text; namespace FabApprovalWorkerService.Services; @@ -20,6 +18,7 @@ public interface IUserService { Task RemoveOOOFlagForUser(int userId); Task SetOOOTempProcessed(OOOTemp oOOTemp); Task> GetAllExpiredOOOUsersAsync(); + Task GetUserEmail(int userId); } public class UserService : IUserService { @@ -341,4 +340,27 @@ public class UserService : IUserService { throw; } } + + public async Task GetUserEmail(int userId) { + try { + if (userId <= 0) throw new ArgumentException($"User Id {userId} is not a valid user Id"); + + _logger.LogInformation($"Attempting to get email for user {userId}"); + + string sql = $"select Email from Users where UserID = {userId}"; + + string? userEmail = (await _dalService.QueryAsync(sql)).ToList().FirstOrDefault(); + + if (userEmail is null) + throw new Exception($"No email found for user {userId}"); + + return userEmail; + } catch (Exception ex) { + StringBuilder errMsgBuilder = new(); + errMsgBuilder.Append($"An exception occurred when attempting to get email for user {userId}. "); + errMsgBuilder.Append($"Exception: {ex.Message}"); + _logger.LogError(errMsgBuilder.ToString()); + throw; + } + } } diff --git a/FabApprovalWorkerService/Workers/ExpiringTECNWorker.cs b/FabApprovalWorkerService/Workers/ExpiringTECNWorker.cs new file mode 100644 index 0000000..1140e83 --- /dev/null +++ b/FabApprovalWorkerService/Workers/ExpiringTECNWorker.cs @@ -0,0 +1,86 @@ +using FabApprovalWorkerService.Models; +using FabApprovalWorkerService.Services; + +using Quartz; + +using System.Net.Mail; +using System.Text; + +namespace FabApprovalWorkerService.Workers; + +public class ExpiringTECNWorker : IJob { + private readonly ILogger _logger; + private readonly IMonInWorkerClient _monInClient; + private readonly IUserService _userService; + private readonly IECNService _ecnService; + private readonly ISmtpService _smtpService; + + public ExpiringTECNWorker(ILogger logger, + IMonInWorkerClient monInClient, + IUserService userService, + IECNService ecnService, + ISmtpService smtpService) { + _logger = logger ?? + throw new ArgumentNullException("ILogger not injected"); + _monInClient = monInClient ?? + throw new ArgumentNullException("IMonInWorkerClient not injected"); + _userService = userService ?? + throw new ArgumentNullException("IUserService not injected"); + _ecnService = ecnService ?? + throw new ArgumentNullException("IECNService not injected"); + _smtpService = smtpService ?? + throw new ArgumentNullException("ISmtpService not injected"); + } + + public async Task Execute(IJobExecutionContext context) { + DateTime start = DateTime.Now; + bool isInternalError = false; + StringBuilder errorMessage = new(); + string metricName = "ExpiringTECNWorker"; + + try { + _logger.LogInformation("Attempting to notify users of expiring TECNs"); + + IEnumerable expiringTECNs = await _ecnService.GetExpiringTECNs(); + + foreach (ECN eCN in expiringTECNs) { + string recipientEmail = await _userService.GetUserEmail(eCN.OriginatorID); + MailAddress recipientAddress = new MailAddress(recipientEmail); + List recipientList = new () { recipientAddress }; + + IEnumerable tecnNotificationUserEmails = await _ecnService.GetTECNNotificationUserEmails(); + List ccRecipientList = new(); + foreach (string email in tecnNotificationUserEmails) { + ccRecipientList.Add(new MailAddress(email)); + } + + StringBuilder bodyBuilder = new(); + bodyBuilder.Append($"Good day, TECN# {eCN.ECNNumber} will be expiring in "); + bodyBuilder.Append($"{(eCN.ExpirationDate - DateTime.Now).Days} "); + bodyBuilder.Append($"on {eCN.ExpirationDate.ToString("MMMM dd, yyyy")}. "); + bodyBuilder.Append($"
Review TECN "); + bodyBuilder.Append("here "); + + string subject = $"Notice of expiring TECN - {eCN.Title}"; + + await _smtpService.SendEmail(recipientList, ccRecipientList, subject, bodyBuilder.ToString()); + } + } catch (Exception ex) { + StringBuilder errMsgBuilder = new(); + errMsgBuilder.Append("An exception occurred when attempting to notify users of expiring TECNs. "); + errMsgBuilder.Append($"Exception: {ex.Message}"); + _logger.LogError(errMsgBuilder.ToString()); + isInternalError = true; + } finally { + DateTime end = DateTime.Now; + double latencyInMS = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", latencyInMS); + + if (isInternalError) { + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } +} diff --git a/FabApprovalWorkerServiceTests/ECNServiceTests.cs b/FabApprovalWorkerServiceTests/ECNServiceTests.cs index 1c66e80..cd54a0d 100644 --- a/FabApprovalWorkerServiceTests/ECNServiceTests.cs +++ b/FabApprovalWorkerServiceTests/ECNServiceTests.cs @@ -142,7 +142,6 @@ internal class ECNServiceTests { Assert.That(actual.Count(), Is.EqualTo(3)); } - // Test when responses include extension dates [Test] public async Task GetExpiringTECNsWithExtensionsFromDbShouldReturnSameResults() { IEnumerable ecns = new List() { @@ -214,4 +213,29 @@ internal class ECNServiceTests { Assert.That(actual.Count(), Is.EqualTo(2)); } + + [Test] + public async Task GetTECNNotificationUserEmailsDbErrorShouldThrowException() { + _mockDalService.Setup(d => d.QueryAsync(It.IsAny())).Throws(new Exception()); + + _ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object); + + Assert.ThrowsAsync(async Task () => await _ecnService.GetTECNNotificationUserEmails()); + } + + [Test] + public async Task GetTECNNotificationUserEmailsShouldReturnExpectedUserEmails() { + IEnumerable userEmails = new List() { + "fake1@email.com", + "fake2@email.com" + }; + + _mockDalService.Setup(d => d.QueryAsync(It.IsAny())).Returns(Task.FromResult(userEmails)); + + _ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object); + + IEnumerable actualEmails = await _ecnService.GetTECNNotificationUserEmails(); + + Assert.That(actualEmails.Count(), Is.EqualTo(2)); + } } diff --git a/FabApprovalWorkerServiceTests/UserServiceTests.cs b/FabApprovalWorkerServiceTests/UserServiceTests.cs index 9e81522..2f08813 100644 --- a/FabApprovalWorkerServiceTests/UserServiceTests.cs +++ b/FabApprovalWorkerServiceTests/UserServiceTests.cs @@ -443,4 +443,33 @@ internal class UserServiceTests { Assert.False(actual); } + + [Test] + public void GetUserEmailWithInvalidUserIdShouldThrowException() { + _userService = new UserService(MOCK_LOGGER, _mockDalService.Object); + + Assert.ThrowsAsync(async Task() => await _userService.GetUserEmail(-1)); + } + + [Test] + public void GetUserEmailWithDbErrorShouldThrowException() { + _mockDalService.Setup(d => d.QueryAsync(It.IsAny())).Throws(new Exception()); + + _userService = new UserService(MOCK_LOGGER, _mockDalService.Object); + + Assert.ThrowsAsync(async Task() => await _userService.GetUserEmail(3)); + } + + [Test] + public async Task GetUserEmailShouldReturnExpectedEmail() { + IEnumerable emailsFromDb = new List() { "user@email.com" }; + + _mockDalService.Setup(d => d.QueryAsync(It.IsAny())).Returns(Task.FromResult(emailsFromDb)); + + _userService = new UserService(MOCK_LOGGER, _mockDalService.Object); + + string actualEmail = await _userService.GetUserEmail(5); + + Assert.That(actualEmail, Is.EqualTo(emailsFromDb.First())); + } } \ No newline at end of file