diff --git a/application/CohortManager/compose.core.yaml b/application/CohortManager/compose.core.yaml index 777c85a630..7d93abc9c6 100644 --- a/application/CohortManager/compose.core.yaml +++ b/application/CohortManager/compose.core.yaml @@ -140,6 +140,9 @@ services: - ParticipantManagementUrl=http://participant-management-data-service:7994/api/ParticipantManagementDataService - ParticipantDemographicDataServiceURL=http://participant-demographic-data-service:7993/api/ParticipantDemographicDataService/ - ExceptionFunctionURL=http://create-exception:7070/api/CreateException + - ManageNemsSubscriptionUnsubscribeURL=http://manage-nems-subscription:9081/api/Unsubscribe + - ManageNemsSubscriptionSubscribeURL=http://manage-nems-subscription:9081/api/ManageNemsSubscriptionSubscribeURL + - RetrievePdsDemographicURL=http://etrieve-pds-demographic:8082/api/RetrievePdsDemographic delete-participant: container_name: delete-participant diff --git a/application/CohortManager/src/Functions/CaasIntegration/receiveCaasFile/receiveCaasFileConfig.cs b/application/CohortManager/src/Functions/CaasIntegration/receiveCaasFile/receiveCaasFileConfig.cs index 18541b8cd6..5fb6b72b93 100644 --- a/application/CohortManager/src/Functions/CaasIntegration/receiveCaasFile/receiveCaasFileConfig.cs +++ b/application/CohortManager/src/Functions/CaasIntegration/receiveCaasFile/receiveCaasFileConfig.cs @@ -19,7 +19,9 @@ public class ReceiveCaasFileConfig public string caasfolder_STORAGE { get; set; } [Required] public string inboundBlobName { get; set; } + [Required] public string ServiceBusConnectionString_client_internal { get; set; } public string GetOrchestrationStatusURL { get; set; } + [Required] public string ParticipantManagementTopic { get; set; } } diff --git a/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/Extensions/CertificateExtensions.cs b/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/Extensions/CertificateExtensions.cs index ec92c8ff92..4931d1dba6 100644 --- a/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/Extensions/CertificateExtensions.cs +++ b/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/Extensions/CertificateExtensions.cs @@ -1,9 +1,11 @@ +namespace NHS.CohortManager.DemographicServices; + using System.Security.Cryptography.X509Certificates; using Azure.Identity; using Azure.Security.KeyVault.Certificates; using Microsoft.Extensions.Logging; -namespace NHS.CohortManager.DemographicServices; + public static class CertificateExtensions { @@ -26,15 +28,16 @@ public static async Task LoadNemsCertificateAsync(this ManageN var certResult = await certClient.DownloadCertificateAsync(config.NemsKeyName); return certResult.Value; } - + if (!string.IsNullOrEmpty(config.NemsLocalCertPath)) { logger.LogInformation("Loading NEMS certificate from local file"); return !string.IsNullOrEmpty(config.NemsLocalCertPassword) ? new X509Certificate2(config.NemsLocalCertPath, config.NemsLocalCertPassword) : new X509Certificate2(config.NemsLocalCertPath); + } - + throw new InvalidOperationException("No certificate configuration found. Please configure either KeyVaultConnectionString or NemsLocalCertPath."); } -} \ No newline at end of file +} diff --git a/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs b/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs index 7fd13447a2..852c375458 100644 --- a/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs +++ b/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs @@ -83,14 +83,6 @@ public class ManageNemsSubscriptionConfig /// public bool NemsBypassServerCertificateValidation { get; set; } = false; - /// - /// Default event types to subscribe to - /// - public string[] NemsDefaultEventTypes { get; set; } = new[] - { - "pds-record-change-1" - }; - /// /// HTTP client timeout in seconds for NEMS API requests /// Default: 300 seconds (5 minutes) @@ -101,5 +93,9 @@ public class ManageNemsSubscriptionConfig /// Custom validation to ensure either KeyVault or local cert is configured /// public bool IsValid => !string.IsNullOrEmpty(KeyVaultConnectionString) || !string.IsNullOrEmpty(NemsLocalCertPath); + /// + /// Bool to set the function to be in stubbed mode. Simulated responses from NEMS + /// + public bool IsStubbed { get; set; } = false; } diff --git a/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/Program.cs b/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/Program.cs index 8c77185490..e70012f8f8 100644 --- a/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/Program.cs +++ b/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/Program.cs @@ -6,28 +6,37 @@ using NHS.CohortManager.DemographicServices; using DataServices.Database; using Microsoft.Extensions.Logging; +using System.Security.Cryptography.X509Certificates; var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); var logger = loggerFactory.CreateLogger("Program"); var host = new HostBuilder(); + +logger.LogInformation("Application Has Started"); // Load configuration host.AddConfiguration(out ManageNemsSubscriptionConfig config); var nemsConfig = config; +X509Certificate2 nemsCertificate; + + // Load NEMS certificate up-front and inject into DI -var nemsCertificate = await nemsConfig.LoadNemsCertificateAsync(logger); + +nemsCertificate = await nemsConfig.LoadNemsCertificateAsync(logger); + host.ConfigureFunctionsWebApplication(); host.AddHttpClient() - .AddNemsHttpClient() + .AddNemsHttpClient(nemsConfig.IsStubbed) .ConfigureServices(services => { // Register NEMS certificate services.AddSingleton(nemsCertificate); + // Register NEMS subscription manager services.AddScoped(); diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantDTO.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantDTO.cs new file mode 100644 index 0000000000..57071a994b --- /dev/null +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantDTO.cs @@ -0,0 +1,8 @@ +namespace NHS.CohortManager.ParticipantManagementService; + +public class BlockParticipantDto +{ + public required long NhsNumber { get; set; } + public required string DateOfBirth { get; set; } + public required string FamilyName { get; set; } +} diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs new file mode 100644 index 0000000000..e469f46fe9 --- /dev/null +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -0,0 +1,259 @@ +namespace NHS.CohortManager.ParticipantManagementService; + +using System; +using System.Globalization; +using System.Text.Json; +using Common; +using DataServices.Client; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Model; + +public class BlockParticipantHandler : IBlockParticipantHandler +{ + private readonly ILogger _logger; + private readonly IDataServiceClient _participantManagementDataService; + private readonly IDataServiceClient _participantDemographicDataService; + private readonly IHttpClientFunction _httpClient; + private readonly UpdateBlockedFlagConfig _config; + public BlockParticipantHandler(ILogger logger, + IDataServiceClient participantManagementDataService, + IDataServiceClient participantDemographicDataService, + IHttpClientFunction httpClient, + IOptions config + ) + { + _logger = logger; + _participantManagementDataService = participantManagementDataService; + _participantDemographicDataService = participantDemographicDataService; + _httpClient = httpClient; + _config = config.Value; + } + + public async Task BlockParticipant(BlockParticipantDto blockParticipantRequest) + { + if (!ValidationHelper.ValidateNHSNumber(blockParticipantRequest.NhsNumber.ToString())) + { + _logger.LogWarning("Participant had an invalid NHS Number and cannot be blocked"); + return new BlockParticipantResult(false, "Invalid NHS Number"); + } + + + + var participantManagementRecord = await _participantManagementDataService.GetSingleByFilter(x => x.NHSNumber == blockParticipantRequest.NhsNumber); + + if (participantManagementRecord == null) + { + return await BlockNewParticipant(blockParticipantRequest); + } + + if (participantManagementRecord.BlockedFlag == 1) + { + _logger.LogWarning("Participant already blocked and cannot be blocked"); + return new BlockParticipantResult(false, "Participant Already Blocked"); + } + + var participantDemographic = await _participantDemographicDataService.GetSingleByFilter(x => x.NhsNumber == blockParticipantRequest.NhsNumber); + + if (!ValidateRecordsMatch(participantDemographic, blockParticipantRequest)) + { + _logger.LogWarning("Participant didn't pass three point check and cannot be blocked"); + return new BlockParticipantResult(false, "Participant Didn't pass three point check"); + } + + _logger.LogInformation("Participant has been blocked"); + return await BlockExistingParticipant(participantManagementRecord); + + + + } + + public async Task UnblockParticipant(long nhsNumber) + { + + var participantManagementRecord = await _participantManagementDataService.GetSingleByFilter(x => x.NHSNumber == nhsNumber); + + if (participantManagementRecord == null) + { + return new BlockParticipantResult(false, "Participant Couldn't be found"); + } + + if (participantManagementRecord.BlockedFlag != 1) + { + _logger.LogInformation("Participant couldn't be unblocked as they are not currently blocked"); + return new BlockParticipantResult(false, "Participant is not blocked"); + } + + + var blockedFlagSet = await SetBlockedFlag(participantManagementRecord, false); + if (!blockedFlagSet) + { + return new BlockParticipantResult(false, "Failed to unset blocked flag"); + } + + if (participantManagementRecord.EligibilityFlag == 0) + { + return new BlockParticipantResult(true, "Participant was unblocked but not resubscribed to Nems as they are ineligible"); + } + + var nemsSubscribed = await SubscribeParticipantToNEMS(nhsNumber); + if (!nemsSubscribed) + { + return new BlockParticipantResult(false, "Participant couldn't be subscribed in Nems"); + } + + _logger.LogInformation("Participant has been unblocked"); + return new BlockParticipantResult(true, "Participant Unblocked"); + + + } + + public async Task GetParticipant(BlockParticipantDto blockParticipantRequest) + { + + if (!ValidationHelper.ValidateNHSNumber(blockParticipantRequest.NhsNumber.ToString())) + { + _logger.LogWarning("Participant had an invalid NHS Number and cannot be blocked"); + return new BlockParticipantResult(false, "Invalid NHS Number"); + } + + var participantDemographic = await _participantDemographicDataService.GetSingleByFilter(x => x.NhsNumber == blockParticipantRequest.NhsNumber); + + if (participantDemographic != null) + { + var recordsMatch = ValidateRecordsMatch(participantDemographic, blockParticipantRequest); + var responseBody = JsonSerializer.Serialize(new BlockParticipantDto + { + NhsNumber = participantDemographic.NhsNumber, + FamilyName = participantDemographic.FamilyName!, + DateOfBirth = participantDemographic.DateOfBirth!, + }); + return new BlockParticipantResult(recordsMatch, responseBody); + } + + var pdsParticipant = await GetPDSParticipant(blockParticipantRequest.NhsNumber); + + if (pdsParticipant == null) + { + return new BlockParticipantResult(false, "Participant Couldn't be found"); + } + + var pdsRecordsMatch = ValidateRecordsMatch(pdsParticipant, blockParticipantRequest); + var pdsResponseBody = JsonSerializer.Serialize(new BlockParticipantDto + { + NhsNumber = pdsParticipant.NhsNumber, + FamilyName = pdsParticipant.FamilyName!, + DateOfBirth = pdsParticipant.DateOfBirth! + }); + + return new BlockParticipantResult(pdsRecordsMatch, pdsResponseBody); + + } + + private async Task BlockNewParticipant(BlockParticipantDto blockParticipantRequest) + { + var pdsParticipant = await GetPDSParticipant(blockParticipantRequest.NhsNumber); + + if (pdsParticipant == null || !ValidateRecordsMatch(pdsParticipant, blockParticipantRequest)) + { + return new BlockParticipantResult(false, "Participant details do not match a records in Cohort Manager or PDS"); + } + + var participantManagementRecord = new ParticipantManagement + { + NHSNumber = pdsParticipant.NhsNumber, + BlockedFlag = 1, + EligibilityFlag = 0, + }; + + var participantManagementAdded = await _participantManagementDataService.Add(participantManagementRecord); + + if (!participantManagementAdded) + { + return new BlockParticipantResult(false, "Unable to add participant to Cohort Manager to be blocked"); + } + + return new BlockParticipantResult(true, "Participant Has been blocked"); + + + } + + private async Task BlockExistingParticipant(ParticipantManagement participant) + { + var blockFlagUpdated = await SetBlockedFlag(participant, true); + + if (!blockFlagUpdated) + { + return new BlockParticipantResult(false, "Failed to Update participant in Cohort Manager"); + } + + var unsubscribeFromNems = await UnsubscribeParticipantFromNEMS(participant.NHSNumber); + + if (!unsubscribeFromNems) + { + return new BlockParticipantResult(false, "Failed to unsubscribe Participant From NEMS"); + } + + return new BlockParticipantResult(true, "Participant Has been blocked"); + + } + + private async Task UnsubscribeParticipantFromNEMS(long nhsNumber) + { + var nemsUnsubscribeResponse = await _httpClient.SendPost(_config.ManageNemsSubscriptionUnsubscribeURL, CreateNhsNumberQueryParams(nhsNumber)); + + return nemsUnsubscribeResponse.IsSuccessStatusCode; + } + + private async Task SubscribeParticipantToNEMS(long nhsNumber) + { + var nemsSubscribeResponse = await _httpClient.SendPost(_config.ManageNemsSubscriptionSubscribeURL, CreateNhsNumberQueryParams(nhsNumber)); + + return nemsSubscribeResponse.IsSuccessStatusCode; + } + + + private async Task SetBlockedFlag(ParticipantManagement participant, bool blocked) + { + participant.BlockedFlag = blocked ? (short)1 : (short)0; + return await _participantManagementDataService.Update(participant); + } + + private async Task GetPDSParticipant(long nhsNumber) + { + var pdsResponse = await _httpClient.SendGet(_config.RetrievePdsDemographicURL, CreateNhsNumberQueryParams(nhsNumber)); + if (string.IsNullOrEmpty(pdsResponse)) + { + _logger.LogWarning("RetrievePDSDemographic Didn't return a valid response"); + return null!; + } + + var pdsDemographic = JsonSerializer.Deserialize(pdsResponse); + + return pdsDemographic!; + } + + private static bool ValidateRecordsMatch(ParticipantDemographic participant, BlockParticipantDto dto) + { + + if (!DateOnly.TryParseExact(dto.DateOfBirth, "yyyy-MM-dd",new CultureInfo("en-GB"),DateTimeStyles.None, out var dtoDateOfBirth )) + { + throw new FormatException("Date of Birth not in the correct format"); + } + + if (!DateOnly.TryParseExact(participant.DateOfBirth, "yyyyMMdd",new CultureInfo("en-GB"),DateTimeStyles.None, out var parsedDob)) + { + return false; + } + return string.Equals(participant.FamilyName, dto.FamilyName, StringComparison.InvariantCultureIgnoreCase) + && participant.NhsNumber == dto.NhsNumber + && parsedDob == dtoDateOfBirth; + } + + private static Dictionary CreateNhsNumberQueryParams(long nhsNumber) => + new Dictionary + { + {"nhsNumber",nhsNumber.ToString()} + }; + +} diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantResult.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantResult.cs new file mode 100644 index 0000000000..a6c2badab9 --- /dev/null +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantResult.cs @@ -0,0 +1,12 @@ +namespace NHS.CohortManager.ParticipantManagementService; + +public class BlockParticipantResult +{ + public BlockParticipantResult(bool success, string? responseMessage = null) + { + Success = success; + ResponseMessage = responseMessage; + } + public bool Success { get; set; } + public string? ResponseMessage { get; set; } +} diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs new file mode 100644 index 0000000000..4870dd018d --- /dev/null +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs @@ -0,0 +1,8 @@ +namespace NHS.CohortManager.ParticipantManagementService; + +public interface IBlockParticipantHandler +{ + Task BlockParticipant(BlockParticipantDto blockParticipantRequest); + Task GetParticipant(BlockParticipantDto blockParticipantRequest); + Task UnblockParticipant(long nhsNumber); +} diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/Program.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/Program.cs index 63f5daf6a6..1337c2d17c 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/Program.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/Program.cs @@ -20,10 +20,11 @@ services.AddBasicHealthCheck("Update Blocked Flag"); services.AddSingleton(); services.AddSingleton(); + services.AddScoped(); }) .AddExceptionHandler() .AddHttpClient() .AddTelemetry() .Build(); -await host.RunAsync(); \ No newline at end of file +await host.RunAsync(); diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs index f4a23da343..b25aad8755 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -3,28 +3,26 @@ namespace NHS.CohortManager.ParticipantManagementService; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; - -using DataServices.Client; using Common; using System.Net; -using Model; +using System.Text.Json; +using Azure.Messaging.EventGrid.SystemEvents; public class UpdateBlockedFlag { - private readonly IDataServiceClient _participantManagementClient; - private readonly IDataServiceClient _participantDemographicClient; private readonly ILogger _logger; private readonly ICreateResponse _createResponse; - private readonly IExceptionHandler _exceptionHandler; + private readonly IBlockParticipantHandler _blockParticipantHandler; + + private const string cannotBeDeserializedMessage = "Request couldn't be deserialized"; + - public UpdateBlockedFlag(IDataServiceClient participantManagementClient, IDataServiceClient participantDemographicClient, ILogger logger, ICreateResponse createResponse, IExceptionHandler exceptionHandler) + public UpdateBlockedFlag(ILogger logger, ICreateResponse createResponse, IBlockParticipantHandler blockParticipantHandler) { - _participantManagementClient = participantManagementClient; - _participantDemographicClient = participantDemographicClient; _logger = logger; _createResponse = createResponse; - _exceptionHandler = exceptionHandler; + _blockParticipantHandler = blockParticipantHandler; } /// @@ -40,8 +38,107 @@ public UpdateBlockedFlag(IDataServiceClient participantMa [Function("BlockParticipant")] public async Task BlockParticipant([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) { + + _logger.LogInformation("Block Participant Called"); - return await Main(1, req); + try + { + var blockParticipantDTOJson = await req.ReadAsStringAsync(); + if (string.IsNullOrWhiteSpace(blockParticipantDTOJson)) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, cannotBeDeserializedMessage); + } + + var blockParticipantDTO = JsonSerializer.Deserialize(blockParticipantDTOJson); + if (blockParticipantDTO == null) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, cannotBeDeserializedMessage); + } + + var blockParticipantResult = await _blockParticipantHandler.BlockParticipant(blockParticipantDTO); + + if (blockParticipantResult.Success) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, blockParticipantResult.ResponseMessage!); + } + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, blockParticipantResult.ResponseMessage!); + } + catch (JsonException jex) + { + _logger.LogError(jex, cannotBeDeserializedMessage); + return _createResponse.CreateHttpResponse(HttpStatusCode.BadRequest, req); + } + catch (FormatException fex) + { + _logger.LogError(fex, cannotBeDeserializedMessage); + return _createResponse.CreateHttpResponse(HttpStatusCode.BadRequest, req); + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while blocking a participant"); + return _createResponse.CreateHttpResponse(HttpStatusCode.InternalServerError, req); + } + + } + /// + /// Get Participant details will look up a participant in cohort manager if they do not exist in cohort manager then they + /// will be looked up in PDS. Once they are found a three point check will be carried out to ensure thier details matched, the found + /// details will be returned for manual assurance that the correct individual has been found + /// + /// + /// BlockParticipantDto or Error message + [Function("GetParticipant")] + public async Task GetParticipantDetails([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) + { + _logger.LogInformation("Get Participant Details Called"); + try + { + var blockParticipantDTOJson = await req.ReadAsStringAsync(); + if (string.IsNullOrWhiteSpace(blockParticipantDTOJson)) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, cannotBeDeserializedMessage); + } + + var blockParticipantDTO = JsonSerializer.Deserialize(blockParticipantDTOJson); + if (blockParticipantDTO == null) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, cannotBeDeserializedMessage); + } + + var getParticipantResult = await _blockParticipantHandler.GetParticipant(blockParticipantDTO); + + if (getParticipantResult.Success) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, getParticipantResult.ResponseMessage!); + } + + if (getParticipantResult.ResponseMessage == "Participant Couldn't be found") + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.NotFound, req, getParticipantResult.ResponseMessage); + } + if (getParticipantResult.ResponseMessage == "Invalid NHS Number") + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, getParticipantResult.ResponseMessage); + } + + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.InternalServerError, req, getParticipantResult.ResponseMessage!); + + } + catch (JsonException jex) + { + _logger.LogError(jex, cannotBeDeserializedMessage); + return _createResponse.CreateHttpResponse(HttpStatusCode.BadRequest, req); + } + catch (FormatException fex) + { + _logger.LogError(fex, cannotBeDeserializedMessage); + return _createResponse.CreateHttpResponse(HttpStatusCode.BadRequest, req); + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while blocking a participant"); + return _createResponse.CreateHttpResponse(HttpStatusCode.InternalServerError, req); + } } /// @@ -58,68 +155,35 @@ public async Task BlockParticipant([HttpTrigger(AuthorizationL public async Task UnblockParticipant([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) { _logger.LogInformation("Unblock Participant Called"); - return await Main(0, req); - } - - private async Task Main(short BlockedFlag, HttpRequestData req) - { - try + var nhsNumber = req.Query["nhsNumber"]; + if (string.IsNullOrWhiteSpace(nhsNumber)) { - string nhsNumberStr = req.Query["NhsNumber"]!; - string dateOfBirth = req.Query["DateOfBirth"]!; - string familyName = req.Query["FamilyName"]!; - - if (string.IsNullOrWhiteSpace(nhsNumberStr) || - string.IsNullOrWhiteSpace(dateOfBirth) || - string.IsNullOrWhiteSpace(familyName)) - { - throw new InvalidDataException("Missing or empty required query parameters."); - } - - long nhsNumber = long.Parse(nhsNumberStr); - if (!ValidationHelper.ValidateNHSNumber(nhsNumberStr)) { - throw new InvalidDataException("Invalid NHS Number"); - } - - // Check participant exists in Participant Demographic table. - ParticipantDemographic participantDemographic = await _participantDemographicClient - .GetSingleByFilter(i => i.NhsNumber == nhsNumber && i.DateOfBirth == dateOfBirth && i.FamilyName == familyName); + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, "No NHS Number provided"); + } - if (participantDemographic == null) - throw new KeyNotFoundException("Could not find participant"); + if (!ValidationHelper.ValidateNHSNumber(nhsNumber)) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, "Invalid NHS Number provided"); + } - // Change blocked flag - ParticipantManagement participantManagement = await _participantManagementClient.GetSingleByFilter(i => i.NHSNumber == nhsNumber && i.ScreeningId == 1); // TODO Unhardcode this (Phase 2) - participantManagement.BlockedFlag = BlockedFlag; + var nhsNumberParsed = long.Parse(nhsNumber); - bool blockFlagUpdated = await _participantManagementClient.Update(participantManagement); - if (!blockFlagUpdated) - throw new HttpRequestException("Failed to update blocked flag"); + var unBlockParticipantResult = await _blockParticipantHandler.UnblockParticipant(nhsNumberParsed); - return _createResponse.CreateHttpResponse(HttpStatusCode.OK, req, "Blocked flag updated successfully"); - } - catch (InvalidDataException ex) + if (unBlockParticipantResult.Success) { - await HandleException(ex, req); - return _createResponse.CreateHttpResponse(HttpStatusCode.BadRequest, req, "Invalid NHS Number or missing parameters"); + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, "Participant successfully unblocked"); } - catch (KeyNotFoundException ex) + if (unBlockParticipantResult.ResponseMessage == "Participant Couldn't be found") { - await HandleException(ex, req); - return _createResponse.CreateHttpResponse(HttpStatusCode.NotFound, req, "Participant not found"); - } - catch (Exception ex) - { - await HandleException(ex, req); - return _createResponse.CreateHttpResponse(HttpStatusCode.InternalServerError, req, "An error occurred while processing the request"); + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.NotFound, req, unBlockParticipantResult.ResponseMessage); } - } + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, unBlockParticipantResult.ResponseMessage!); + + - private async Task HandleException(Exception ex, HttpRequestData req) - { - _logger.LogError(ex, "An error occurred while processing the request for blocking/unblocking a participant"); - await _exceptionHandler.CreateSystemExceptionLogFromNhsNumber(ex, req.Query["NhsNumber"]!, "", "1", req.ToString()!); } - -} \ No newline at end of file + + +} diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlagConfig.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlagConfig.cs index 512de48dd5..ecef7df80c 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlagConfig.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlagConfig.cs @@ -5,9 +5,15 @@ namespace NHS.CohortManager.ParticipantManagementService; public class UpdateBlockedFlagConfig { [Required] - public string ParticipantManagementUrl {get; set;} + public required string ParticipantManagementUrl { get; set; } [Required] - public string ParticipantDemographicDataServiceURL {get; set;} + public required string ParticipantDemographicDataServiceURL { get; set; } [Required] - public string ExceptionFunctionURL {get; set;} -} \ No newline at end of file + public required string ExceptionFunctionURL { get; set; } + [Required] + public required string ManageNemsSubscriptionUnsubscribeURL { get; set; } + [Required] + public required string ManageNemsSubscriptionSubscribeURL { get; set; } + [Required] + public required string RetrievePdsDemographicURL { get; set; } +} diff --git a/application/CohortManager/src/Functions/ScreeningValidationService/StaticValidation/Breast_Screening_staticRules.json b/application/CohortManager/src/Functions/ScreeningValidationService/StaticValidation/Breast_Screening_staticRules.json index b4635a11ff..53d5dc91ab 100644 --- a/application/CohortManager/src/Functions/ScreeningValidationService/StaticValidation/Breast_Screening_staticRules.json +++ b/application/CohortManager/src/Functions/ScreeningValidationService/StaticValidation/Breast_Screening_staticRules.json @@ -170,19 +170,19 @@ ] }, { - "WorkflowName": "ADD", - "Rules": [ - { - "RuleName": "71.NewParticipantWithNoAddress.NBO.NonFatal", - "Expression": "!(participant.RecordType == Actions.New AND string.IsNullOrEmpty(participant.AddressLine1) AND string.IsNullOrEmpty(participant.AddressLine2) AND string.IsNullOrEmpty(participant.AddressLine3) AND string.IsNullOrEmpty(participant.AddressLine4) AND string.IsNullOrEmpty(participant.AddressLine5))", + "WorkflowName": "ADD", + "Rules": [ + { + "RuleName": "71.NewParticipantWithNoAddress.NBO.NonFatal", + "Expression": "!(participant.RecordType == Actions.New AND string.IsNullOrEmpty(participant.AddressLine1) AND string.IsNullOrEmpty(participant.AddressLine2) AND string.IsNullOrEmpty(participant.AddressLine3) AND string.IsNullOrEmpty(participant.AddressLine4) AND string.IsNullOrEmpty(participant.AddressLine5))", "Actions": { "OnFailure": { "Name": "OutputExpression", - "Context": { - "Expression": "\"Address is missing\"" + "Context": { + "Expression": "\"Address is missing\"" } } - } + } } ] }, diff --git a/application/CohortManager/src/Functions/Shared/Common/AzureServiceBusClient.cs b/application/CohortManager/src/Functions/Shared/Common/AzureServiceBusClient.cs index 74ea639190..dc65fdd124 100644 --- a/application/CohortManager/src/Functions/Shared/Common/AzureServiceBusClient.cs +++ b/application/CohortManager/src/Functions/Shared/Common/AzureServiceBusClient.cs @@ -30,6 +30,8 @@ public AzureServiceBusClient(ILogger logger, ServiceBusCl /// public async Task AddAsync(T message, string queueName) { + ArgumentException.ThrowIfNullOrWhiteSpace(queueName); + var sender = _senders.GetOrAdd(queueName, _serviceBusClient.CreateSender); try diff --git a/application/CohortManager/src/Functions/Shared/Common/Extensions/HttpClientExtension.cs b/application/CohortManager/src/Functions/Shared/Common/Extensions/HttpClientExtension.cs index 4768acc44f..17e8e1f9c3 100644 --- a/application/CohortManager/src/Functions/Shared/Common/Extensions/HttpClientExtension.cs +++ b/application/CohortManager/src/Functions/Shared/Common/Extensions/HttpClientExtension.cs @@ -5,9 +5,9 @@ namespace Common; public static class HttpClientExtension { - public static IHostBuilder AddHttpClient(this IHostBuilder hostBuilder, bool UseFakeHttpService = false) + public static IHostBuilder AddHttpClient(this IHostBuilder hostBuilder, bool useFakeHttpService = false) { - if (UseFakeHttpService) + if (useFakeHttpService) { return hostBuilder.ConfigureServices(_ => { @@ -23,12 +23,20 @@ public static IHostBuilder AddHttpClient(this IHostBuilder hostBuilder, bool Use }); } - public static IHostBuilder AddNemsHttpClient(this IHostBuilder hostBuilder) + public static IHostBuilder AddNemsHttpClient(this IHostBuilder hostBuilder, bool useStubbedEndpoint = false) { return hostBuilder.ConfigureServices(_ => { _.AddTransient(); - _.AddTransient(); + if (useStubbedEndpoint) + { + _.AddTransient(); + } + else + { + _.AddTransient(); + } + }); } } diff --git a/application/CohortManager/src/Functions/Shared/Common/HttpClientFunction.cs b/application/CohortManager/src/Functions/Shared/Common/HttpClientFunction.cs index 9c8a8c15d3..4a0e45d595 100644 --- a/application/CohortManager/src/Functions/Shared/Common/HttpClientFunction.cs +++ b/application/CohortManager/src/Functions/Shared/Common/HttpClientFunction.cs @@ -37,6 +37,26 @@ public async Task SendPost(string url, string data) throw; } } + public async Task SendPost(string url, Dictionary parameters) + { + using var client = _factory.CreateClient(); + + url = QueryHelpers.AddQueryString(url, parameters!); + + client.BaseAddress = new Uri(url); + client.Timeout = _timeout; + + try + { + HttpResponseMessage response = await client.PostAsync(url,null); + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, errorMessage, url, ex.Message); + throw; + } + } public async Task SendGet(string url) { @@ -229,4 +249,6 @@ private async Task GetOrThrowAsync(HttpClient client) return await GetResponseText(response); } + + } diff --git a/application/CohortManager/src/Functions/Shared/Common/Interfaces/IHttpClientFunction.cs b/application/CohortManager/src/Functions/Shared/Common/Interfaces/IHttpClientFunction.cs index 892525336a..5ac75edb6a 100644 --- a/application/CohortManager/src/Functions/Shared/Common/Interfaces/IHttpClientFunction.cs +++ b/application/CohortManager/src/Functions/Shared/Common/Interfaces/IHttpClientFunction.cs @@ -10,6 +10,13 @@ public interface IHttpClientFunction /// HttpResponseMessage Task SendPost(string url, string data); /// + /// Performs a POST request with query string parameters using HttpClient. + /// + /// URL to be used in request. + /// Query Parameters to be added to the url and used in request. + /// HttpResponseMessage + Task SendPost(string url, Dictionary parameters); + /// /// Performs a GET request using HttpClient and returns the response body as a string or null if the request failed. /// /// URL to be used in request. diff --git a/application/CohortManager/src/Functions/Shared/Common/PdsHttpClientMock.cs b/application/CohortManager/src/Functions/Shared/Common/PdsHttpClientMock.cs index 7709d039c8..6389f63070 100644 --- a/application/CohortManager/src/Functions/Shared/Common/PdsHttpClientMock.cs +++ b/application/CohortManager/src/Functions/Shared/Common/PdsHttpClientMock.cs @@ -12,7 +12,7 @@ namespace Common; /// /// Mock implementation of IHttpClientFunction specifically designed for PDS (Personal Demographics Service) calls. /// This mock returns PdsDemographic objects for SendGet calls and FHIR Patient JSON for SendPdsGet calls. -/// +/// /// WARNING: This is NOT a general-purpose HTTP client mock. It is designed specifically for PDS service testing. /// Other services (NEMS, ServiceNow, etc.) should not use this mock as it returns PDS-specific data structures. /// @@ -29,7 +29,11 @@ public PdsHttpClientMock(IFhirPatientDemographicMapper fhirPatientDemographicMap public async Task SendPost(string url, string data) { await Task.CompletedTask; - return CreateFakeHttpResponse(url); + return HttpStubUtilities.CreateFakeHttpResponse(url,""); + } + public Task SendPost(string url, Dictionary parameters) + { + throw new NotImplementedException(); } public async Task SendGet(string url, Dictionary parameters) @@ -58,7 +62,7 @@ public async Task SendPdsGet(string url, string bearerToken { var patient = GetPatientMockObject("complete-patient.json"); await Task.CompletedTask; - return CreateFakeHttpResponse(url, patient); + return HttpStubUtilities.CreateFakeHttpResponse(url, patient); } private static string GetPatientMockObject(string filename) @@ -79,7 +83,7 @@ private static string GetPatientMockObject(string filename) public async Task SendPut(string url, string data) { await Task.CompletedTask; - return CreateFakeHttpResponse(url); + return HttpStubUtilities.CreateFakeHttpResponse(url,""); } public async Task SendDelete(string url) diff --git a/application/CohortManager/src/Functions/Shared/Common/StubbedNemsHttpClientFunction.cs b/application/CohortManager/src/Functions/Shared/Common/StubbedNemsHttpClientFunction.cs new file mode 100644 index 0000000000..cd254262d2 --- /dev/null +++ b/application/CohortManager/src/Functions/Shared/Common/StubbedNemsHttpClientFunction.cs @@ -0,0 +1,57 @@ +namespace Common; + +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Common; +using Microsoft.Extensions.Logging; + +public class StubbedNemsHttpClientFunction : INemsHttpClientFunction +{ + public string GenerateJwtToken(string asid, string audience, string scope) + { + var header = new + { + alg = "none", + typ = "JWT" + }; + + var payload = new + { + iss = "https://nems.nhs.uk", + sub = $"https://fhir.nhs.uk/Id/accredited-system|{asid}", + aud = audience, + exp = DateTimeOffset.UtcNow.AddHours(1).ToUnixTimeSeconds(), + iat = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + reason_for_request = "directcare", + scope = scope, + requesting_system = $"https://fhir.nhs.uk/Id/accredited-system|{asid}" + }; + + var headerJson = JsonSerializer.Serialize(header); + var payloadJson = JsonSerializer.Serialize(payload); + + var headerEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(headerJson)) + .TrimEnd('=').Replace('+', '-').Replace('/', '_'); + var payloadEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(payloadJson)) + .TrimEnd('=').Replace('+', '-').Replace('/', '_'); + + // Unsigned JWT (signature is empty) + return $"{headerEncoded}.{payloadEncoded}."; + } + + public async Task SendSubscriptionDelete(NemsSubscriptionRequest request, int timeoutSeconds = 300) + { + await Task.CompletedTask; + return HttpStubUtilities.CreateFakeHttpResponse(request.Url, ""); + } + + public async Task SendSubscriptionPost(NemsSubscriptionPostRequest request, int timeoutSeconds = 300) + { + await Task.CompletedTask; + string subId = $"https://clinicals.spineservices.nhs.uk/STU3/Subscription/{Guid.NewGuid():N}"; + return HttpStubUtilities.CreateFakeHttpResponse(request.Url, "", System.Net.HttpStatusCode.OK, new Uri(subId)); + + } +} diff --git a/application/CohortManager/src/Functions/Shared/Common/Utilities/HTTPStubUtilities.cs b/application/CohortManager/src/Functions/Shared/Common/Utilities/HTTPStubUtilities.cs new file mode 100644 index 0000000000..2411157367 --- /dev/null +++ b/application/CohortManager/src/Functions/Shared/Common/Utilities/HTTPStubUtilities.cs @@ -0,0 +1,27 @@ +namespace Common; + +using System.Net; + +public static class HttpStubUtilities +{ + /// + /// takes in a fake string content and returns 200 OK response or a Given Response + /// + /// + /// + /// + public static HttpResponseMessage CreateFakeHttpResponse(string url, string content = "", HttpStatusCode httpStatusCode = HttpStatusCode.OK, Uri? location = null) + { + var HttpResponseData = new HttpResponseMessage(); + if (string.IsNullOrEmpty(url)) + { + HttpResponseData.StatusCode = HttpStatusCode.InternalServerError; + return HttpResponseData; + } + HttpResponseData.Headers.Location = location; + HttpResponseData.Content = new StringContent(content); + HttpResponseData.StatusCode = HttpStatusCode.OK; + return HttpResponseData; + } + +} diff --git a/infrastructure/tf-core/environments/development.tfvars b/infrastructure/tf-core/environments/development.tfvars index eb17b36e06..ebbdbb980e 100644 --- a/infrastructure/tf-core/environments/development.tfvars +++ b/infrastructure/tf-core/environments/development.tfvars @@ -424,6 +424,20 @@ function_apps = { { env_var_name = "ParticipantManagementUrl" function_app_key = "ParticipantManagementDataService" + }, + { + env_var_name = "ManageNemsSubscriptionUnsubscribeURL" + function_app_key = "ManageNemsSubscription" + endpoint_name = "Unsubscribe" + }, + { + env_var_name = "ManageNemsSubscriptionSubscribeURL" + function_app_key = "ManageNemsSubscription" + endpoint_name = "Subscribe" + }, + { + env_var_name = "RetrievePdsDemographicURL" + function_app_key = "RetrievePDSDemographic" } ] } diff --git a/infrastructure/tf-core/environments/integration.tfvars b/infrastructure/tf-core/environments/integration.tfvars index 0446d8b652..c638634741 100644 --- a/infrastructure/tf-core/environments/integration.tfvars +++ b/infrastructure/tf-core/environments/integration.tfvars @@ -424,6 +424,20 @@ function_apps = { { env_var_name = "ParticipantManagementUrl" function_app_key = "ParticipantManagementDataService" + }, + { + env_var_name = "ManageNemsSubscriptionUnsubscribeURL" + function_app_key = "ManageNemsSubscription" + endpoint_name = "Unsubscribe" + }, + { + env_var_name = "ManageNemsSubscriptionSubscribeURL" + function_app_key = "ManageNemsSubscription" + endpoint_name = "Subscribe" + }, + { + env_var_name = "RetrievePdsDemographicURL" + function_app_key = "RetrievePDSDemographic" } ] } diff --git a/infrastructure/tf-core/environments/nft.tfvars b/infrastructure/tf-core/environments/nft.tfvars index 40c9b04981..75be760418 100644 --- a/infrastructure/tf-core/environments/nft.tfvars +++ b/infrastructure/tf-core/environments/nft.tfvars @@ -423,6 +423,20 @@ function_apps = { { env_var_name = "ParticipantManagementUrl" function_app_key = "ParticipantManagementDataService" + }, + { + env_var_name = "ManageNemsSubscriptionUnsubscribeURL" + function_app_key = "ManageNemsSubscription" + endpoint_name = "Unsubscribe" + }, + { + env_var_name = "ManageNemsSubscriptionSubscribeURL" + function_app_key = "ManageNemsSubscription" + endpoint_name = "Subscribe" + }, + { + env_var_name = "RetrievePdsDemographicURL" + function_app_key = "RetrievePDSDemographic" } ] } diff --git a/infrastructure/tf-core/environments/preprod.tfvars b/infrastructure/tf-core/environments/preprod.tfvars index 86799b2f9a..d489195531 100644 --- a/infrastructure/tf-core/environments/preprod.tfvars +++ b/infrastructure/tf-core/environments/preprod.tfvars @@ -430,6 +430,20 @@ function_apps = { { env_var_name = "ParticipantManagementUrl" function_app_key = "ParticipantManagementDataService" + }, + { + env_var_name = "ManageNemsSubscriptionUnsubscribeURL" + function_app_key = "ManageNemsSubscription" + endpoint_name = "Unsubscribe" + }, + { + env_var_name = "ManageNemsSubscriptionSubscribeURL" + function_app_key = "ManageNemsSubscription" + endpoint_name = "Subscribe" + }, + { + env_var_name = "RetrievePdsDemographicURL" + function_app_key = "RetrievePDSDemographic" } ] } diff --git a/infrastructure/tf-core/environments/production.tfvars b/infrastructure/tf-core/environments/production.tfvars index aaf722906f..73bb0eba6f 100644 --- a/infrastructure/tf-core/environments/production.tfvars +++ b/infrastructure/tf-core/environments/production.tfvars @@ -415,6 +415,20 @@ function_apps = { { env_var_name = "ParticipantManagementUrl" function_app_key = "ParticipantManagementDataService" + }, + { + env_var_name = "ManageNemsSubscriptionUnsubscribeURL" + function_app_key = "ManageNemsSubscription" + endpoint_name = "Unsubscribe" + }, + { + env_var_name = "ManageNemsSubscriptionSubscribeURL" + function_app_key = "ManageNemsSubscription" + endpoint_name = "Subscribe" + }, + { + env_var_name = "RetrievePdsDemographicURL" + function_app_key = "RetrievePDSDemographic" } ] } diff --git a/infrastructure/tf-core/environments/sandbox.tfvars b/infrastructure/tf-core/environments/sandbox.tfvars index 0962758d85..fad0a88338 100644 --- a/infrastructure/tf-core/environments/sandbox.tfvars +++ b/infrastructure/tf-core/environments/sandbox.tfvars @@ -540,6 +540,20 @@ function_apps = { { env_var_name = "ParticipantManagementUrl" function_app_key = "ParticipantManagementDataService" + }, + { + env_var_name = "ManageNemsSubscriptionUnsubscribeURL" + function_app_key = "ManageNemsSubscription" + endpoint_name = "Unsubscribe" + }, + { + env_var_name = "ManageNemsSubscriptionSubscribeURL" + function_app_key = "ManageNemsSubscription" + endpoint_name = "Subscribe" + }, + { + env_var_name = "RetrievePdsDemographicURL" + function_app_key = "RetrievePDSDemographic" } ] } diff --git a/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs b/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs index f9f50f68a0..217a4a6238 100644 --- a/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs +++ b/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs @@ -11,223 +11,512 @@ namespace NHS.CohortManager.Tests.ParticipantManagementServiceTests; using System.Linq.Expressions; using System.Collections.Specialized; using NHS.CohortManager.ParticipantManagementService; +using RulesEngine.Models; +using System.Runtime.CompilerServices; +using Microsoft.Extensions.Options; +using System.Text.Json; +using System.Text; [TestClass] public class UpdateBlockedFlagTests { - private readonly Mock> _dsMockParticipantManagement = new(); - private readonly Mock> _dsMockParticipantDemographic = new(); - private readonly Mock> _loggerMock = new(); - private readonly Mock _createResponseMock = new(); - private readonly Mock _exceptionHandler = new(); - private readonly SetupRequest _setupRequest = new(); private readonly UpdateBlockedFlag _sut; - private readonly NameValueCollection queryParams; + private readonly Mock> _mockParticipantManagementClient = new(); + private readonly Mock> _mockParticipantDemographicClient = new(); + private readonly Mock> _mockUpdateBlockedFlagLogger = new(); + private readonly Mock> _mockHandlerLogger = new(); + private readonly Mock _mockCreateResponse = new(); + private readonly BlockParticipantHandler _blockParticipantHandler; + private readonly Mock _mockHttpClient = new(); + private readonly Mock> _mockConfig = new(); + private Mock _request; + private readonly SetupRequest _setupRequest = new(); public UpdateBlockedFlagTests() { - queryParams = new NameValueCollection + _mockConfig.Setup(x => x.Value).Returns(new UpdateBlockedFlagConfig { - { "NhsNumber", "8253303483"}, - { "ScreeningId", "1"}, - { "DateOfBirth", "01/01/2000"}, - { "FamilyName", "Smith"} - }; - - _createResponseMock.Setup(x => x.CreateHttpResponse(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((HttpStatusCode statusCode, HttpRequestData req, string ResponseBody) => + ParticipantDemographicDataServiceURL = "participantManagementUrl", + ParticipantManagementUrl = "ParticipantManagementUrl", + ExceptionFunctionURL = "ExceptionFunctionUrl", + ManageNemsSubscriptionSubscribeURL = "NemsSubscribeUrl", + ManageNemsSubscriptionUnsubscribeURL = "NemsUnsubscribeUrl", + RetrievePdsDemographicURL = "RetrievePdsDemographicUrl" + }); + _blockParticipantHandler = new BlockParticipantHandler(_mockHandlerLogger.Object, _mockParticipantManagementClient.Object, _mockParticipantDemographicClient.Object, _mockHttpClient.Object, _mockConfig.Object); + _sut = new UpdateBlockedFlag(_mockUpdateBlockedFlagLogger.Object, _mockCreateResponse.Object, _blockParticipantHandler); + + _mockCreateResponse.Setup(x => x.CreateHttpResponseWithBodyAsync( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((HttpStatusCode statusCode, HttpRequestData req, string responseBody) => { var response = req.CreateResponse(statusCode); - response.Headers.Add("Content-Type", "application/json; charset=utf-8"); - response.WriteString(ResponseBody); - return response; + response.WriteString(responseBody); + return Task.FromResult(response); }); + } + + [TestMethod] + public async Task BlockParticipant_ExistingParticipant_ReturnsSuccess() + { + //arrange + var requestBody = new BlockParticipantDto + { + NhsNumber = 6427635034, + FamilyName = "Jones", + DateOfBirth = "1923-10-12" + }; + + _request = _setupRequest.Setup(JsonSerializer.Serialize(requestBody)); - _createResponseMock.Setup(x => x.CreateHttpResponseWithBodyAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(async (HttpStatusCode statusCode, HttpRequestData req, string ResponseBody) => + _mockParticipantManagementClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .ReturnsAsync(new ParticipantManagement { - var response = req.CreateResponse(statusCode); - response.Headers.Add("Content-Type", "application/json; charset=utf-8"); - await response.WriteStringAsync(ResponseBody); - return response; + NHSNumber = 6427635034, + BlockedFlag = 0, + + }); + _mockParticipantDemographicClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .ReturnsAsync(new ParticipantDemographic + { + NhsNumber = 6427635034, + FamilyName = "Jones", + DateOfBirth = "19231012" }); - _dsMockParticipantDemographic - .Setup(x => x.GetSingleByFilter(It.IsAny>>())) - .ReturnsAsync(new ParticipantDemographic()); - _dsMockParticipantManagement - .Setup(x => x.GetSingleByFilter(It.IsAny>>())) - .ReturnsAsync(new ParticipantManagement()); - _dsMockParticipantManagement - .Setup(x => x.Update(It.IsAny())) + _mockParticipantManagementClient.Setup(x => x.Update(It.IsAny())) .ReturnsAsync(true); + _mockHttpClient.Setup(x => x.SendPost("NemsUnsubscribeUrl", It.IsAny>())) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK + }); + + //act + var result = await _sut.BlockParticipant(_request.Object); - _sut = new UpdateBlockedFlag(_dsMockParticipantManagement.Object, _dsMockParticipantDemographic.Object, _loggerMock.Object, _createResponseMock.Object, _exceptionHandler.Object); + //asset + Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); + _mockHttpClient.Verify(x => x.SendPost("NemsUnsubscribeUrl", It.IsAny>()), Times.Once); + _mockParticipantManagementClient.Verify(x => x.Update(It.IsAny()), Times.Once); + _mockHttpClient.VerifyNoOtherCalls(); } + [TestMethod] + public async Task BlockParticipant_NonExistentParticipant_ReturnsSuccess() + { + //arrange + var requestBody = new BlockParticipantDto + { + NhsNumber = 6427635034, + FamilyName = "Jones", + DateOfBirth = "1923-10-12" + }; + + _request = _setupRequest.Setup(JsonSerializer.Serialize(requestBody)); + + _mockParticipantManagementClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .Returns(Task.FromResult(null!)); + + + var pdsDemoResponse = JsonSerializer.Serialize( + new ParticipantDemographic + { + NhsNumber = 6427635034, + FamilyName = "Jones", + DateOfBirth = "19231012" + }); + + _mockHttpClient.Setup(x => x.SendGet("RetrievePdsDemographicUrl", It.IsAny>())) + .ReturnsAsync(pdsDemoResponse); + + _mockParticipantManagementClient.Setup(x => x.Add(It.IsAny())) + .ReturnsAsync(true); + + //act + var result = await _sut.BlockParticipant(_request.Object); + + //asset + Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); + _mockHttpClient.Verify(x => x.SendGet("RetrievePdsDemographicUrl", It.IsAny>()), Times.Once); + _mockParticipantManagementClient.Verify(x => x.GetSingleByFilter(It.IsAny>>())); + _mockParticipantManagementClient.Verify(x => x.Add(It.IsAny()), Times.Once); + _mockParticipantManagementClient.VerifyNoOtherCalls(); + _mockHttpClient.VerifyNoOtherCalls(); + } [TestMethod] - public async Task BlockParticipant_ValidRequest_UpdateBlockedFlag() + public async Task BlockParticipant_InvalidNhsNumber_ReturnsFailure() { - // Arrange - var request = _setupRequest.Setup(""); - request.Setup(r => r.Query).Returns(queryParams); + //arrange + var requestBody = new BlockParticipantDto + { + NhsNumber = 6427635035, + FamilyName = "Jones", + DateOfBirth = "1923-10-12" + }; - // Act - var response = await _sut.BlockParticipant(request.Object); + _request = _setupRequest.Setup(JsonSerializer.Serialize(requestBody)); - // Assert - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - _dsMockParticipantManagement - .Verify(x => x.Update(It.Is(pm => pm.BlockedFlag == 1)), Times.Once); + //act + var result = await _sut.BlockParticipant(_request.Object); + + //asset + Assert.AreEqual(HttpStatusCode.BadRequest, result.StatusCode); } + [TestMethod] + public async Task BlockParticipant_ParticipantAlreadyBlocked_ReturnsFailure() + { + //arrange + var requestBody = new BlockParticipantDto + { + NhsNumber = 6427635034, + FamilyName = "Jones", + DateOfBirth = "1923-10-12" + }; + + _request = _setupRequest.Setup(JsonSerializer.Serialize(requestBody)); + + _mockParticipantManagementClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .ReturnsAsync(new ParticipantManagement + { + NHSNumber = 6427635034, + BlockedFlag = 1, + + }); + + //act + var result = await _sut.BlockParticipant(_request.Object); + + //asset + Assert.AreEqual(HttpStatusCode.BadRequest, result.StatusCode); + _mockHttpClient.Verify(x => x.SendPost("NemsUnsubscribeUrl", It.IsAny>()), Times.Never); + _mockHttpClient.VerifyNoOtherCalls(); + _mockParticipantManagementClient.Verify(x => x.GetSingleByFilter(It.IsAny>>()), Times.Once); + _mockParticipantManagementClient.VerifyNoOtherCalls(); + } [TestMethod] - public async Task UnblockParticipant_ValidRequest_UpdateBlockedFlag() + public async Task BlockParticipant_ExistingParticipantFailsThreePointCheck_ReturnsSuccess() { - // Arrange - var request = _setupRequest.Setup(""); - request.Setup(r => r.Query).Returns(queryParams); + //arrange + var requestBody = new BlockParticipantDto + { + NhsNumber = 6427635034, + FamilyName = "Jones", + DateOfBirth = "1923-10-12" + }; + + _request = _setupRequest.Setup(JsonSerializer.Serialize(requestBody)); + + _mockParticipantManagementClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .ReturnsAsync(new ParticipantManagement + { + NHSNumber = 6427635034, + BlockedFlag = 0, - // Act - var response = await _sut.UnblockParticipant(request.Object); + }); + _mockParticipantDemographicClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .ReturnsAsync(new ParticipantDemographic + { + NhsNumber = 6427635034, + FamilyName = "Davies", + DateOfBirth = "19231012" + }); - // Assert - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - _dsMockParticipantManagement - .Verify(x => x.Update(It.Is(pm => pm.BlockedFlag == 0)), Times.Once); + //act + var result = await _sut.BlockParticipant(_request.Object); + + //asset + Assert.AreEqual(HttpStatusCode.BadRequest, result.StatusCode); + _mockHttpClient.Verify(x => x.SendPost("NemsUnsubscribeUrl", It.IsAny>()), Times.Never); + _mockHttpClient.VerifyNoOtherCalls(); + _mockParticipantManagementClient.Verify(x => x.GetSingleByFilter(It.IsAny>>()), Times.Once); + _mockParticipantManagementClient.VerifyNoOtherCalls(); + _mockParticipantDemographicClient.Verify(x => x.GetSingleByFilter(It.IsAny>>()), Times.Once); + _mockParticipantDemographicClient.VerifyNoOtherCalls(); } + [TestMethod] + public async Task BlockParticipant_NonExistentParticipantFailsThreePointCheck_ReturnsFailure() + { + //arrange + var requestBody = new BlockParticipantDto + { + NhsNumber = 6427635034, + FamilyName = "Jones", + DateOfBirth = "1923-10-12" + }; + + _request = _setupRequest.Setup(JsonSerializer.Serialize(requestBody)); + + _mockParticipantManagementClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .Returns(Task.FromResult(null!)); + + + var pdsDemoResponse = JsonSerializer.Serialize( + new ParticipantDemographic + { + NhsNumber = 6427635034, + FamilyName = "Davies", + DateOfBirth = "19231012" + }); + + _mockHttpClient.Setup(x => x.SendGet("RetrievePdsDemographicUrl", It.IsAny>())) + .ReturnsAsync(pdsDemoResponse); + + _mockParticipantManagementClient.Setup(x => x.Add(It.IsAny())) + .ReturnsAsync(true); + + + //act + var result = await _sut.BlockParticipant(_request.Object); + //asset + Assert.AreEqual(HttpStatusCode.BadRequest, result.StatusCode); + _mockHttpClient.Verify(x => x.SendGet("RetrievePdsDemographicUrl", It.IsAny>()), Times.Once); + _mockParticipantManagementClient.Verify(x => x.GetSingleByFilter(It.IsAny>>())); + _mockParticipantManagementClient.VerifyNoOtherCalls(); + _mockHttpClient.VerifyNoOtherCalls(); + } [TestMethod] - public async Task BlockParticipant_ParticipantDoesNotExistInDemographicTable_ReturnNotFound() + public async Task GetParticipant_ParticipantExistsInCM_ReturnsSuccess() { - // Arrange - var request = _setupRequest.Setup(""); - - _dsMockParticipantDemographic - .Setup(x => x.GetSingleByFilter(It.IsAny>>())) - .ReturnsAsync((ParticipantDemographic?)null); - - request.Setup(r => r.Query).Returns(queryParams); - - // Act - var response = await _sut.BlockParticipant(request.Object); - - // Assert - _exceptionHandler.Verify(x => x.CreateSystemExceptionLogFromNhsNumber( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny()), - Times.Once); - Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); + //arrange + var requestBody = new BlockParticipantDto + { + NhsNumber = 6427635034, + FamilyName = "Jones", + DateOfBirth = "1923-10-12" + }; + + _request = _setupRequest.Setup(JsonSerializer.Serialize(requestBody)); + + _mockParticipantDemographicClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .ReturnsAsync(new ParticipantDemographic + { + NhsNumber = 6427635034, + FamilyName = "Jones", + DateOfBirth = "19231012" + }); + + + + //act + var result = await _sut.GetParticipantDetails(_request.Object); + + //asset + Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); + _mockParticipantDemographicClient.Verify(x => x.GetSingleByFilter(It.IsAny>>())); + _mockParticipantManagementClient.VerifyNoOtherCalls(); + _mockHttpClient.VerifyNoOtherCalls(); } + [TestMethod] + public async Task GetParticipant_ParticipantOnlyInPDS_ReturnsSuccess() + { + //arrange + var requestBody = new BlockParticipantDto + { + NhsNumber = 6427635034, + FamilyName = "Jones", + DateOfBirth = "1923-10-12" + }; + + _request = _setupRequest.Setup(JsonSerializer.Serialize(requestBody)); + + _mockParticipantDemographicClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .Returns(Task.FromResult(null!)); + + var pdsDemoResponse = JsonSerializer.Serialize( + new ParticipantDemographic + { + NhsNumber = 6427635034, + FamilyName = "Jones", + DateOfBirth = "19231012" + }); + + _mockHttpClient.Setup(x => x.SendGet("RetrievePdsDemographicUrl", It.IsAny>())) + .ReturnsAsync(pdsDemoResponse); + + + //act + var result = await _sut.GetParticipantDetails(_request.Object); + //asset + Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); + _mockHttpClient.Verify(x => x.SendGet("RetrievePdsDemographicUrl", It.IsAny>()), Times.Once); + _mockParticipantDemographicClient.Verify(x => x.GetSingleByFilter(It.IsAny>>())); + _mockParticipantManagementClient.VerifyNoOtherCalls(); + _mockHttpClient.VerifyNoOtherCalls(); + } [TestMethod] - public async Task BlockParticipant_UpdateFails_ReturnInternalServerError() + public async Task GetParticipant_ParticipantNotExists_ReturnsFailure() { - // Arrange - var request = _setupRequest.Setup(""); - - _dsMockParticipantManagement - .Setup(x => x.Update(It.IsAny())) - .ReturnsAsync(false); - - request.Setup(r => r.Query).Returns(queryParams); - - // Act - var response = await _sut.BlockParticipant(request.Object); - - // Assert - _exceptionHandler.Verify(x => x.CreateSystemExceptionLogFromNhsNumber( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny()), - Times.Once); - Assert.AreEqual(HttpStatusCode.InternalServerError, response.StatusCode); + //arrange + var requestBody = new BlockParticipantDto + { + NhsNumber = 6427635034, + FamilyName = "Jones", + DateOfBirth = "1923-10-12" + }; + + _request = _setupRequest.Setup(JsonSerializer.Serialize(requestBody)); + + _mockParticipantDemographicClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .Returns(Task.FromResult(null!)); + + var pdsDemoResponse = JsonSerializer.Serialize( + new ParticipantDemographic + { + NhsNumber = 6427635034, + FamilyName = "Jones", + DateOfBirth = "19231012" + }); + + _mockHttpClient.Setup(x => x.SendGet("RetrievePdsDemographicUrl", It.IsAny>())) + .Returns(Task.FromResult("")!); + + + //act + var result = await _sut.GetParticipantDetails(_request.Object); + + //asset + Assert.AreEqual(HttpStatusCode.NotFound, result.StatusCode); + _mockHttpClient.Verify(x => x.SendGet("RetrievePdsDemographicUrl", It.IsAny>()), Times.Once); + _mockParticipantDemographicClient.Verify(x => x.GetSingleByFilter(It.IsAny>>()), Times.Once); + _mockParticipantManagementClient.VerifyNoOtherCalls(); + _mockHttpClient.VerifyNoOtherCalls(); } + [TestMethod] + public async Task GetParticipant_InvalidNhsNumber_ReturnsFailure() + { + //arrange + var requestBody = new BlockParticipantDto + { + NhsNumber = 6427635035, + FamilyName = "Jones", + DateOfBirth = "1923-10-12" + }; + + _request = _setupRequest.Setup(JsonSerializer.Serialize(requestBody)); + + //act + var result = await _sut.GetParticipantDetails(_request.Object); + //asset + Assert.AreEqual(HttpStatusCode.BadRequest, result.StatusCode); + } [TestMethod] - public async Task BlockParticipant_NoResponseParticipantManagement_ReturnInternalServerError() + public async Task UnblockParticipant_ParticipantIsBlocked_ReturnsSuccess() { - // Arrange - var request = _setupRequest.Setup(""); - request.Setup(r => r.Query).Returns(queryParams); - - //Simulates no response from the Participant management service. - _dsMockParticipantManagement.Setup(x => x.GetSingleByFilter(It.IsAny>>())).Throws(new Exception()); - - // Act - var response = await _sut.BlockParticipant(request.Object); - - // Assert - _exceptionHandler.Verify(x => x.CreateSystemExceptionLogFromNhsNumber( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny()), - Times.Once); - Assert.AreEqual(HttpStatusCode.InternalServerError, response.StatusCode); + //arrange + var queryParams = new NameValueCollection + { + {"nhsNumber","6427635034"} + }; + _request = _setupRequest.Setup("", queryParams, HttpMethod.Post); + + _mockParticipantManagementClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .ReturnsAsync(new ParticipantManagement + { + NHSNumber = 6427635034, + BlockedFlag = 1, + EligibilityFlag = 1 + }); + + _mockParticipantManagementClient.Setup(x => x.Update(It.IsAny())) + .ReturnsAsync(true); + + _mockHttpClient.Setup(x => x.SendPost("NemsSubscribeUrl", It.IsAny>())) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK + }); + + + //act + var result = await _sut.UnblockParticipant(_request.Object); + + //asset + Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); + _mockHttpClient.Verify(x => x.SendPost("NemsSubscribeUrl", It.IsAny>()), Times.Once); + _mockParticipantManagementClient.Verify(x => x.GetSingleByFilter(It.IsAny>>()), Times.Once); + _mockParticipantManagementClient.Verify(x => x.Update(It.IsAny()), Times.Once); + _mockParticipantManagementClient.VerifyNoOtherCalls(); + _mockHttpClient.VerifyNoOtherCalls(); } [TestMethod] - public async Task BlockParticipant_InvalidNHSNumber_ReturnBadRequest() + [DataRow("6427635035")] + [DataRow("642763")] + [DataRow("abs7635035")] + public async Task UnblockParticipant_InvalidNhsNumber_ReturnsFailure(string nhsNumber) { - // Arrange - var request = _setupRequest.Setup(""); + //arrange var queryParams = new NameValueCollection { - { "NhsNumber", "1234567890"}, - { "ScreeningId", "1"}, - { "DateOfBirth", "01/01/2000"}, - { "LastName", "Smith"} + {"nhsNumber",nhsNumber} }; + _request = _setupRequest.Setup("", queryParams, HttpMethod.Post); - request.Setup(r => r.Query).Returns(queryParams); - - // Act - var response = await _sut.BlockParticipant(request.Object); + //act + var result = await _sut.UnblockParticipant(_request.Object); - // Assert - _exceptionHandler.Verify(x => x.CreateSystemExceptionLogFromNhsNumber( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny()), - Times.Once); - Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode); + //asset + Assert.AreEqual(HttpStatusCode.BadRequest, result.StatusCode); } + [TestMethod] + public async Task UnblockParticipant_ParticipantNotFound_ReturnsFailure() + { + //arrange + var queryParams = new NameValueCollection + { + {"nhsNumber","6427635034"} + }; + _request = _setupRequest.Setup("", queryParams, HttpMethod.Post); + + _mockParticipantManagementClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .Returns(Task.FromResult(null!)); + //act + var result = await _sut.UnblockParticipant(_request.Object); + + //asset + Assert.AreEqual(HttpStatusCode.NotFound, result.StatusCode); + _mockParticipantManagementClient.Verify(x => x.GetSingleByFilter(It.IsAny>>()), Times.Once); + _mockHttpClient.VerifyNoOtherCalls(); + } [TestMethod] - public async Task BlockParticipant_MissingQueryParams_ReturnBadRequest() + public async Task UnblockParticipant_ParticipantNotBlocked_ReturnBadRequest() { - // Arrange - var request = _setupRequest.Setup(""); + //arrange var queryParams = new NameValueCollection { - { "NhsNumber", "8253303483"}, - { "ScreeningId", "1"}, - { "DateOfBirth", "01/01/2000"} + {"nhsNumber","6427635034"} }; + _request = _setupRequest.Setup("", queryParams, HttpMethod.Post); + + _mockParticipantManagementClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .ReturnsAsync(new ParticipantManagement + { + NHSNumber = 6427635034, + BlockedFlag = 0, + EligibilityFlag = 1 + }); - request.Setup(r => r.Query).Returns(queryParams); - // Act - var response = await _sut.BlockParticipant(request.Object); + //act + var result = await _sut.UnblockParticipant(_request.Object); - // Assert - _exceptionHandler.Verify(x => x.CreateSystemExceptionLogFromNhsNumber( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny()), - Times.Once); - Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode); + //asset + Assert.AreEqual(HttpStatusCode.BadRequest, result.StatusCode); + _mockParticipantManagementClient.Verify(x => x.GetSingleByFilter(It.IsAny>>()), Times.Once); + _mockParticipantManagementClient.VerifyNoOtherCalls(); + _mockHttpClient.VerifyNoOtherCalls(); } -} \ No newline at end of file + + + + +}