Added training reminder worker

This commit is contained in:
Chase Tucker 2024-04-10 09:59:55 -07:00
parent 156dee0751
commit ed89f25dad
9 changed files with 394 additions and 9 deletions

View File

@ -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; }
}

View File

@ -8,6 +8,7 @@ public interface IECNService {
Task<IEnumerable<ECN>> GetExpiringTECNs(); Task<IEnumerable<ECN>> GetExpiringTECNs();
Task<IEnumerable<ECN>> GetExpiredTECNs(); Task<IEnumerable<ECN>> GetExpiredTECNs();
Task<IEnumerable<string>> GetTECNNotificationUserEmails(); Task<IEnumerable<string>> GetTECNNotificationUserEmails();
Task<ECN> GetEcnByNumber(int ecnNumber);
} }
public class ECNService : IECNService { public class ECNService : IECNService {
@ -19,6 +20,28 @@ public class ECNService : IECNService {
_dalService = dalService ?? throw new ArgumentNullException("IDalService not injected"); _dalService = dalService ?? throw new ArgumentNullException("IDalService not injected");
} }
public async Task<ECN> 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<ECN>(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<IEnumerable<ECN>> GetExpiredTECNs() { public async Task<IEnumerable<ECN>> GetExpiredTECNs() {
try { try {
_logger.LogInformation("Attempting to get all TECNs expired in the last day"); _logger.LogInformation("Attempting to get all TECNs expired in the last day");

View File

@ -10,6 +10,9 @@ public interface ITrainingService {
Task DeleteTrainingAssignment(int trainingId); Task DeleteTrainingAssignment(int trainingId);
Task<IEnumerable<int>> GetTrainingAssignmentIdsForTraining(int trainingId); Task<IEnumerable<int>> GetTrainingAssignmentIdsForTraining(int trainingId);
Task DeleteDocAssignment(int trainingAssignmentId); Task DeleteDocAssignment(int trainingAssignmentId);
Task<IEnumerable<TrainingAssignment>> GetActiveTrainingAssignments();
Task UpdateTrainingAssignmentLastNotification(int trainingAssignmentId);
Task<int> GetEcnNumberByTrainingId(int trainingId);
} }
public class TrainingService : ITrainingService { public class TrainingService : ITrainingService {
@ -121,4 +124,66 @@ public class TrainingService : ITrainingService {
throw; throw;
} }
} }
public async Task<IEnumerable<TrainingAssignment>> 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<TrainingAssignment>(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<int> 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<int>(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;
}
}
} }

View File

@ -19,6 +19,7 @@ public interface IUserService {
Task<bool> SetOOOTempProcessed(OOOTemp oOOTemp); Task<bool> SetOOOTempProcessed(OOOTemp oOOTemp);
Task<List<User>> GetAllExpiredOOOUsersAsync(); Task<List<User>> GetAllExpiredOOOUsersAsync();
Task<string> GetUserEmail(int userId); Task<string> GetUserEmail(int userId);
Task<User> GetUserById(int userId);
} }
public class UserService : IUserService { public class UserService : IUserService {
@ -355,7 +356,7 @@ public class UserService : IUserService {
string sql = $"select Email from Users where UserID = {userId}"; string sql = $"select Email from Users where UserID = {userId}";
string? userEmail = (await _dalService.QueryAsync<string>(sql)).ToList().FirstOrDefault(); string? userEmail = (await _dalService.QueryAsync<string>(sql)).FirstOrDefault();
if (userEmail is null) if (userEmail is null)
throw new Exception($"No email found for user {userId}"); throw new Exception($"No email found for user {userId}");
@ -369,4 +370,27 @@ public class UserService : IUserService {
throw; throw;
} }
} }
public async Task<User> 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<User>(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;
}
}
} }

View File

@ -14,15 +14,34 @@ drop table if exists TrainingAssignments;
create table TrainingAssignments ( create table TrainingAssignments (
ID integer primary key, ID integer primary key,
UserID integer not null,
DateAssigned text not null,
TrainingID integer not null, TrainingID integer not null,
Deleted integer default 0, Deleted integer default 0,
status integer default 0, status integer default 0,
DeletedDate text DeletedDate text,
LastNotification text
); );
insert into TrainingAssignments (TrainingID) insert into TrainingAssignments (TrainingID, UserID, DateAssigned)
values (1), (1), (2), (2), (3), (3), (4), (4), (5), (5), (6), (6), values (1, 1, '2024-02-01 00:00:00.000'),
(7), (7), (8), (8), (9), (9); (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; drop table if exists TrainingDocAcks;

View File

@ -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<TrainingNotificationWorker> _logger;
private readonly ITrainingService _trainingService;
private readonly IUserService _userService;
private readonly IECNService _ecnService;
private readonly ISmtpService _smtpService;
private readonly IMonInClient _monInClient;
public TrainingNotificationWorker(ILogger<TrainingNotificationWorker> 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<TrainingAssignment> 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);
}
}
}
}

View File

@ -29,7 +29,7 @@ internal class ECNServiceTests {
} }
[Test] [Test]
public async Task GetExpiringTECNsWithDbErrorShouldThrowException() { public void GetExpiringTECNsWithDbErrorShouldThrowException() {
_mockDalService.Setup(d => d.QueryAsync<ECN>(It.IsAny<string>())).ThrowsAsync(new Exception()); _mockDalService.Setup(d => d.QueryAsync<ECN>(It.IsAny<string>())).ThrowsAsync(new Exception());
_ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object); _ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object);
@ -38,7 +38,7 @@ internal class ECNServiceTests {
} }
[Test] [Test]
public async Task GetExpiredTECNsWithDbErrorShouldThrowException() { public void GetExpiredTECNsWithDbErrorShouldThrowException() {
_mockDalService.Setup(d => d.QueryAsync<ECN>(It.IsAny<string>())).ThrowsAsync(new Exception()); _mockDalService.Setup(d => d.QueryAsync<ECN>(It.IsAny<string>())).ThrowsAsync(new Exception());
_ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object); _ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object);
@ -215,7 +215,7 @@ internal class ECNServiceTests {
} }
[Test] [Test]
public async Task GetTECNNotificationUserEmailsDbErrorShouldThrowException() { public void GetTECNNotificationUserEmailsDbErrorShouldThrowException() {
_mockDalService.Setup(d => d.QueryAsync<string>(It.IsAny<string>())).Throws(new Exception()); _mockDalService.Setup(d => d.QueryAsync<string>(It.IsAny<string>())).Throws(new Exception());
_ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object); _ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object);
@ -238,4 +238,52 @@ internal class ECNServiceTests {
Assert.That(actualEmails.Count(), Is.EqualTo(2)); Assert.That(actualEmails.Count(), Is.EqualTo(2));
} }
[Test]
public void GetEcnByNumberWithInvalidNumberShouldThrowException() {
_ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object);
Assert.ThrowsAsync<ArgumentException>(async Task () => await _ecnService.GetEcnByNumber(0));
Assert.ThrowsAsync<ArgumentException>(async Task () => await _ecnService.GetEcnByNumber(-4));
}
[Test]
public void GetEcnByNumberDbErrorShouldThrowException() {
_mockDalService.Setup(d => d.QueryAsync<ECN>(It.IsAny<string>())).Throws(new Exception());
_ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object);
Assert.ThrowsAsync<Exception>(async Task () => await _ecnService.GetEcnByNumber(2));
}
[Test]
public void GetEcnByNumberEcnNotFoundShouldThrowException() {
IEnumerable<ECN> expectedEcns = new List<ECN>();
_mockDalService.Setup(d => d.QueryAsync<ECN>(It.IsAny<string>())).Returns(Task.FromResult(expectedEcns));
_ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object);
Assert.ThrowsAsync<Exception>(async Task () => await _ecnService.GetEcnByNumber(8));
}
[Test]
public async Task GetEcnByNumberShouldReturnExpectedEcn() {
IEnumerable<ECN> expectedEcns = new List<ECN>() {
new ECN() {
ECNNumber = 1,
OriginatorID = 1,
Title = "title"
}
};
_mockDalService.Setup(d => d.QueryAsync<ECN>(It.IsAny<string>())).Returns(Task.FromResult(expectedEcns));
_ecnService = new ECNService(_mockLogger.Object, _mockDalService.Object);
ECN actualEcn = await _ecnService.GetEcnByNumber(9);
Assert.That(expectedEcns.First(), Is.EqualTo(actualEcn));
}
} }

View File

@ -1,4 +1,5 @@
using FabApprovalWorkerService.Services; using FabApprovalWorkerService.Models;
using FabApprovalWorkerService.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -26,6 +27,8 @@ public class TrainingServiceTests {
public void TrainingServiceWithNullDalServiceShouldThrowException() { public void TrainingServiceWithNullDalServiceShouldThrowException() {
Assert.Throws<ArgumentNullException>(() => new TrainingService(_mockLogger.Object, null)); Assert.Throws<ArgumentNullException>(() => new TrainingService(_mockLogger.Object, null));
} }
<<<<<<< Updated upstream
=======
[Test] [Test]
public void DeleteDocAssignmentWithInvalidIdShouldThrowException() { public void DeleteDocAssignmentWithInvalidIdShouldThrowException() {
@ -169,4 +172,71 @@ public class TrainingServiceTests {
_mockDalService.Verify(d => d.ExecuteAsync(It.IsAny<string>())); _mockDalService.Verify(d => d.ExecuteAsync(It.IsAny<string>()));
} }
[Test]
public async Task GetActiveTrainingAssignmentsShouldReturnExpectedAssignments() {
IEnumerable<TrainingAssignment> expectedAssignments = new List<TrainingAssignment>() {
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<TrainingAssignment>(It.IsAny<string>())).Returns(Task.FromResult(expectedAssignments));
_trainingService = new TrainingService(_mockLogger.Object, _mockDalService.Object);
IEnumerable<TrainingAssignment> actualAssignments = await _trainingService.GetActiveTrainingAssignments();
Assert.That(actualAssignments.Count() == expectedAssignments.Count());
}
[Test]
public void GetActiveTrainingAssignmentsWithDbErrorShouldThrowException() {
_mockDalService.Setup(d => d.QueryAsync<TrainingAssignment>(It.IsAny<string>())).Throws(new Exception());
_trainingService = new TrainingService(_mockLogger.Object, _mockDalService.Object);
Assert.ThrowsAsync<Exception>(async Task() => await _trainingService.GetActiveTrainingAssignments());
}
[Test]
public void UpdateTrainingAssignmentLastNotificationWithInvalidIdShouldThrowException() {
_trainingService = new TrainingService(_mockLogger.Object, _mockDalService.Object);
Assert.ThrowsAsync<ArgumentException>(async Task () => await _trainingService.UpdateTrainingAssignmentLastNotification(-1));
}
[Test]
public void UpdateTrainingAssignmentLastNotificationDbErrorShouldThrowException() {
_mockDalService.Setup(d => d.ExecuteAsync(It.IsAny<string>())).Throws(new Exception());
_trainingService = new TrainingService(_mockLogger.Object, _mockDalService.Object);
Assert.ThrowsAsync<Exception>(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<string>()), Times.Once());
}
>>>>>>> Stashed changes
} }

View File

@ -2,6 +2,7 @@ using FabApprovalWorkerService.Models;
using FabApprovalWorkerService.Services; using FabApprovalWorkerService.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Identity.Client;
using Moq; using Moq;
@ -458,4 +459,56 @@ internal class UserServiceTests {
Assert.That(actualEmail, Is.EqualTo(emailsFromDb.First())); Assert.That(actualEmail, Is.EqualTo(emailsFromDb.First()));
} }
[Test]
public void GetUserByIdWithInvalidIdShouldThrowException() {
_userService = new UserService(MOCK_LOGGER, _mockDalService.Object);
Assert.ThrowsAsync<ArgumentException>(async Task () => await _userService.GetUserById(-4));
Assert.ThrowsAsync<ArgumentException>(async Task () => await _userService.GetUserById(0));
}
[Test]
public void GetUserByIdDbErrorShouldThrowException() {
_mockDalService.Setup(d => d.QueryAsync<User>(It.IsAny<string>())).Throws(new Exception());
_userService = new UserService(MOCK_LOGGER, _mockDalService.Object);
Assert.ThrowsAsync<Exception>(async Task () => await _userService.GetUserById(5));
}
[Test]
public void GetUserByIdNoUserFoundShouldThrowException() {
IEnumerable<User> emptyUsers = new List<User>();
_mockDalService.Setup(d => d.QueryAsync<User>(It.IsAny<string>())).Returns(Task.FromResult(emptyUsers));
Assert.ThrowsAsync<Exception>(async Task() => await _userService.GetUserById(5));
}
[Test]
public async Task GetUserByIdShouldReturnExpectedUser() {
IEnumerable<User> expectedUsers = new List<User>() {
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<User>(It.IsAny<string>())).Returns(Task.FromResult(expectedUsers));
_userService = new UserService(MOCK_LOGGER, _mockDalService.Object);
User actualUser = await _userService.GetUserById(4);
Assert.That(expectedUsers.First(), Is.EqualTo(actualUser));
}
} }