diff --git a/FabApprovalWorkerService/Models/TrainingAssignment.cs b/FabApprovalWorkerService/Models/TrainingAssignment.cs new file mode 100644 index 0000000..32a1ee9 --- /dev/null +++ b/FabApprovalWorkerService/Models/TrainingAssignment.cs @@ -0,0 +1,12 @@ +using Dapper.Contrib.Extensions; + +namespace FabApprovalWorkerService.Models; +[Table("TrainingAssignment")] +public class TrainingAssignment { + [Key] + public int ID { get; set; } + public int TrainingID { get; set; } + public bool Status { get; set; } = false; + public bool Deleted { get; set; } = false; + public DateTime DeletedDate { get; set; } +} diff --git a/FabApprovalWorkerService/Services/ECNService.cs b/FabApprovalWorkerService/Services/ECNService.cs index 27bb8ca..defdaa9 100644 --- a/FabApprovalWorkerService/Services/ECNService.cs +++ b/FabApprovalWorkerService/Services/ECNService.cs @@ -8,6 +8,7 @@ public interface IECNService { Task> GetExpiringTECNs(); Task> GetExpiredTECNs(); Task> GetTECNNotificationUserEmails(); + Task GetEcnByNumber(int ecnNumber); } public class ECNService : IECNService { @@ -19,6 +20,28 @@ public class ECNService : IECNService { _dalService = dalService ?? throw new ArgumentNullException("IDalService not injected"); } + public async Task GetEcnByNumber(int ecnNumber) { + try { + _logger.LogInformation($"Attempting to get ECN {ecnNumber}"); + + if (ecnNumber <= 0) throw new ArgumentException($"{ecnNumber} not a valid ECN number"); + + string sql = $"select * from ECN where ECNNumber = {ecnNumber}"; + + ECN? ecn = (await _dalService.QueryAsync(sql)).FirstOrDefault(); + + if (ecn is null) throw new Exception($"ECN {ecnNumber} not found"); + + return ecn; + } catch (Exception ex) { + StringBuilder errMsgBuilder = new(); + errMsgBuilder.Append($"An exception occurred when attempting to get ECN {ecnNumber}. "); + errMsgBuilder.Append($"Exception: {ex.Message}"); + _logger.LogError(errMsgBuilder.ToString()); + throw; + } + } + public async Task> GetExpiredTECNs() { try { _logger.LogInformation("Attempting to get all TECNs expired in the last day"); diff --git a/FabApprovalWorkerService/Services/TrainingService.cs b/FabApprovalWorkerService/Services/TrainingService.cs index f64598f..f343cba 100644 --- a/FabApprovalWorkerService/Services/TrainingService.cs +++ b/FabApprovalWorkerService/Services/TrainingService.cs @@ -10,6 +10,9 @@ public interface ITrainingService { Task DeleteTrainingAssignment(int trainingId); Task> GetTrainingAssignmentIdsForTraining(int trainingId); Task DeleteDocAssignment(int trainingAssignmentId); + Task> GetActiveTrainingAssignments(); + Task UpdateTrainingAssignmentLastNotification(int trainingAssignmentId); + Task GetEcnNumberByTrainingId(int trainingId); } public class TrainingService : ITrainingService { @@ -121,4 +124,66 @@ public class TrainingService : ITrainingService { throw; } } + + public async Task> GetActiveTrainingAssignments() { + try { + _logger.LogInformation($"Attempting to get active training assignments"); + + StringBuilder queryBuilder = new(); + queryBuilder.Append("select ID, UserID, DateAssigned, TrainingID, status, Deleted, DeletedDate, LastNotification "); + queryBuilder.Append("from TrainingAssignments where status = 0 and (Deleted is null or Deleted = 0);"); + + return await _dalService.QueryAsync(queryBuilder.ToString()); + } catch (Exception ex) { + StringBuilder errMsgBuilder = new(); + errMsgBuilder.Append($"An exception occurred when attempting to get active training assignments. "); + errMsgBuilder.Append($"Exception: {ex.Message}"); + _logger.LogError(errMsgBuilder.ToString()); + throw; + } + } + + public async Task UpdateTrainingAssignmentLastNotification(int trainingAssignmentId) { + try { + _logger.LogInformation($"Attempting to update last notification date for training assignment {trainingAssignmentId}"); + + if (trainingAssignmentId <= 0) + throw new ArgumentException($"{trainingAssignmentId} is not a valid training assignment Id"); + + StringBuilder queryBuilder = new(); + queryBuilder.Append($"update TrainingAssignments set LastNotification = {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); + queryBuilder.Append($"where ID = {trainingAssignmentId};"); + + await _dalService.ExecuteAsync(queryBuilder.ToString()); + } catch (Exception ex) { + StringBuilder errMsgBuilder = new(); + errMsgBuilder.Append("An exception occurred when attempting to update last notification "); + errMsgBuilder.Append($"for training assignment {trainingAssignmentId}. Exception: {ex.Message}"); + _logger.LogError(errMsgBuilder.ToString()); + throw; + } + } + + public async Task GetEcnNumberByTrainingId(int trainingId) { + try { + _logger.LogInformation($"Attempting to get ECN number for training {trainingId}"); + + if (trainingId <= 0) + throw new ArgumentException($"{trainingId} is not a valid training Id"); + + string sql = $"select e.ECNNumber from Training t join ECN e on t.ECN = e.ECNNumber where t.TrainingID = {trainingId};"; + + int ecnNumber = (await _dalService.QueryAsync(sql)).FirstOrDefault(); + + if (ecnNumber <= 0) throw new Exception($"ECN number not found for training {trainingId}"); + + return ecnNumber; + } catch (Exception ex) { + StringBuilder errMsgBuilder = new(); + errMsgBuilder.Append($"An exception occurred when attempting to get ECN number for training {trainingId}. "); + errMsgBuilder.Append($"Exception: {ex.Message}"); + _logger.LogError(errMsgBuilder.ToString()); + throw; + } + } } diff --git a/FabApprovalWorkerService/Services/UserService.cs b/FabApprovalWorkerService/Services/UserService.cs index 139f8a6..d6f0c50 100644 --- a/FabApprovalWorkerService/Services/UserService.cs +++ b/FabApprovalWorkerService/Services/UserService.cs @@ -19,6 +19,7 @@ public interface IUserService { Task SetOOOTempProcessed(OOOTemp oOOTemp); Task> GetAllExpiredOOOUsersAsync(); Task GetUserEmail(int userId); + Task GetUserById(int userId); } public class UserService : IUserService { @@ -355,7 +356,7 @@ public class UserService : IUserService { string sql = $"select Email from Users where UserID = {userId}"; - string? userEmail = (await _dalService.QueryAsync(sql)).ToList().FirstOrDefault(); + string? userEmail = (await _dalService.QueryAsync(sql)).FirstOrDefault(); if (userEmail is null) throw new Exception($"No email found for user {userId}"); @@ -369,4 +370,27 @@ public class UserService : IUserService { throw; } } + + public async Task GetUserById(int userId) { + if (userId <= 0) throw new ArgumentException($"{userId} not a valid UserID"); + + try { + _logger.LogInformation($"Attempting to get user {userId}"); + + string sql = $"select * from Users where UserID = {userId}"; + + User? user = (await _dalService.QueryAsync(sql)).FirstOrDefault(); + + if (user is null) + throw new Exception($"No user found for id {userId}"); + + return user; + } 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/SetupScripts/CreateTrainingTables.sql b/FabApprovalWorkerService/SetupScripts/CreateTrainingTables.sql index eecc93b..c5f87c0 100644 --- a/FabApprovalWorkerService/SetupScripts/CreateTrainingTables.sql +++ b/FabApprovalWorkerService/SetupScripts/CreateTrainingTables.sql @@ -14,15 +14,34 @@ drop table if exists TrainingAssignments; create table TrainingAssignments ( ID integer primary key, + UserID integer not null, + DateAssigned text not null, TrainingID integer not null, Deleted integer default 0, status integer default 0, - DeletedDate text + DeletedDate text, + LastNotification text ); -insert into TrainingAssignments (TrainingID) -values (1), (1), (2), (2), (3), (3), (4), (4), (5), (5), (6), (6), -(7), (7), (8), (8), (9), (9); +insert into TrainingAssignments (TrainingID, UserID, DateAssigned) +values (1, 1, '2024-02-01 00:00:00.000'), +(1, 11, '2024-04-01 00:00:00.000'), +(2, 23, '2024-02-01 00:00:00.000'), +(2, 18, '2024-02-01 00:00:00.000'), +(3, 5, '2024-02-01 00:00:00.000'), +(3, 25, '2024-02-01 00:00:00.000'), +(4, 15, '2024-04-01 00:00:00.000'), +(4, 12, '2024-02-01 00:00:00.000'), +(5, 9, '2024-02-01 00:00:00.000'), +(5, 19, '2024-02-01 00:00:00.000'), +(6, 13, '2024-02-01 00:00:00.000'), +(6, 3, '2024-04-01 00:00:00.000'), +(7, 29, '2024-02-01 00:00:00.000'), +(7, 17, '2024-02-01 00:00:00.000'), +(8, 8, '2024-02-01 00:00:00.000'), +(8, 4, '2024-02-01 00:00:00.000'), +(9, 17, '2024-02-01 00:00:00.000'), +(9, 16, '2024-04-01 00:00:00.000'); drop table if exists TrainingDocAcks; diff --git a/FabApprovalWorkerService/Workers/TrainingNotificationWorker.cs b/FabApprovalWorkerService/Workers/TrainingNotificationWorker.cs new file mode 100644 index 0000000..58fbb9c --- /dev/null +++ b/FabApprovalWorkerService/Workers/TrainingNotificationWorker.cs @@ -0,0 +1,71 @@ +using FabApprovalWorkerService.Models; +using FabApprovalWorkerService.Services; + +using Infineon.Monitoring.MonA; + +using Quartz; + +using System.Text; + +namespace FabApprovalWorkerService.Workers; + +public class TrainingNotificationWorker : IJob { + private readonly ILogger _logger; + private readonly ITrainingService _trainingService; + private readonly IUserService _userService; + private readonly IECNService _ecnService; + private readonly ISmtpService _smtpService; + private readonly IMonInClient _monInClient; + + public TrainingNotificationWorker(ILogger logger, + ITrainingService trainingService, + IUserService userService, + IECNService ecnService, + ISmtpService smtpService, + IMonInClient monInClient) { + _logger = logger ?? throw new ArgumentNullException("ILogger not injected"); + _trainingService = trainingService ?? throw new ArgumentNullException("ITrainingService 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"); + _monInClient = monInClient ?? throw new ArgumentNullException("IMonInClient not injected"); + } + + public async Task Execute(IJobExecutionContext context) { + DateTime start = DateTime.Now; + bool isInternalError = false; + StringBuilder errorMessage = new(); + string metricName = "TrainingNotificationWorker"; + + try { + _logger.LogInformation("Attempting to send training notifications"); + + IEnumerable trainingAssignments = await _trainingService.GetActiveTrainingAssignments(); + + foreach (TrainingAssignment trainingAssignment in trainingAssignments) { + ECN ecn = await _ecnService.GetEcnByNumber + + User user = await _userService.GetUserById(trainingAssignment.UserID); + + } + + _logger.LogInformation("Successfully sent training notifications"); + } catch (Exception ex) { + StringBuilder errMsgBuilder = new(); + errMsgBuilder.Append("An exception occurred when attempting to send training notifications. "); + errMsgBuilder.Append($"Exception: {ex.Message}"); + _logger.LogError(errMsgBuilder.ToString()); + isInternalError = true; + } finally { + DateTime end = DateTime.Now; + double latencyInMS = (end - start).TotalMilliseconds; + _monInClient.PostMetric(metricName + "Latency", latencyInMS); + + if (isInternalError) { + _monInClient.PostStatus(metricName, State.Critical); + } else { + _monInClient.PostStatus(metricName, State.Ok); + } + } + } +} diff --git a/FabApprovalWorkerServiceTests/ECNServiceTests.cs b/FabApprovalWorkerServiceTests/ECNServiceTests.cs index 394ea80..54c8c0b 100644 --- a/FabApprovalWorkerServiceTests/ECNServiceTests.cs +++ b/FabApprovalWorkerServiceTests/ECNServiceTests.cs @@ -29,7 +29,7 @@ internal class ECNServiceTests { } [Test] - public async Task GetExpiringTECNsWithDbErrorShouldThrowException() { + public void GetExpiringTECNsWithDbErrorShouldThrowException() { _mockDalService.Setup(d => d.QueryAsync(It.IsAny())).ThrowsAsync(new Exception()); _ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object); @@ -38,7 +38,7 @@ internal class ECNServiceTests { } [Test] - public async Task GetExpiredTECNsWithDbErrorShouldThrowException() { + public void GetExpiredTECNsWithDbErrorShouldThrowException() { _mockDalService.Setup(d => d.QueryAsync(It.IsAny())).ThrowsAsync(new Exception()); _ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object); @@ -215,7 +215,7 @@ internal class ECNServiceTests { } [Test] - public async Task GetTECNNotificationUserEmailsDbErrorShouldThrowException() { + public void GetTECNNotificationUserEmailsDbErrorShouldThrowException() { _mockDalService.Setup(d => d.QueryAsync(It.IsAny())).Throws(new Exception()); _ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object); @@ -238,4 +238,52 @@ internal class ECNServiceTests { Assert.That(actualEmails.Count(), Is.EqualTo(2)); } + + [Test] + public void GetEcnByNumberWithInvalidNumberShouldThrowException() { + _ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object); + + Assert.ThrowsAsync(async Task () => await _ecnService.GetEcnByNumber(0)); + + Assert.ThrowsAsync(async Task () => await _ecnService.GetEcnByNumber(-4)); + } + + [Test] + public void GetEcnByNumberDbErrorShouldThrowException() { + _mockDalService.Setup(d => d.QueryAsync(It.IsAny())).Throws(new Exception()); + + _ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object); + + Assert.ThrowsAsync(async Task () => await _ecnService.GetEcnByNumber(2)); + } + + [Test] + public void GetEcnByNumberEcnNotFoundShouldThrowException() { + IEnumerable expectedEcns = new List(); + + _mockDalService.Setup(d => d.QueryAsync(It.IsAny())).Returns(Task.FromResult(expectedEcns)); + + _ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object); + + Assert.ThrowsAsync(async Task () => await _ecnService.GetEcnByNumber(8)); + } + + [Test] + public async Task GetEcnByNumberShouldReturnExpectedEcn() { + IEnumerable expectedEcns = new List() { + new ECN() { + ECNNumber = 1, + OriginatorID = 1, + Title = "title" + } + }; + + _mockDalService.Setup(d => d.QueryAsync(It.IsAny())).Returns(Task.FromResult(expectedEcns)); + + _ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object); + + ECN actualEcn = await _ecnService.GetEcnByNumber(9); + + Assert.That(expectedEcns.First(), Is.EqualTo(actualEcn)); + } } diff --git a/FabApprovalWorkerServiceTests/TrainingServiceTests.cs b/FabApprovalWorkerServiceTests/TrainingServiceTests.cs index fa81327..4ffc309 100644 --- a/FabApprovalWorkerServiceTests/TrainingServiceTests.cs +++ b/FabApprovalWorkerServiceTests/TrainingServiceTests.cs @@ -1,4 +1,5 @@ -using FabApprovalWorkerService.Services; +using FabApprovalWorkerService.Models; +using FabApprovalWorkerService.Services; using Microsoft.Extensions.Logging; @@ -26,6 +27,8 @@ public class TrainingServiceTests { public void TrainingServiceWithNullDalServiceShouldThrowException() { Assert.Throws(() => new TrainingService(_mockLogger.Object, null)); } +<<<<<<< Updated upstream +======= [Test] public void DeleteDocAssignmentWithInvalidIdShouldThrowException() { @@ -169,4 +172,71 @@ public class TrainingServiceTests { _mockDalService.Verify(d => d.ExecuteAsync(It.IsAny())); } + + [Test] + public async Task GetActiveTrainingAssignmentsShouldReturnExpectedAssignments() { + IEnumerable expectedAssignments = new List() { + new TrainingAssignment() { + ID = 1, + TrainingID = 1, + UserID = 1, + DateAssigned = DateTime.Now + }, + new TrainingAssignment() { + ID = 2, + TrainingID = 1, + UserID = 2, + DateAssigned = DateTime.Now + }, + new TrainingAssignment() { + ID = 3, + TrainingID = 1, + UserID = 3, + DateAssigned = DateTime.Now + } + }; + + _mockDalService.Setup(d => d.QueryAsync(It.IsAny())).Returns(Task.FromResult(expectedAssignments)); + + _trainingService = new TrainingService(_mockLogger.Object, _mockDalService.Object); + + IEnumerable actualAssignments = await _trainingService.GetActiveTrainingAssignments(); + + Assert.That(actualAssignments.Count() == expectedAssignments.Count()); + } + + [Test] + public void GetActiveTrainingAssignmentsWithDbErrorShouldThrowException() { + _mockDalService.Setup(d => d.QueryAsync(It.IsAny())).Throws(new Exception()); + + _trainingService = new TrainingService(_mockLogger.Object, _mockDalService.Object); + + Assert.ThrowsAsync(async Task() => await _trainingService.GetActiveTrainingAssignments()); + } + + [Test] + public void UpdateTrainingAssignmentLastNotificationWithInvalidIdShouldThrowException() { + _trainingService = new TrainingService(_mockLogger.Object, _mockDalService.Object); + + Assert.ThrowsAsync(async Task () => await _trainingService.UpdateTrainingAssignmentLastNotification(-1)); + } + + [Test] + public void UpdateTrainingAssignmentLastNotificationDbErrorShouldThrowException() { + _mockDalService.Setup(d => d.ExecuteAsync(It.IsAny())).Throws(new Exception()); + + _trainingService = new TrainingService(_mockLogger.Object, _mockDalService.Object); + + Assert.ThrowsAsync(async Task () => await _trainingService.UpdateTrainingAssignmentLastNotification(1)); + } + + [Test] + public async Task UpdateTrainingAssignmentLastNotificationShouldExecuteSql() { + _trainingService = new TrainingService(_mockLogger.Object, _mockDalService.Object); + + await _trainingService.UpdateTrainingAssignmentLastNotification(1); + + _mockDalService.Verify(d => d.ExecuteAsync(It.IsAny()), Times.Once()); + } +>>>>>>> Stashed changes } diff --git a/FabApprovalWorkerServiceTests/UserServiceTests.cs b/FabApprovalWorkerServiceTests/UserServiceTests.cs index c2a5b8c..9a14634 100644 --- a/FabApprovalWorkerServiceTests/UserServiceTests.cs +++ b/FabApprovalWorkerServiceTests/UserServiceTests.cs @@ -2,6 +2,7 @@ using FabApprovalWorkerService.Models; using FabApprovalWorkerService.Services; using Microsoft.Extensions.Logging; +using Microsoft.Identity.Client; using Moq; @@ -458,4 +459,56 @@ internal class UserServiceTests { Assert.That(actualEmail, Is.EqualTo(emailsFromDb.First())); } + + [Test] + public void GetUserByIdWithInvalidIdShouldThrowException() { + _userService = new UserService(MOCK_LOGGER, _mockDalService.Object); + + Assert.ThrowsAsync(async Task () => await _userService.GetUserById(-4)); + + Assert.ThrowsAsync(async Task () => await _userService.GetUserById(0)); + } + + [Test] + public void GetUserByIdDbErrorShouldThrowException() { + _mockDalService.Setup(d => d.QueryAsync(It.IsAny())).Throws(new Exception()); + + _userService = new UserService(MOCK_LOGGER, _mockDalService.Object); + + Assert.ThrowsAsync(async Task () => await _userService.GetUserById(5)); + } + + [Test] + public void GetUserByIdNoUserFoundShouldThrowException() { + IEnumerable emptyUsers = new List(); + + _mockDalService.Setup(d => d.QueryAsync(It.IsAny())).Returns(Task.FromResult(emptyUsers)); + + Assert.ThrowsAsync(async Task() => await _userService.GetUserById(5)); + } + + [Test] + public async Task GetUserByIdShouldReturnExpectedUser() { + IEnumerable expectedUsers = new List() { + new User() { + UserID = 1, + LoginID = "id", + FirstName = "firstName", + LastName = "lastName", + Email = "email", + IsAdmin = false, + OOO = false, + CanViewITAR = true, + IsManager = false + } + }; + + _mockDalService.Setup(d => d.QueryAsync(It.IsAny())).Returns(Task.FromResult(expectedUsers)); + + _userService = new UserService(MOCK_LOGGER, _mockDalService.Object); + + User actualUser = await _userService.GetUserById(4); + + Assert.That(expectedUsers.First(), Is.EqualTo(actualUser)); + } } \ No newline at end of file