diff --git a/Fab2ApprovalSystem.sln b/Fab2ApprovalSystem.sln index e5b15ec..95aaedb 100644 --- a/Fab2ApprovalSystem.sln +++ b/Fab2ApprovalSystem.sln @@ -14,6 +14,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MesaFabApproval.Client", "M {2C16014D-B04E-46AF-AB4C-D2691D44A339} = {2C16014D-B04E-46AF-AB4C-D2691D44A339} EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MesaFabApproval.API.Test", "MesaFabApproval.Test\MesaFabApproval.API.Test.csproj", "{D03AB305-BA29-4EB1-AC66-ABBF76FBF5C1}" + ProjectSection(ProjectDependencies) = postProject + {2C16014D-B04E-46AF-AB4C-D2691D44A339} = {2C16014D-B04E-46AF-AB4C-D2691D44A339} + {34D52F44-A81F-4247-8180-16E204824A07} = {34D52F44-A81F-4247-8180-16E204824A07} + {852E528D-015A-43B5-999D-F281E3359E5E} = {852E528D-015A-43B5-999D-F281E3359E5E} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -36,6 +43,10 @@ Global {34D52F44-A81F-4247-8180-16E204824A07}.Debug|Any CPU.Build.0 = Debug|Any CPU {34D52F44-A81F-4247-8180-16E204824A07}.Release|Any CPU.ActiveCfg = Release|Any CPU {34D52F44-A81F-4247-8180-16E204824A07}.Release|Any CPU.Build.0 = Release|Any CPU + {D03AB305-BA29-4EB1-AC66-ABBF76FBF5C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D03AB305-BA29-4EB1-AC66-ABBF76FBF5C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D03AB305-BA29-4EB1-AC66-ABBF76FBF5C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D03AB305-BA29-4EB1-AC66-ABBF76FBF5C1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MesaFabApproval.API/Controllers/PCRBController.cs b/MesaFabApproval.API/Controllers/PCRBController.cs index 79216eb..7d125a6 100644 --- a/MesaFabApproval.API/Controllers/PCRBController.cs +++ b/MesaFabApproval.API/Controllers/PCRBController.cs @@ -1,4 +1,5 @@ using MesaFabApproval.API.Services; +using MesaFabApproval.API.Utilities; using MesaFabApproval.Shared.Models; using MesaFabApproval.Shared.Services; @@ -13,12 +14,12 @@ namespace MesaFabApproval.API.Controllers; public class PCRBController : ControllerBase { private readonly ILogger _logger; private readonly IPCRBService _pcrbService; - private readonly IMonInWorkerClient _monInClient; + private readonly IMonInUtils _monInUtils; - public PCRBController(ILogger logger, IPCRBService pcrbService, IMonInWorkerClient monInClient) { + public PCRBController(ILogger logger, IPCRBService pcrbService, IMonInUtils monInUtils) { _logger = logger ?? throw new ArgumentNullException("ILogger not injected"); _pcrbService = pcrbService ?? throw new ArgumentNullException("IPCRBService not injected"); - _monInClient = monInClient ?? throw new ArgumentNullException("IMonInWorkerClient not injected"); + _monInUtils = monInUtils ?? throw new ArgumentNullException("IMonInUtils not injected"); } [HttpPost] @@ -49,17 +50,8 @@ public class PCRBController : ControllerBase { string metricName = "CreateNewPCRB"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -84,14 +76,8 @@ public class PCRBController : ControllerBase { string metricName = "GetAllPCRBs"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - - if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + + _monInUtils.PostMetrics(metricName, millisecondsDiff, false, isInternalError); } } @@ -123,17 +109,8 @@ public class PCRBController : ControllerBase { string metricName = "GetPCRBbyTitle"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -165,17 +142,8 @@ public class PCRBController : ControllerBase { string metricName = "GetPCRBbyPlanNumber"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -207,17 +175,8 @@ public class PCRBController : ControllerBase { string metricName = "UpdatePCRB"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -249,17 +208,8 @@ public class PCRBController : ControllerBase { string metricName = "DeletePCRB"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -294,17 +244,8 @@ public class PCRBController : ControllerBase { string metricName = "UploadPCRBAttachment"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -336,17 +277,8 @@ public class PCRBController : ControllerBase { string metricName = "GetPCRBAttachments"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -391,17 +323,8 @@ public class PCRBController : ControllerBase { string metricName = "GetPCRBAttachmentFile"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -433,17 +356,8 @@ public class PCRBController : ControllerBase { string metricName = "UpdatePCRBAttachment"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -475,17 +389,8 @@ public class PCRBController : ControllerBase { string metricName = "DeletePCRBAttachment"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -517,17 +422,8 @@ public class PCRBController : ControllerBase { string metricName = "CreatePCRBActionItem"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -559,17 +455,8 @@ public class PCRBController : ControllerBase { string metricName = "UpdatePCRBActionItem"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -601,17 +488,8 @@ public class PCRBController : ControllerBase { string metricName = "DeletePCRBActionItem"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -643,17 +521,8 @@ public class PCRBController : ControllerBase { string metricName = "GetPCRBActionItems"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -685,17 +554,8 @@ public class PCRBController : ControllerBase { string metricName = "CreatePCR3Document"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -727,17 +587,8 @@ public class PCRBController : ControllerBase { string metricName = "UpdatePCR3Document"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -769,17 +620,8 @@ public class PCRBController : ControllerBase { string metricName = "GetPCR3Documents"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -811,17 +653,8 @@ public class PCRBController : ControllerBase { string metricName = "CreatePCRBAttendee"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -853,17 +686,8 @@ public class PCRBController : ControllerBase { string metricName = "UpdatePCRBAttendee"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -895,17 +719,8 @@ public class PCRBController : ControllerBase { string metricName = "DeletePCRBAttendee"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -937,17 +752,8 @@ public class PCRBController : ControllerBase { string metricName = "GetPCRBAttendees"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -979,17 +785,8 @@ public class PCRBController : ControllerBase { string metricName = "NotifyNewPCRBApprovers"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -1023,17 +820,8 @@ public class PCRBController : ControllerBase { string metricName = "NotifyPCRBApprovers"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -1067,17 +855,8 @@ public class PCRBController : ControllerBase { string metricName = "NotifyPCRBOriginator"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } @@ -1111,17 +890,140 @@ public class PCRBController : ControllerBase { string metricName = "NotifyPCRBResponsiblePerson"; DateTime end = DateTime.Now; double millisecondsDiff = (end - start).TotalMilliseconds; - _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); - if (isArgumentError) { - _logger.LogWarning(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Ok); - } else if (isInternalError) { - _logger.LogError(errorMessage); - _monInClient.PostStatus(metricName, StatusValue.Critical); - } else { - _monInClient.PostStatus(metricName, StatusValue.Ok); - } + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); + } + } + + [HttpPost] + [Route("pcrb/followUp")] + public async Task CreateFollowUp(PCRBFollowUp followUp) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to create follow up"); + + if (followUp is null) throw new ArgumentNullException("follow up cannot be null"); + + await _pcrbService.CreateFollowUp(followUp); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Unable to create follow up, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "CreatePCRBFollowUp"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); + } + } + + [HttpGet] + [Route("pcrb/followUps")] + public async Task GetFollowUpsByPlanNumber(int planNumber, bool bypassCache) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation($"Attempting to get attendees for plan# {planNumber}"); + + if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#"); + + List attendees = (await _pcrbService.GetFollowUpsByPlanNumber(planNumber, bypassCache)).ToList(); + + return Ok(attendees); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot get follow ups for plan# {planNumber}, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "GetPCRBFollowUps"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); + } + } + + [HttpPut] + [Route("pcrb/followUp")] + public async Task UpdateFollowUp(PCRBFollowUp followUp) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to update follow up"); + + if (followUp is null) throw new ArgumentNullException("follow up cannot be null"); + + await _pcrbService.UpdateFollowUp(followUp); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Unable to update follow up, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "UpdatePCRBFollowUp"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); + } + } + + [HttpDelete] + [Route("pcrb/followUp")] + public async Task DeleteFollowUp(int id) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to delete follow up"); + + if (id <= 0) throw new ArgumentException($"{id} is not a valid PCRB follow up ID"); + + await _pcrbService.DeleteFollowUp(id); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Unable to delete follow up, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "DeletePCRBFollowUp"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + + _monInUtils.PostMetrics(metricName, millisecondsDiff, isArgumentError, isInternalError); } } } \ No newline at end of file diff --git a/MesaFabApproval.API/Program.cs b/MesaFabApproval.API/Program.cs index 0246239..c7bfb67 100644 --- a/MesaFabApproval.API/Program.cs +++ b/MesaFabApproval.API/Program.cs @@ -6,6 +6,7 @@ using dotenv.net; using MesaFabApproval.API.Clients; using MesaFabApproval.API.Services; +using MesaFabApproval.API.Utilities; using MesaFabApproval.Models; using MesaFabApproval.Shared.Services; @@ -94,6 +95,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -101,7 +103,6 @@ builder.Services.AddScoped(); builder.Services.AddControllers(); -#if DEBUG builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { @@ -128,7 +129,6 @@ builder.Services.AddSwaggerGen(c => { } }); }); -#endif WebApplication app = builder.Build(); diff --git a/MesaFabApproval.API/Services/DalService.cs b/MesaFabApproval.API/Services/DalService.cs index de6adfb..de5a62d 100644 --- a/MesaFabApproval.API/Services/DalService.cs +++ b/MesaFabApproval.API/Services/DalService.cs @@ -1,4 +1,5 @@ using System.Data; +using System.Text; using Dapper; @@ -6,7 +7,9 @@ namespace MesaFabApproval.API.Services; public interface IDalService { Task> QueryAsync(string sql); + Task> QueryAsync(string sql, object paramaters); Task ExecuteAsync(string sql); + Task ExecuteAsync(string sql, T paramaters); } public class DalService : IDalService { @@ -55,6 +58,45 @@ public class DalService : IDalService { return result; } + public async Task> QueryAsync(string sql, object parameters) { + if (sql is null) throw new ArgumentNullException("sql cannot be null"); + if (parameters is null) throw new ArgumentNullException("parameters cannot be null"); + + StringBuilder logBuilder = new(); + + int remainingRetries = RETRIES; + bool queryWasSuccessful = false; + Exception exception = null; + IEnumerable result = new List(); + while (!queryWasSuccessful && remainingRetries > 0) { + int backoffSeconds = (RETRIES - remainingRetries--) * BACKOFF_SECONDS_INTERVAL; + Task.Delay(backoffSeconds * 1000).Wait(); + + try { + logBuilder.Clear(); + logBuilder.Append($"Attempting to perform query with {sql} "); + logBuilder.Append($"and parameters {parameters.ToString()}. "); + logBuilder.Append($"Remaining retries: {remainingRetries}"); + _logger.LogInformation(logBuilder.ToString()); + + using (IDbConnection conn = _dbConnectionService.GetConnection()) { + result = await conn.QueryAsync(sql, parameters); + } + + queryWasSuccessful = true; + } catch (Exception ex) { + _logger.LogError($"An exception occurred while attempting to perform a query. Exception: {ex.Message}"); + exception = ex; + } + } + + if (!queryWasSuccessful && exception is not null) { + throw exception; + } + + return result; + } + public async Task ExecuteAsync(string sql) { if (sql is null) throw new ArgumentNullException("sql cannot be null"); @@ -86,4 +128,36 @@ public class DalService : IDalService { return rowsAffected; } + + public async Task ExecuteAsync(string sql, T parameters) { + if (sql is null) throw new ArgumentNullException("sql cannot be null"); + + int remainingRetries = RETRIES; + bool queryWasSuccessful = false; + Exception exception = null; + int rowsAffected = 0; + while (!queryWasSuccessful && remainingRetries > 0) { + int backoffSeconds = (RETRIES - remainingRetries--) * BACKOFF_SECONDS_INTERVAL; + Task.Delay(backoffSeconds * 1000).Wait(); + + try { + _logger.LogInformation($"Attempting to execute {sql} with parameters. Remaining retries: {remainingRetries}"); + + using (IDbConnection conn = _dbConnectionService.GetConnection()) { + rowsAffected = await conn.ExecuteAsync(sql, parameters); + } + + queryWasSuccessful = true; + } catch (Exception ex) { + _logger.LogError($"An exception occurred while attempting to execute a query. Exception: {ex.Message}"); + exception = ex; + } + } + + if (!queryWasSuccessful && exception is not null) { + throw exception; + } + + return rowsAffected; + } } \ No newline at end of file diff --git a/MesaFabApproval.API/Services/PCRBService.cs b/MesaFabApproval.API/Services/PCRBService.cs index 6f23b07..6bfe1d5 100644 --- a/MesaFabApproval.API/Services/PCRBService.cs +++ b/MesaFabApproval.API/Services/PCRBService.cs @@ -37,6 +37,10 @@ public interface IPCRBService { Task NotifyApprovers(PCRBNotification notification); Task NotifyOriginator(PCRBNotification notification); Task NotifyResponsiblePerson(PCRBActionItemNotification notification); + Task CreateFollowUp(PCRBFollowUp followUp); + Task> GetFollowUpsByPlanNumber(int planNumber, bool bypassCache); + Task UpdateFollowUp(PCRBFollowUp followUp); + Task DeleteFollowUp(int id); } public class PCRBService : IPCRBService { @@ -755,6 +759,90 @@ public class PCRBService : IPCRBService { } } + public async Task CreateFollowUp(PCRBFollowUp followUp) { + try { + _logger.LogInformation("Attempting to create PCRB follow up"); + + if (followUp is null) throw new ArgumentNullException("follow up cannot be null"); + + StringBuilder queryBuilder = new(); + queryBuilder.Append("insert into CCPCRBFollowUp (PlanNumber, Step, FollowUpDate, CompletedDate) "); + queryBuilder.Append("values (@PlanNumber, @Step, @FollowUpDate, @CompletedDate)"); + + int rowsReturned = await _dalService.ExecuteAsync(queryBuilder.ToString(), followUp); + + if (rowsReturned <= 0) throw new Exception("unable to insert new follow up in the database"); + } catch (Exception ex) { + _logger.LogError($"Unable to create new follow up, because {ex.Message}"); + throw; + } + } + + public async Task> GetFollowUpsByPlanNumber(int planNumber, bool bypassCache) { + try { + _logger.LogInformation($"Attempting to fetch follow ups for PCRB {planNumber}"); + + if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#"); + + IEnumerable? followUps = new List(); + + if (!bypassCache) + followUps = _cache.Get>($"pcrbFollowUps{planNumber}"); + + if (followUps is null || followUps.Count() == 0) { + string sql = "select * from CCPCRBFollowUp where PlanNumber=@PlanNumber"; + + followUps = await _dalService.QueryAsync(sql, new { PlanNumber = planNumber }); + + if (followUps is not null) + _cache.Set($"pcrbFollowUps{planNumber}", followUps, DateTimeOffset.Now.AddMinutes(15)); + } + + return followUps ?? new List(); + } catch (Exception ex) { + _logger.LogError($"Unable to fetch follow ups for PCRB {planNumber}, because {ex.Message}"); + throw; + } + } + + public async Task UpdateFollowUp(PCRBFollowUp followUp) { + try { + _logger.LogInformation("Attempting to update a follow up"); + + if (followUp is null) + throw new ArgumentNullException("follow up cannot be null"); + + StringBuilder queryBuilder = new(); + queryBuilder.Append("update CCPCRBFollowUp set Step=@Step, FollowUpDate=@FollowUpDate, IsComplete=@IsComplete, "); + queryBuilder.Append("IsDeleted=@IsDeleted, CompletedDate=@CompletedDate, Comments=@Comments "); + queryBuilder.Append("where ID=@ID"); + + int rowsAffected = await _dalService.ExecuteAsync(queryBuilder.ToString(), followUp); + + if (rowsAffected <= 0) throw new Exception("update failed in database"); + } catch (Exception ex) { + _logger.LogError($"Unable to update follow up, because {ex.Message}"); + throw; + } + } + + public async Task DeleteFollowUp(int id) { + try { + _logger.LogInformation($"Attempting to delete follow up {id}"); + + if (id <= 0) throw new ArgumentException($"{id} is not a valid follow up ID"); + + string sql = "delete from CCPCRBFollowUp where ID=@ID"; + + int rowsAffected = await _dalService.ExecuteAsync(sql, new { ID = id }); + + if (rowsAffected <= 0) throw new Exception("delete operation failed in database"); + } catch (Exception ex) { + _logger.LogError($"Unable to delete follow up {id}, because {ex.Message}"); + throw; + } + } + private async Task SaveAttachmentInDb(IFormFile file, PCRBAttachment attachment) { try { _logger.LogInformation($"Attempting to save attachment to database"); diff --git a/MesaFabApproval.API/Utilities/MonInUtils.cs b/MesaFabApproval.API/Utilities/MonInUtils.cs new file mode 100644 index 0000000..26adf93 --- /dev/null +++ b/MesaFabApproval.API/Utilities/MonInUtils.cs @@ -0,0 +1,48 @@ +using MesaFabApproval; +using MesaFabApproval.API; +using MesaFabApproval.API.Services; +using MesaFabApproval.API.Utilities; +using MesaFabApproval.Shared.Models; +using MesaFabApproval.Shared.Services; + +namespace MesaFabApproval.API.Utilities; + +public interface IMonInUtils { + public void PostMetrics(string metricName, + double latency, + bool isArgumentError, + bool isInternalError); +} + +public class MonInUtils : IMonInUtils { + private readonly IMonInWorkerClient _monInClient; + private readonly ILogger _logger; + + public MonInUtils(IMonInWorkerClient monInClient, ILogger logger) { + _monInClient = monInClient ?? + throw new ArgumentNullException("IMonInWorkerClient not injected"); + _logger = logger ?? + throw new ArgumentNullException("ILogger not injected"); + } + + public void PostMetrics(string metricName, + double latency, + bool isArgumentError, + bool isInternalError) { + try { + _logger.LogInformation("Attempting to post metrics to MonIn"); + + _monInClient.PostAverage(metricName + "Latency", latency); + + if (isArgumentError) { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } catch (Exception ex) { + _logger.LogError($"Unable to post metrics to MonIn, because {ex.Message}"); + } + } +} diff --git a/MesaFabApproval.API/pipeline.yml b/MesaFabApproval.API/pipeline.yml index e2b8fe8..0f90121 100644 --- a/MesaFabApproval.API/pipeline.yml +++ b/MesaFabApproval.API/pipeline.yml @@ -30,6 +30,14 @@ stages: configuration: $(BuildConfiguration) projects: MesaFabApproval.API + - task: DotNetCoreCLI@2 + displayName: "Test" + inputs: + command: "test" + configuration: $(BuildConfiguration) + publishTestResults: true + projects: MesaFabApproval.API.Test + - task: DotNetCoreCLI@2 displayName: "Publish" inputs: @@ -66,6 +74,14 @@ stages: configuration: $(BuildConfiguration) projects: MesaFabApproval.API + - task: DotNetCoreCLI@2 + displayName: "Test" + inputs: + command: "test" + configuration: $(BuildConfiguration) + publishTestResults: true + projects: MesaFabApproval.API.Test + - task: DotNetCoreCLI@2 displayName: "Publish" inputs: diff --git a/MesaFabApproval.Shared/Models/PCRBFollowUp.cs b/MesaFabApproval.Shared/Models/PCRBFollowUp.cs new file mode 100644 index 0000000..e90db8c --- /dev/null +++ b/MesaFabApproval.Shared/Models/PCRBFollowUp.cs @@ -0,0 +1,14 @@ +using MesaFabApproval.Shared.Utilities; + +namespace MesaFabApproval.Shared.Models; + +public class PCRBFollowUp { + public int ID { get; set; } + public required int PlanNumber { get; set; } + public required int Step { get; set; } + public required DateTime FollowUpDate { get; set; } + public bool IsComplete { get; set; } = false; + public bool IsDeleted { get; set; } = false; + public DateTime CompletedDate { get; set; } = DateTimeUtilities.MAX_DT; + public string Comments { get; set; } = string.Empty; +} diff --git a/MesaFabApproval.Test/MesaFabApproval.API.Test.csproj b/MesaFabApproval.Test/MesaFabApproval.API.Test.csproj new file mode 100644 index 0000000..7250603 --- /dev/null +++ b/MesaFabApproval.Test/MesaFabApproval.API.Test.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MesaFabApproval.Test/MonInUtilsTests.cs b/MesaFabApproval.Test/MonInUtilsTests.cs new file mode 100644 index 0000000..061ddaf --- /dev/null +++ b/MesaFabApproval.Test/MonInUtilsTests.cs @@ -0,0 +1,63 @@ +using Moq; +using Microsoft.Extensions.Logging; +using MesaFabApproval.API.Utilities; +using MesaFabApproval.Shared.Models; +using MesaFabApproval.Shared.Services; + +namespace NICAIntegrationServiceTests.Util; + +public class MonInUtilsTests { + private readonly Mock _mockMonInClient; + private readonly Mock> _mockLogger; + private readonly MonInUtils _monInUtils; + + public MonInUtilsTests() { + _mockMonInClient = new Mock(); + _mockMonInClient + .Setup(client => client.PostAverage(It.IsAny(), It.IsAny())) + .Verifiable(); + + _mockLogger = new Mock>(); + + _monInUtils = new MonInUtils(_mockMonInClient.Object, _mockLogger.Object); + } + + [Fact] + public void PostMetrics_ShouldPostAverageAndStatusOk_WhenNoErrors() { + string metricName = "TestMetric"; + double latency = 100; + bool isArgumentError = false; + bool isInternalError = false; + + _monInUtils.PostMetrics(metricName, latency, isArgumentError, isInternalError); + + _mockMonInClient.Verify(client => client.PostAverage(metricName + "Latency", latency), Times.Once); + _mockMonInClient.Verify(client => client.PostStatus(metricName, StatusValue.Ok), Times.Once); + } + + [Fact] + public void PostMetrics_ShouldPostAverageAndStatusOk_WhenArgumentError() { + string metricName = "TestMetric"; + double latency = 100; + bool isArgumentError = true; + bool isInternalError = false; + + _monInUtils.PostMetrics(metricName, latency, isArgumentError, isInternalError); + + _mockMonInClient.Verify(client => client.PostAverage(metricName + "Latency", latency), Times.Once); + _mockMonInClient.Verify(client => client.PostStatus(metricName, StatusValue.Ok), Times.Once); + } + + [Fact] + public void PostMetrics_ShouldPostAverageAndStatusCritical_WhenInternalError() { + string metricName = "TestMetric"; + double latency = 100; + bool isArgumentError = false; + bool isInternalError = true; + + _monInUtils.PostMetrics(metricName, latency, isArgumentError, isInternalError); + + _mockMonInClient.Verify(client => client.PostAverage(metricName + "Latency", latency), Times.Once); + _mockMonInClient.Verify(client => client.PostStatus(metricName, StatusValue.Critical), Times.Once); + } +} \ No newline at end of file diff --git a/MesaFabApproval.Test/PCRBServiceTests.cs b/MesaFabApproval.Test/PCRBServiceTests.cs new file mode 100644 index 0000000..61c483c --- /dev/null +++ b/MesaFabApproval.Test/PCRBServiceTests.cs @@ -0,0 +1,243 @@ +using MesaFabApproval.API.Services; +using MesaFabApproval.Models; +using MesaFabApproval.Shared.Models; + +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; + +using Moq; + +namespace MesaFabApproval.Tests.Services; + +public static class MockMemoryCacheService { + public static Mock GetMemoryCache(object expectedValue) { + Mock mockMemoryCache = new Mock(); + mockMemoryCache + .Setup(x => x.TryGetValue(It.IsAny(), out expectedValue)) + .Returns(true); + mockMemoryCache + .Setup(x => x.CreateEntry(It.IsAny())) + .Returns(Mock.Of()); + return mockMemoryCache; + } +} + +public class PCRBServiceTests { + private readonly Mock> _loggerMock; + private readonly Mock _dalServiceMock; + private readonly Mock _cacheMock; + private readonly Mock _userServiceMock; + private readonly Mock _approvalServiceMock; + private readonly Mock _smtpServiceMock; + private readonly PCRBService _pcrbService; + + private static IEnumerable FOLLOW_UPS = new List() { + new PCRBFollowUp { ID = 1, PlanNumber = 1, Step = 1, FollowUpDate = DateTime.Now } + }; + + public PCRBServiceTests() { + _loggerMock = new Mock>(); + _dalServiceMock = new Mock(); + _userServiceMock = new Mock(); + _approvalServiceMock = new Mock(); + _smtpServiceMock = new Mock(); + _cacheMock = MockMemoryCacheService.GetMemoryCache(FOLLOW_UPS); + + var appSettings = new AppSettings( + Company: "Infineon", + DbConnectionString: "connectionString", + JwtAudience: "audience", + JwtIssuer: "issuer", + JwtKey: "key", + MrbAttachmentPath: "mrbAttachmentPath", + PcrbAttachmentPath: "pcrbAttachmentPath", + ShouldSendEmail: false, + SiteBaseUrl: "siteBaseUrl", + WorkingDirectoryName: "workingDirectoryName" + ); + + _pcrbService = new PCRBService( + _loggerMock.Object, + _dalServiceMock.Object, + _cacheMock.Object, + _userServiceMock.Object, + _approvalServiceMock.Object, + _smtpServiceMock.Object, + appSettings + ); + } + + [Fact] + public async Task CreateFollowUp_WithValidParam_ShouldCreateFollowUp() { + var followUp = new PCRBFollowUp { + PlanNumber = 1, + Step = 1, + FollowUpDate = DateTime.Now + }; + + _dalServiceMock.Setup(d => d.ExecuteAsync(It.IsAny(), followUp)) + .ReturnsAsync(1); + + await _pcrbService.CreateFollowUp(followUp); + + _dalServiceMock.Verify(d => d.ExecuteAsync(It.IsAny(), followUp), Times.Once); + } + + [Fact] + public async Task CreateFollowUp_WithNullParam_ShouldThrowException() { + await Assert.ThrowsAsync(() => _pcrbService.CreateFollowUp(null)); + } + + [Fact] + public async Task CreateFollowUp_WithDatabaseFailure_ShouldThrowException() { + var followUp = new PCRBFollowUp { + PlanNumber = 1, + Step = 1, + FollowUpDate = DateTime.Now + }; + + _dalServiceMock.Setup(d => d.ExecuteAsync(It.IsAny(), followUp)) + .ReturnsAsync(0); + + await Assert.ThrowsAsync(() => _pcrbService.CreateFollowUp(followUp)); + } + + [Fact] + public async Task CreateFollowUp_WithDatabaseException_ShouldThrowException() { + var followUp = new PCRBFollowUp { + PlanNumber = 1, + Step = 1, + FollowUpDate = DateTime.Now + }; + + _dalServiceMock.Setup(d => d.ExecuteAsync(It.IsAny(), followUp)) + .Throws(); + + await Assert.ThrowsAsync(() => _pcrbService.CreateFollowUp(followUp)); + } + + [Fact] + public async Task GetFollowUpsByPlanNumber_WithCacheBypass_ShouldReturnFollowUps() { + int planNumber = 1; + + _dalServiceMock.Setup(d => d.QueryAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(FOLLOW_UPS); + + IEnumerable result = await _pcrbService.GetFollowUpsByPlanNumber(planNumber, true); + + Assert.NotNull(result); + Assert.Single(result); + Assert.Equal(FOLLOW_UPS, result); + _dalServiceMock.Verify(d => d.QueryAsync(It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public async Task GetFollowUpsByPlanNumber_WithCacheBypass_AndDatabaseException_ShouldThrowException() { + int planNumber = 1; + + _dalServiceMock.Setup(d => d.QueryAsync(It.IsAny(), It.IsAny())) + .Throws(); + + await Assert.ThrowsAsync(() => _pcrbService.GetFollowUpsByPlanNumber(planNumber, true)); + } + + [Fact] + public async Task GetFollowUpsByPlanNumber_WithoutCacheBypass_ShouldReturnFollowUps() { + int planNumber = 1; + + IEnumerable result = await _pcrbService.GetFollowUpsByPlanNumber(planNumber, false); + + Assert.NotNull(result); + Assert.Single(result); + Assert.Equal(FOLLOW_UPS, result); + } + + [Fact] + public async Task UpdateFollowUp_WithValidParam_ShouldUpdateFollowUp() { + var followUp = new PCRBFollowUp { + ID = 1, + PlanNumber = 1, + Step = 1, + FollowUpDate = DateTime.Now + }; + + _dalServiceMock.Setup(d => d.ExecuteAsync(It.IsAny(), followUp)) + .ReturnsAsync(1); + + await _pcrbService.UpdateFollowUp(followUp); + + _dalServiceMock.Verify(d => d.ExecuteAsync(It.IsAny(), followUp), Times.Once); + } + + [Fact] + public async Task UpdateFollowUp_WithNullParam_ShouldThrowException() { + await Assert.ThrowsAsync(() => _pcrbService.UpdateFollowUp(null)); + } + + [Fact] + public async Task UpdateFollowUp_WithDatabaseFailure_ShouldThrowException() { + var followUp = new PCRBFollowUp { + ID = 1, + PlanNumber = 1, + Step = 1, + FollowUpDate = DateTime.Now + }; + + _dalServiceMock.Setup(d => d.ExecuteAsync(It.IsAny(), followUp)) + .ReturnsAsync(0); + + await Assert.ThrowsAsync(() => _pcrbService.UpdateFollowUp(followUp)); + } + + [Fact] + public async Task UpdateFollowUp_WithDatabaseException_ShouldThrowException() { + var followUp = new PCRBFollowUp { + ID = 1, + PlanNumber = 1, + Step = 1, + FollowUpDate = DateTime.Now + }; + + _dalServiceMock.Setup(d => d.ExecuteAsync(It.IsAny(), followUp)) + .Throws(); + + await Assert.ThrowsAsync(() => _pcrbService.UpdateFollowUp(followUp)); + } + + [Fact] + public async Task DeleteFollowUp_WithValidId_ShouldDeleteFollowUp() { + int followUpId = 1; + + _dalServiceMock.Setup(d => d.ExecuteAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(1); + + await _pcrbService.DeleteFollowUp(followUpId); + + _dalServiceMock.Verify(d => d.ExecuteAsync(It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public async Task DeleteFollowUp_WithInvalidId_ShouldThrowException() { + await Assert.ThrowsAsync(() => _pcrbService.DeleteFollowUp(0)); + } + + [Fact] + public async Task DeleteFollowUp_WithDatabaseFailure_ShouldThrowException() { + int followUpId = 1; + + _dalServiceMock.Setup(d => d.ExecuteAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(0); + + await Assert.ThrowsAsync(() => _pcrbService.DeleteFollowUp(followUpId)); + } + + [Fact] + public async Task DeleteFollowUp_WithDatabaseException_ShouldThrowException() { + int followUpId = 1; + + _dalServiceMock.Setup(d => d.ExecuteAsync(It.IsAny(), It.IsAny())) + .Throws(); + + await Assert.ThrowsAsync(() => _pcrbService.DeleteFollowUp(followUpId)); + } +}