From 653f9ad5d928fcc5dbbf8148c8fb34c5cdc507dc Mon Sep 17 00:00:00 2001 From: Joseph2910 <184524642+Joseph2910@users.noreply.github.com> Date: Wed, 23 Jul 2025 13:26:11 +0100 Subject: [PATCH 01/28] feat: Add PDS Stub --- .../UpdateBlockedFlag/UpdateBlockedFlag.cs | 48 ++++++++++++++++--- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs index f4a23da343..a833e46c84 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -42,6 +42,7 @@ public async Task BlockParticipant([HttpTrigger(AuthorizationL { _logger.LogInformation("Block Participant Called"); return await Main(1, req); + } /// @@ -77,7 +78,8 @@ private async Task Main(short BlockedFlag, HttpRequestData req } long nhsNumber = long.Parse(nhsNumberStr); - if (!ValidationHelper.ValidateNHSNumber(nhsNumberStr)) { + if (!ValidationHelper.ValidateNHSNumber(nhsNumberStr)) + { throw new InvalidDataException("Invalid NHS Number"); } @@ -85,14 +87,35 @@ private async Task Main(short BlockedFlag, HttpRequestData req ParticipantDemographic participantDemographic = await _participantDemographicClient .GetSingleByFilter(i => i.NhsNumber == nhsNumber && i.DateOfBirth == dateOfBirth && i.FamilyName == familyName); - if (participantDemographic == null) + ParticipantManagement participantManagement; + bool blockFlagUpdated; + if (participantDemographic == null && BlockedFlag == 1) + { + //Search PDS + participantManagement = CheckPDS(nhsNumber, dateOfBirth, familyName); //TODO Remove Mock and add call to PDS. + // If PDS returns non 200 + if (participantManagement == null) //TODO This will become conclave expression when we can call PDS like a DataService + { + throw new KeyNotFoundException("Could not find participant"); + } + else + { + participantManagement.ScreeningId = 1; + blockFlagUpdated = await _participantManagementClient.Add(participantManagement); + } + } + else if (participantDemographic == null && BlockedFlag == 0) + { throw new KeyNotFoundException("Could not find participant"); + } + else + { + participantManagement = await _participantManagementClient.GetSingleByFilter(i => i.NHSNumber == nhsNumber && i.ScreeningId == 1); // TODO Unhardcode this (Phase 2) + // Change blocked flag + participantManagement.BlockedFlag = BlockedFlag; + blockFlagUpdated = await _participantManagementClient.Update(participantManagement); + } - // Change blocked flag - ParticipantManagement participantManagement = await _participantManagementClient.GetSingleByFilter(i => i.NHSNumber == nhsNumber && i.ScreeningId == 1); // TODO Unhardcode this (Phase 2) - participantManagement.BlockedFlag = BlockedFlag; - - bool blockFlagUpdated = await _participantManagementClient.Update(participantManagement); if (!blockFlagUpdated) throw new HttpRequestException("Failed to update blocked flag"); @@ -121,5 +144,16 @@ 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()!); } + + // THIS IS TEMPORARY UNTIL PDS INTEGRATION IS COMPLETE + private static ParticipantManagement CheckPDS(long nhsNumber, string dateOfBirth, string familyName) + { + ParticipantManagement participant = new() + { + NHSNumber = nhsNumber, + }; + + return participant; + } } \ No newline at end of file From c96022900d07c6e23af941eb727451e7c3d081d7 Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Thu, 31 Jul 2025 15:17:24 +0100 Subject: [PATCH 02/28] block participant refactor --- .../UpdateBlockedFlag/BlockParticipantDTO.cs | 8 + .../BlockParticipantHandler.cs | 138 ++++++++++++++++++ .../BlockParticipantResult.cs | 12 ++ .../IBlockParticipantHandler.cs | 6 + .../UpdateBlockedFlag/Program.cs | 3 +- .../UpdateBlockedFlag/UpdateBlockedFlag.cs | 59 ++++---- .../UpdateBlockedFlagConfig.cs | 16 +- .../Shared/Common/HttpClientFunction.cs | 22 +++ .../Common/Interfaces/IHttpClientFunction.cs | 7 + 9 files changed, 238 insertions(+), 33 deletions(-) create mode 100644 application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantDTO.cs create mode 100644 application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs create mode 100644 application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantResult.cs create mode 100644 application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs 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..833983bf02 --- /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 DateOnly 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..71fe9a32b9 --- /dev/null +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -0,0 +1,138 @@ +namespace NHS.CohortManager.ParticipantManagementService; + +using System; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Security.Cryptography.X509Certificates; +using System.Text.Json; +using Common; +using DataServices.Client; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Identity.Client; +using Microsoft.Net.Http.Headers; +using Model; +using RulesEngine.Models; + +public class BlockParticipantHandler : IBlockParticipantHandler +{ + private readonly ILogger _logger; + private readonly IDataServiceClient _participantManagementDataService; + private readonly IHttpClientFunction _httpClient; + private readonly UpdateBlockedFlagConfig _config; + public BlockParticipantHandler(ILogger logger, IDataServiceClient participantManagementDataService, IHttpClientFunction httpClient, IOptions config) + { + _logger = logger; + _participantManagementDataService = participantManagementDataService; + _httpClient = httpClient; + _config = config.Value; + } + + public async Task BlockParticipant(BlockParticipantDTO blockParticipantRequest) + { + if (!ValidationHelper.ValidateNHSNumber(blockParticipantRequest.NhsNumber.ToString())) + { + throw new InvalidDataException("Invalid NHS Number"); + } + + var participantManagementRecord = await _participantManagementDataService.GetSingleByFilter(x => x.NHSNumber == blockParticipantRequest.NhsNumber); + + if (participantManagementRecord != null) + { + return await BlockExistingParticipant(participantManagementRecord); + } + + return await BlockNewParticipant(blockParticipantRequest); + + } + + 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.ManageNemsSubscriptionSubscribeURL, CreateNhsNumberQueryParams(nhsNumber)); + + return nemsUnsubscribeResponse.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)); + var pdsDemographic = JsonSerializer.Deserialize(pdsResponse); + + return pdsDemographic; + } + + private static bool ValidateRecordsMatch(ParticipantDemographic participant, BlockParticipantDTO dto) + { + return string.Equals(participant.FamilyName, dto.FamilyName, StringComparison.InvariantCultureIgnoreCase) + && participant.NhsNumber == dto.NhsNumber + && DateOnly.TryParse(participant.DateOfBirth, out var parsedDob) + && parsedDob == dto.DateOfBirth; + } + + 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..3d8cbdb070 --- /dev/null +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs @@ -0,0 +1,6 @@ +namespace NHS.CohortManager.ParticipantManagementService; + +public interface IBlockParticipantHandler +{ + Task BlockParticipant(BlockParticipantDTO blockParticipantRequest); +} 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 a833e46c84..351fa7355b 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -16,15 +16,17 @@ public class UpdateBlockedFlag private readonly ILogger _logger; private readonly ICreateResponse _createResponse; private readonly IExceptionHandler _exceptionHandler; + private readonly IBlockParticipantHandler _blockParticipantHandler; - public UpdateBlockedFlag(IDataServiceClient participantManagementClient, IDataServiceClient participantDemographicClient, ILogger logger, ICreateResponse createResponse, IExceptionHandler exceptionHandler) + public UpdateBlockedFlag(IDataServiceClient participantManagementClient, IDataServiceClient participantDemographicClient, ILogger logger, ICreateResponse createResponse, IExceptionHandler exceptionHandler, IBlockParticipantHandler blockParticipantHandler) { _participantManagementClient = participantManagementClient; _participantDemographicClient = participantDemographicClient; _logger = logger; _createResponse = createResponse; _exceptionHandler = exceptionHandler; + _blockParticipantHandler = blockParticipantHandler; } /// @@ -41,8 +43,13 @@ public UpdateBlockedFlag(IDataServiceClient participantMa public async Task BlockParticipant([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) { _logger.LogInformation("Block Participant Called"); - return await Main(1, req); - + + var blockParticipantDTO = await req.ReadFromJsonAsync(); + + var blockParticipantResult = await _blockParticipantHandler.BlockParticipant(blockParticipantDTO); + + return _createResponse.CreateHttpResponse(HttpStatusCode.OK, req, blockParticipantResult.ResponseMessage); + } /// @@ -62,6 +69,13 @@ public async Task UnblockParticipant([HttpTrigger(Authorizatio return await Main(0, req); } + + // [Function("PreviewParticipant")] + // public async Task PreviewParticipant([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) + // { + + // } + private async Task Main(short BlockedFlag, HttpRequestData req) { try @@ -91,18 +105,18 @@ private async Task Main(short BlockedFlag, HttpRequestData req bool blockFlagUpdated; if (participantDemographic == null && BlockedFlag == 1) { - //Search PDS - participantManagement = CheckPDS(nhsNumber, dateOfBirth, familyName); //TODO Remove Mock and add call to PDS. - // If PDS returns non 200 - if (participantManagement == null) //TODO This will become conclave expression when we can call PDS like a DataService - { - throw new KeyNotFoundException("Could not find participant"); - } - else - { - participantManagement.ScreeningId = 1; - blockFlagUpdated = await _participantManagementClient.Add(participantManagement); - } + // //Search PDS + // //participantManagement = CheckPDS(nhsNumber, dateOfBirth, familyName); //TODO Remove Mock and add call to PDS. + // // If PDS returns non 200 + // if (participantManagement == null) //TODO This will become conclave expression when we can call PDS like a DataService + // { + // throw new KeyNotFoundException("Could not find participant"); + // } + // else + // { + // participantManagement.ScreeningId = 1; + // blockFlagUpdated = await _participantManagementClient.Add(participantManagement); + // } } else if (participantDemographic == null && BlockedFlag == 0) { @@ -116,7 +130,7 @@ private async Task Main(short BlockedFlag, HttpRequestData req blockFlagUpdated = await _participantManagementClient.Update(participantManagement); } - if (!blockFlagUpdated) + if (!true) throw new HttpRequestException("Failed to update blocked flag"); return _createResponse.CreateHttpResponse(HttpStatusCode.OK, req, "Blocked flag updated successfully"); @@ -145,15 +159,4 @@ private async Task HandleException(Exception ex, HttpRequestData req) await _exceptionHandler.CreateSystemExceptionLogFromNhsNumber(ex, req.Query["NhsNumber"]!, "", "1", req.ToString()!); } - // THIS IS TEMPORARY UNTIL PDS INTEGRATION IS COMPLETE - private static ParticipantManagement CheckPDS(long nhsNumber, string dateOfBirth, string familyName) - { - ParticipantManagement participant = new() - { - NHSNumber = nhsNumber, - }; - - return participant; - } - -} \ 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..9ae9153bf3 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlagConfig.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlagConfig.cs @@ -1,13 +1,21 @@ namespace NHS.CohortManager.ParticipantManagementService; using System.ComponentModel.DataAnnotations; +using Azure.Core; +using Microsoft.Azure.Amqp; 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/Shared/Common/HttpClientFunction.cs b/application/CohortManager/src/Functions/Shared/Common/HttpClientFunction.cs index 9a2ad85257..e033e8c3cd 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) { @@ -206,4 +226,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 faf01942fb..c12842b07c 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. From 2c82910a0573d9512c81a71d8e361f42fd675944 Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Mon, 4 Aug 2025 16:31:49 +0100 Subject: [PATCH 03/28] Stubbing Nems API --- .../receiveCaasFile/receiveCaasFileConfig.cs | 2 + .../Extensions/CertificateExtensions.cs | 11 ++-- .../ManageNemsSubscriptionConfig.cs | 13 +++-- .../ManageNemsSubscription/Program.cs | 13 ++++- .../BlockParticipantHandler.cs | 21 ++++++- .../Shared/Common/AzureServiceBusClient.cs | 2 + .../Common/Extensions/HttpClientExtension.cs | 16 ++++-- .../Shared/Common/HttpClientFunctionMock.cs | 27 ++------- .../Common/StubbedNemsHttpClientFunction.cs | 57 +++++++++++++++++++ .../Common/Utilities/HTTPStubUtilities.cs | 47 +++++++++++++++ 10 files changed, 172 insertions(+), 37 deletions(-) create mode 100644 application/CohortManager/src/Functions/Shared/Common/StubbedNemsHttpClientFunction.cs create mode 100644 application/CohortManager/src/Functions/Shared/Common/Utilities/HTTPStubUtilities.cs 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..2dc0511d42 100644 --- a/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs +++ b/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs @@ -1,6 +1,7 @@ namespace NHS.CohortManager.DemographicServices; using System.ComponentModel.DataAnnotations; +using Hl7.Fhir.Specification.Navigation; /// /// Configuration settings for NEMS subscription management @@ -86,10 +87,10 @@ public class ManageNemsSubscriptionConfig /// /// Default event types to subscribe to /// - public string[] NemsDefaultEventTypes { get; set; } = new[] - { - "pds-record-change-1" - }; + // public string[] NemsDefaultEventTypes { get; set; } = new[] + // { + // "pds-record-change-1" + // }; /// /// HTTP client timeout in seconds for NEMS API requests @@ -101,5 +102,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 = true; } 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/BlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs index 71fe9a32b9..7e0c909535 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -18,12 +18,19 @@ 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, IHttpClientFunction httpClient, IOptions config) + public BlockParticipantHandler(ILogger logger, + IDataServiceClient participantManagementDataService, + IDataServiceClient participantDemographicDataService, + IHttpClientFunction httpClient, + IOptions config + ) { _logger = logger; _participantManagementDataService = participantManagementDataService; + _participantDemographicDataService = participantDemographicDataService; _httpClient = httpClient; _config = config.Value; } @@ -35,8 +42,20 @@ public async Task BlockParticipant(BlockParticipantDTO b throw new InvalidDataException("Invalid NHS Number"); } + var participantDemographic = await _participantDemographicDataService.GetSingleByFilter(x => x.NhsNumber == blockParticipantRequest.NhsNumber); + + if (!ValidateRecordsMatch(participantDemographic, blockParticipantRequest)) + { + return new BlockParticipantResult(false, "Participant Didn't pass three point check"); + } + var participantManagementRecord = await _participantManagementDataService.GetSingleByFilter(x => x.NHSNumber == blockParticipantRequest.NhsNumber); + if (participantManagementRecord.BlockedFlag == 1) + { + return new BlockParticipantResult(false, "Participant Already Blocked"); + } + if (participantManagementRecord != null) { return await BlockExistingParticipant(participantManagementRecord); 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 e836459992..95028b3a24 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/HttpClientFunctionMock.cs b/application/CohortManager/src/Functions/Shared/Common/HttpClientFunctionMock.cs index 2c34580b0f..47758b433f 100644 --- a/application/CohortManager/src/Functions/Shared/Common/HttpClientFunctionMock.cs +++ b/application/CohortManager/src/Functions/Shared/Common/HttpClientFunctionMock.cs @@ -13,7 +13,7 @@ public class HttpClientFunctionMock : IHttpClientFunction public async Task SendPost(string url, string data) { await Task.CompletedTask; - return CreateFakeHttpResponse(url); + return HTTPStubUtilities.CreateFakeHttpResponse(url,""); } public async Task SendGet(string url, Dictionary parameters) @@ -38,7 +38,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) @@ -59,7 +59,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) @@ -79,25 +79,8 @@ public async Task SendGetResponse(string url) throw new NotImplementedException(); } - - /// - /// takes in a fake string content and returns 200 OK response - /// - /// - /// - /// - private static HttpResponseMessage CreateFakeHttpResponse(string url, string content = "") + public Task SendPost(string url, Dictionary parameters) { - var HttpResponseData = new HttpResponseMessage(); - if (string.IsNullOrEmpty(url)) - { - HttpResponseData.StatusCode = HttpStatusCode.InternalServerError; - return HttpResponseData; - } - - HttpResponseData.Content = new StringContent(content); - HttpResponseData.StatusCode = HttpStatusCode.OK; - return HttpResponseData; + throw new NotImplementedException(); } - } 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..1d8b9c6d97 --- /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..0b2bd1da8b --- /dev/null +++ b/application/CohortManager/src/Functions/Shared/Common/Utilities/HTTPStubUtilities.cs @@ -0,0 +1,47 @@ +namespace Common; + +using System.Net; + +public static class HTTPStubUtilities +{ + + /// + /// takes in a fake string content and returns 200 OK response + /// + /// + /// + /// + public static HttpResponseMessage CreateFakeHttpResponse(string url, string content = "") + { + var HttpResponseData = new HttpResponseMessage(); + if (string.IsNullOrEmpty(url)) + { + HttpResponseData.StatusCode = HttpStatusCode.InternalServerError; + return HttpResponseData; + } + + HttpResponseData.Content = new StringContent(content); + HttpResponseData.StatusCode = HttpStatusCode.OK; + return HttpResponseData; + } + /// + /// 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; + } + +} From fc9c802b0c02da234629f3226acb14848c3cc884 Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Mon, 4 Aug 2025 17:35:51 +0100 Subject: [PATCH 04/28] Update Logging and returning body --- .../UpdateBlockedFlag/BlockParticipantHandler.cs | 11 +++++++++-- .../UpdateBlockedFlag/UpdateBlockedFlag.cs | 6 +++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs index 7e0c909535..62d8968498 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -39,6 +39,7 @@ public async Task BlockParticipant(BlockParticipantDTO b { if (!ValidationHelper.ValidateNHSNumber(blockParticipantRequest.NhsNumber.ToString())) { + _logger.LogWarning("Participant had an invalid NHS Number and cannot be blocked"); throw new InvalidDataException("Invalid NHS Number"); } @@ -46,6 +47,7 @@ public async Task BlockParticipant(BlockParticipantDTO b if (!ValidateRecordsMatch(participantDemographic, blockParticipantRequest)) { + _logger.LogWarning("Participant had an didn't pass three point check and cannot be blocked"); return new BlockParticipantResult(false, "Participant Didn't pass three point check"); } @@ -53,6 +55,7 @@ public async Task BlockParticipant(BlockParticipantDTO b if (participantManagementRecord.BlockedFlag == 1) { + _logger.LogWarning("Participant already blocked and cannot be blocked"); return new BlockParticipantResult(false, "Participant Already Blocked"); } @@ -115,7 +118,7 @@ private async Task BlockExistingParticipant(ParticipantM private async Task UnsubscribeParticipantFromNEMS(long nhsNumber) { - var nemsUnsubscribeResponse = await _httpClient.SendPost(_config.ManageNemsSubscriptionSubscribeURL, CreateNhsNumberQueryParams(nhsNumber)); + var nemsUnsubscribeResponse = await _httpClient.SendPost(_config.ManageNemsSubscriptionUnsubscribeURL, CreateNhsNumberQueryParams(nhsNumber)); return nemsUnsubscribeResponse.IsSuccessStatusCode; } @@ -136,9 +139,13 @@ private async Task GetPDSParticipant(long nhsNumber) private static bool ValidateRecordsMatch(ParticipantDemographic participant, BlockParticipantDTO dto) { + + if (!DateOnly.TryParseExact(participant.DateOfBirth, "yyyyMMdd", out var parsedDob)) + { + return false; + } return string.Equals(participant.FamilyName, dto.FamilyName, StringComparison.InvariantCultureIgnoreCase) && participant.NhsNumber == dto.NhsNumber - && DateOnly.TryParse(participant.DateOfBirth, out var parsedDob) && parsedDob == dto.DateOfBirth; } diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs index 351fa7355b..51dd77e17b 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -48,7 +48,11 @@ public async Task BlockParticipant([HttpTrigger(AuthorizationL var blockParticipantResult = await _blockParticipantHandler.BlockParticipant(blockParticipantDTO); - return _createResponse.CreateHttpResponse(HttpStatusCode.OK, req, blockParticipantResult.ResponseMessage); + if (blockParticipantResult.Success) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, blockParticipantResult.ResponseMessage); + } + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, blockParticipantResult.ResponseMessage); } From b067b343793a5d58848c0de80c8dd0c6526e55ef Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Tue, 5 Aug 2025 14:21:37 +0100 Subject: [PATCH 05/28] get participant and bug fixes --- .../UpdateBlockedFlag/BlockParticipantDTO.cs | 2 +- .../BlockParticipantHandler.cs | 71 +++++++-- .../IBlockParticipantHandler.cs | 3 +- .../UpdateBlockedFlag/UpdateBlockedFlag.cs | 145 +++++++----------- 4 files changed, 120 insertions(+), 101 deletions(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantDTO.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantDTO.cs index 833983bf02..139074e35c 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantDTO.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantDTO.cs @@ -3,6 +3,6 @@ namespace NHS.CohortManager.ParticipantManagementService; public class BlockParticipantDTO { public required long NhsNumber { get; set; } - public required DateOnly DateOfBirth { 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 index 62d8968498..9cd9a991e0 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -7,6 +7,7 @@ namespace NHS.CohortManager.ParticipantManagementService; using System.Text.Json; using Common; using DataServices.Client; +using Microsoft.EntityFrameworkCore.Storage.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Identity.Client; @@ -40,7 +41,22 @@ public async Task BlockParticipant(BlockParticipantDTO b if (!ValidationHelper.ValidateNHSNumber(blockParticipantRequest.NhsNumber.ToString())) { _logger.LogWarning("Participant had an invalid NHS Number and cannot be blocked"); - throw new InvalidDataException("Invalid NHS Number"); + 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); @@ -51,20 +67,52 @@ public async Task BlockParticipant(BlockParticipantDTO b return new BlockParticipantResult(false, "Participant Didn't pass three point check"); } - var participantManagementRecord = await _participantManagementDataService.GetSingleByFilter(x => x.NHSNumber == blockParticipantRequest.NhsNumber); - if (participantManagementRecord.BlockedFlag == 1) + return await BlockExistingParticipant(participantManagementRecord); + + + + } + + public async Task GetParticipant(BlockParticipantDTO dto) + { + + if (!ValidationHelper.ValidateNHSNumber(dto.NhsNumber.ToString())) { - _logger.LogWarning("Participant already blocked and cannot be blocked"); - return new BlockParticipantResult(false, "Participant Already Blocked"); + _logger.LogWarning("Participant had an invalid NHS Number and cannot be blocked"); + return new BlockParticipantResult(false, "Invalid NHS Number"); } - if (participantManagementRecord != null) + var participantDemographic = await _participantDemographicDataService.GetSingleByFilter(x => x.NhsNumber == dto.NhsNumber); + + if (participantDemographic != null) { - return await BlockExistingParticipant(participantManagementRecord); + var recordsMatch = ValidateRecordsMatch(participantDemographic, dto); + var responseBody = JsonSerializer.Serialize(new BlockParticipantDTO + { + NhsNumber = participantDemographic.NhsNumber, + FamilyName = participantDemographic.FamilyName, + DateOfBirth = participantDemographic.DateOfBirth, + }); + return new BlockParticipantResult(recordsMatch, responseBody); } - return await BlockNewParticipant(blockParticipantRequest); + var pdsParticipant = await GetPDSParticipant(dto.NhsNumber); + + if (pdsParticipant == null) + { + return new BlockParticipantResult(false, "Participant Couldn't be found"); + } + + var pdsRecordsMatch = ValidateRecordsMatch(pdsParticipant, dto); + var pdsResponseBody = JsonSerializer.Serialize(new BlockParticipantDTO + { + NhsNumber = pdsParticipant.NhsNumber, + FamilyName = pdsParticipant.FamilyName, + DateOfBirth = participantDemographic.DateOfBirth + }); + + return new BlockParticipantResult(pdsRecordsMatch, pdsResponseBody); } @@ -140,13 +188,18 @@ private async Task GetPDSParticipant(long nhsNumber) private static bool ValidateRecordsMatch(ParticipantDemographic participant, BlockParticipantDTO dto) { + if (!DateOnly.TryParseExact(dto.DateOfBirth, "yyyy-MM-dd", out var dtoDateOfBirth)) + { + throw new FormatException("Date of Birth not in the correct format"); + } + if (!DateOnly.TryParseExact(participant.DateOfBirth, "yyyyMMdd", out var parsedDob)) { return false; } return string.Equals(participant.FamilyName, dto.FamilyName, StringComparison.InvariantCultureIgnoreCase) && participant.NhsNumber == dto.NhsNumber - && parsedDob == dto.DateOfBirth; + && parsedDob == dtoDateOfBirth; } private static Dictionary CreateNhsNumberQueryParams(long nhsNumber) => diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs index 3d8cbdb070..a298882cab 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs @@ -2,5 +2,6 @@ namespace NHS.CohortManager.ParticipantManagementService; public interface IBlockParticipantHandler { - Task BlockParticipant(BlockParticipantDTO blockParticipantRequest); + Task BlockParticipant(BlockParticipantDTO blockParticipantRequest); + Task GetParticipant(BlockParticipantDTO blockParticipantRequest); } diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs index 51dd77e17b..074d8efc87 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -8,6 +8,7 @@ namespace NHS.CohortManager.ParticipantManagementService; using Common; using System.Net; using Model; +using System.Text.Json; public class UpdateBlockedFlag { @@ -42,18 +43,65 @@ public UpdateBlockedFlag(IDataServiceClient participantMa [Function("BlockParticipant")] public async Task BlockParticipant([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) { - _logger.LogInformation("Block Participant Called"); - var blockParticipantDTO = await req.ReadFromJsonAsync(); - var blockParticipantResult = await _blockParticipantHandler.BlockParticipant(blockParticipantDTO); + _logger.LogInformation("Block Participant Called"); + try + { + var blockParticipantDTO = await req.ReadFromJsonAsync(); + + var blockParticipantResult = await _blockParticipantHandler.BlockParticipant(blockParticipantDTO); - if (blockParticipantResult.Success) + if (blockParticipantResult.Success) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, blockParticipantResult.ResponseMessage); + } + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, blockParticipantResult.ResponseMessage); + } + catch (JsonException jex) { - return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, blockParticipantResult.ResponseMessage); + _logger.LogError(jex, "Participant Block Dto couldn't be deserialized"); + return _createResponse.CreateHttpResponse(HttpStatusCode.BadRequest, req); } - return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, blockParticipantResult.ResponseMessage); + catch (FormatException fex) + { + _logger.LogError(fex, "Participant Block Dto couldn't be deserialized"); + 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); + } + + } + [Function("GetParticipant")] + public async Task GetParticipantDetails([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) + { + _logger.LogInformation("Get Participant Details Called"); + try + { + var blockParticipantDTO = await req.ReadFromJsonAsync(); + var getParticipantResult = await _blockParticipantHandler.GetParticipant(blockParticipantDTO); + + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, getParticipantResult.ResponseMessage); + } + catch (JsonException jex) + { + _logger.LogError(jex, "Participant Block Dto couldn't be deserialized"); + return _createResponse.CreateHttpResponse(HttpStatusCode.BadRequest, req); + } + catch (FormatException fex) + { + _logger.LogError(fex, "Participant Block Dto couldn't be deserialized"); + 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); + } } /// @@ -70,93 +118,10 @@ 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); + return _createResponse.CreateHttpResponse(HttpStatusCode.NotImplemented, req); } - // [Function("PreviewParticipant")] - // public async Task PreviewParticipant([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) - // { - - // } - - private async Task Main(short BlockedFlag, HttpRequestData req) - { - try - { - 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); - - ParticipantManagement participantManagement; - bool blockFlagUpdated; - if (participantDemographic == null && BlockedFlag == 1) - { - // //Search PDS - // //participantManagement = CheckPDS(nhsNumber, dateOfBirth, familyName); //TODO Remove Mock and add call to PDS. - // // If PDS returns non 200 - // if (participantManagement == null) //TODO This will become conclave expression when we can call PDS like a DataService - // { - // throw new KeyNotFoundException("Could not find participant"); - // } - // else - // { - // participantManagement.ScreeningId = 1; - // blockFlagUpdated = await _participantManagementClient.Add(participantManagement); - // } - } - else if (participantDemographic == null && BlockedFlag == 0) - { - throw new KeyNotFoundException("Could not find participant"); - } - else - { - participantManagement = await _participantManagementClient.GetSingleByFilter(i => i.NHSNumber == nhsNumber && i.ScreeningId == 1); // TODO Unhardcode this (Phase 2) - // Change blocked flag - participantManagement.BlockedFlag = BlockedFlag; - blockFlagUpdated = await _participantManagementClient.Update(participantManagement); - } - - if (!true) - throw new HttpRequestException("Failed to update blocked flag"); - - return _createResponse.CreateHttpResponse(HttpStatusCode.OK, req, "Blocked flag updated successfully"); - } - catch (InvalidDataException ex) - { - await HandleException(ex, req); - return _createResponse.CreateHttpResponse(HttpStatusCode.BadRequest, req, "Invalid NHS Number or missing parameters"); - } - catch (KeyNotFoundException ex) - { - 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"); - } - - } - private async Task HandleException(Exception ex, HttpRequestData req) { _logger.LogError(ex, "An error occurred while processing the request for blocking/unblocking a participant"); From 00a155c07a9a50a245b42f7715d273d3d88a792f Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Tue, 5 Aug 2025 15:53:48 +0100 Subject: [PATCH 06/28] unblock participant --- .../BlockParticipantHandler.cs | 41 +++++++++++++++++++ .../IBlockParticipantHandler.cs | 1 + .../UpdateBlockedFlag/UpdateBlockedFlag.cs | 27 +++++++++--- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs index 9cd9a991e0..8ae88e53be 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -72,6 +72,39 @@ public async Task BlockParticipant(BlockParticipantDTO b + } + + public async Task UnblockParticipant(long nhsNumber) + { + if (!ValidationHelper.ValidateNHSNumber(nhsNumber.ToString())) + { + _logger.LogWarning("Invalid NHS Number and cannot be unblocked"); + return new BlockParticipantResult(false, "Invalid NHS Number"); + } + + var participantManagementRecord = await _participantManagementDataService.GetSingleByFilter(x => x.NHSNumber == nhsNumber); + + if (participantManagementRecord.BlockedFlag != 1) + { + _logger.LogInformation("Participant couldn't be unblocked as they are currently blocked"); + return new BlockParticipantResult(false, "Participant is not blocked"); + } + + var nemsSubscribed = await SubscribeParticipantToNEMS(nhsNumber); + if (!nemsSubscribed) + { + return new BlockParticipantResult(false, "Participant couldn't be subscribed in Nems"); + } + + var blockedFlagSet = await SetBlockedFlag(participantManagementRecord, false); + if (!blockedFlagSet) + { + return new BlockParticipantResult(false, "Failed to unset blocked flag"); + } + + return new BlockParticipantResult(true, "Participant Unblocked"); + + } public async Task GetParticipant(BlockParticipantDTO dto) @@ -171,6 +204,14 @@ private async Task UnsubscribeParticipantFromNEMS(long 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; diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs index a298882cab..fbe4caa0d6 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs @@ -4,4 +4,5 @@ public interface IBlockParticipantHandler { Task BlockParticipant(BlockParticipantDTO blockParticipantRequest); Task GetParticipant(BlockParticipantDTO blockParticipantRequest); + Task UnblockParticipant(long nhsNumber); } diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs index 074d8efc87..7fd024544d 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -118,14 +118,29 @@ public async Task GetParticipantDetails([HttpTrigger(Authoriza public async Task UnblockParticipant([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) { _logger.LogInformation("Unblock Participant Called"); - return _createResponse.CreateHttpResponse(HttpStatusCode.NotImplemented, req); - } + var nhsNumber = req.Query["nhsNumber"]; + if (string.IsNullOrWhiteSpace(nhsNumber)) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, "No NHS Number provided"); + } + if (!ValidationHelper.ValidateNHSNumber(nhsNumber)) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, "Invalid NHS Number provided"); + } + + var nhsNumberParsed = long.Parse(nhsNumber); + + var unBlockParticipantResult = await _blockParticipantHandler.UnblockParticipant(nhsNumberParsed); + + if (!unBlockParticipantResult.Success) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, unBlockParticipantResult.ResponseMessage); + } + + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, "Participant successfully unblocked"); - 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()!); } + } From cd0896479e5df241ec3ee5c3f3b36db05782037c Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Tue, 5 Aug 2025 15:54:15 +0100 Subject: [PATCH 07/28] tidying --- .../UpdateBlockedFlag/BlockParticipantHandler.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs index 8ae88e53be..1587e75022 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -1,19 +1,12 @@ namespace NHS.CohortManager.ParticipantManagementService; using System; -using System.Net.Sockets; -using System.Runtime.CompilerServices; -using System.Security.Cryptography.X509Certificates; using System.Text.Json; using Common; using DataServices.Client; -using Microsoft.EntityFrameworkCore.Storage.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.Identity.Client; -using Microsoft.Net.Http.Headers; using Model; -using RulesEngine.Models; public class BlockParticipantHandler : IBlockParticipantHandler { From 897f940d036d790d88393daa1241b0b6901ca8fa Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Tue, 5 Aug 2025 16:13:55 +0100 Subject: [PATCH 08/28] Eligbility flag logic --- .../BlockParticipantHandler.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs index 1587e75022..718a157e13 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -83,11 +83,6 @@ public async Task UnblockParticipant(long nhsNumber) return new BlockParticipantResult(false, "Participant is not blocked"); } - var nemsSubscribed = await SubscribeParticipantToNEMS(nhsNumber); - if (!nemsSubscribed) - { - return new BlockParticipantResult(false, "Participant couldn't be subscribed in Nems"); - } var blockedFlagSet = await SetBlockedFlag(participantManagementRecord, false); if (!blockedFlagSet) @@ -95,6 +90,18 @@ public async Task UnblockParticipant(long nhsNumber) return new BlockParticipantResult(false, "Failed to unset blocked flag"); } + if (participantManagementRecord.EligibilityFlag == 1) + { + 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"); + } + + return new BlockParticipantResult(true, "Participant Unblocked"); From f68f937f0e1970df54070fd5850fb00db5b1adab Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Wed, 6 Aug 2025 09:04:38 +0100 Subject: [PATCH 09/28] Logging and remove rule --- .../UpdateBlockedFlag/BlockParticipantHandler.cs | 4 ++-- .../Breast_Screening_staticRules.json | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs index 718a157e13..4a78ff7d48 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -60,7 +60,7 @@ public async Task BlockParticipant(BlockParticipantDTO b return new BlockParticipantResult(false, "Participant Didn't pass three point check"); } - + _logger.LogInformation("Participant has been blocked"); return await BlockExistingParticipant(participantManagementRecord); @@ -101,7 +101,7 @@ public async Task UnblockParticipant(long nhsNumber) return new BlockParticipantResult(false, "Participant couldn't be subscribed in Nems"); } - + _logger.LogInformation("Participant has been unblocked"); return new BlockParticipantResult(true, "Participant Unblocked"); 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 e8459de7ab..0458ffd175 100644 --- a/application/CohortManager/src/Functions/ScreeningValidationService/StaticValidation/Breast_Screening_staticRules.json +++ b/application/CohortManager/src/Functions/ScreeningValidationService/StaticValidation/Breast_Screening_staticRules.json @@ -185,19 +185,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\"" } } - } + } } ] }, From 45c1f940ec3d308216178b4242eba80cdfc61263 Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Wed, 6 Aug 2025 10:08:48 +0100 Subject: [PATCH 10/28] intial tests --- .../UpdateBlockedFlag/UpdateBlockedFlag.cs | 8 +- .../UpdateBlockedFlagTests.cs | 230 ++---------------- 2 files changed, 23 insertions(+), 215 deletions(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs index 7fd024544d..a2a008cf3a 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -12,21 +12,15 @@ namespace NHS.CohortManager.ParticipantManagementService; 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; - public UpdateBlockedFlag(IDataServiceClient participantManagementClient, IDataServiceClient participantDemographicClient, ILogger logger, ICreateResponse createResponse, IExceptionHandler exceptionHandler, IBlockParticipantHandler blockParticipantHandler) + public UpdateBlockedFlag(ILogger logger, ICreateResponse createResponse, IBlockParticipantHandler blockParticipantHandler) { - _participantManagementClient = participantManagementClient; - _participantDemographicClient = participantDemographicClient; _logger = logger; _createResponse = createResponse; - _exceptionHandler = exceptionHandler; _blockParticipantHandler = blockParticipantHandler; } diff --git a/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs b/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs index f9f50f68a0..2de22138fd 100644 --- a/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs +++ b/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs @@ -11,223 +11,37 @@ 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; [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(); 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) => - { - var response = req.CreateResponse(statusCode); - response.Headers.Add("Content-Type", "application/json; charset=utf-8"); - response.WriteString(ResponseBody); - return response; - }); - - _createResponseMock.Setup(x => x.CreateHttpResponseWithBodyAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(async (HttpStatusCode statusCode, HttpRequestData req, string ResponseBody) => - { - var response = req.CreateResponse(statusCode); - response.Headers.Add("Content-Type", "application/json; charset=utf-8"); - await response.WriteStringAsync(ResponseBody); - return response; - }); - - _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())) - .ReturnsAsync(true); - - - _sut = new UpdateBlockedFlag(_dsMockParticipantManagement.Object, _dsMockParticipantDemographic.Object, _loggerMock.Object, _createResponseMock.Object, _exceptionHandler.Object); - } - - [TestMethod] - public async Task BlockParticipant_ValidRequest_UpdateBlockedFlag() - { - // Arrange - var request = _setupRequest.Setup(""); - request.Setup(r => r.Query).Returns(queryParams); - - // Act - var response = await _sut.BlockParticipant(request.Object); - - // Assert - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - _dsMockParticipantManagement - .Verify(x => x.Update(It.Is(pm => pm.BlockedFlag == 1)), Times.Once); - } - - [TestMethod] - public async Task UnblockParticipant_ValidRequest_UpdateBlockedFlag() - { - // Arrange - var request = _setupRequest.Setup(""); - request.Setup(r => r.Query).Returns(queryParams); - - // Act - var response = await _sut.UnblockParticipant(request.Object); - - // Assert - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - _dsMockParticipantManagement - .Verify(x => x.Update(It.Is(pm => pm.BlockedFlag == 0)), Times.Once); - } - - [TestMethod] - public async Task BlockParticipant_ParticipantDoesNotExistInDemographicTable_ReturnNotFound() - { - // 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); + 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); } - [TestMethod] - public async Task BlockParticipant_UpdateFails_ReturnInternalServerError() - { - // 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); - } - - [TestMethod] - public async Task BlockParticipant_NoResponseParticipantManagement_ReturnInternalServerError() - { - // 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); - } - - [TestMethod] - public async Task BlockParticipant_InvalidNHSNumber_ReturnBadRequest() - { - // Arrange - var request = _setupRequest.Setup(""); - var queryParams = new NameValueCollection - { - { "NhsNumber", "1234567890"}, - { "ScreeningId", "1"}, - { "DateOfBirth", "01/01/2000"}, - { "LastName", "Smith"} - }; - - 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.BadRequest, response.StatusCode); - } - - [TestMethod] - public async Task BlockParticipant_MissingQueryParams_ReturnBadRequest() - { - // Arrange - var request = _setupRequest.Setup(""); - var queryParams = new NameValueCollection - { - { "NhsNumber", "8253303483"}, - { "ScreeningId", "1"}, - { "DateOfBirth", "01/01/2000"} - }; - - 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.BadRequest, response.StatusCode); - } -} \ No newline at end of file +} From b1f41141edc41a2cda9cea41d98b4f3b170924bc Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Wed, 6 Aug 2025 10:34:04 +0100 Subject: [PATCH 11/28] tfvars and compose --- application/CohortManager/compose.core.yaml | 3 +++ .../ManageNemsSubscriptionConfig.cs | 8 -------- .../tf-core/environments/development.tfvars | 14 ++++++++++++++ .../tf-core/environments/integration.tfvars | 14 ++++++++++++++ infrastructure/tf-core/environments/nft.tfvars | 14 ++++++++++++++ infrastructure/tf-core/environments/preprod.tfvars | 14 ++++++++++++++ .../tf-core/environments/production.tfvars | 14 ++++++++++++++ infrastructure/tf-core/environments/sandbox.tfvars | 14 ++++++++++++++ 8 files changed, 87 insertions(+), 8 deletions(-) diff --git a/application/CohortManager/compose.core.yaml b/application/CohortManager/compose.core.yaml index ae992dcf77..1fe7aa9d2d 100644 --- a/application/CohortManager/compose.core.yaml +++ b/application/CohortManager/compose.core.yaml @@ -137,6 +137,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/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs b/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs index 2dc0511d42..f5b99653bb 100644 --- a/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs +++ b/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs @@ -84,14 +84,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) diff --git a/infrastructure/tf-core/environments/development.tfvars b/infrastructure/tf-core/environments/development.tfvars index 43b8ab75b7..47496b1f35 100644 --- a/infrastructure/tf-core/environments/development.tfvars +++ b/infrastructure/tf-core/environments/development.tfvars @@ -418,6 +418,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 35afa0c9d6..30837e89c8 100644 --- a/infrastructure/tf-core/environments/integration.tfvars +++ b/infrastructure/tf-core/environments/integration.tfvars @@ -418,6 +418,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 b686bcdb1d..497e923b9d 100644 --- a/infrastructure/tf-core/environments/nft.tfvars +++ b/infrastructure/tf-core/environments/nft.tfvars @@ -417,6 +417,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 d8f6eee494..70640debca 100644 --- a/infrastructure/tf-core/environments/preprod.tfvars +++ b/infrastructure/tf-core/environments/preprod.tfvars @@ -442,6 +442,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 43dbd5beba..81b40bff3c 100644 --- a/infrastructure/tf-core/environments/production.tfvars +++ b/infrastructure/tf-core/environments/production.tfvars @@ -409,6 +409,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 971ab5c27f..830bdaa50a 100644 --- a/infrastructure/tf-core/environments/sandbox.tfvars +++ b/infrastructure/tf-core/environments/sandbox.tfvars @@ -534,6 +534,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" } ] } From 22c8b8420130fa060be769079cd32873993aec6a Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Wed, 6 Aug 2025 10:43:26 +0100 Subject: [PATCH 12/28] function signature overlap --- .../Common/Utilities/HTTPStubUtilities.cs | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/application/CohortManager/src/Functions/Shared/Common/Utilities/HTTPStubUtilities.cs b/application/CohortManager/src/Functions/Shared/Common/Utilities/HTTPStubUtilities.cs index 0b2bd1da8b..b187d914b2 100644 --- a/application/CohortManager/src/Functions/Shared/Common/Utilities/HTTPStubUtilities.cs +++ b/application/CohortManager/src/Functions/Shared/Common/Utilities/HTTPStubUtilities.cs @@ -4,26 +4,6 @@ namespace Common; public static class HTTPStubUtilities { - - /// - /// takes in a fake string content and returns 200 OK response - /// - /// - /// - /// - public static HttpResponseMessage CreateFakeHttpResponse(string url, string content = "") - { - var HttpResponseData = new HttpResponseMessage(); - if (string.IsNullOrEmpty(url)) - { - HttpResponseData.StatusCode = HttpStatusCode.InternalServerError; - return HttpResponseData; - } - - HttpResponseData.Content = new StringContent(content); - HttpResponseData.StatusCode = HttpStatusCode.OK; - return HttpResponseData; - } /// /// takes in a fake string content and returns 200 OK response or a Given Response /// From 6dba11515ad72b570cc42c80622fe5fe27722d40 Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Wed, 6 Aug 2025 10:52:40 +0100 Subject: [PATCH 13/28] some sonar qube fixes --- .../ManageNemsSubscriptionConfig.cs | 2 +- .../UpdateBlockedFlag/BlockParticipantDTO.cs | 2 +- .../BlockParticipantHandler.cs | 40 ++++++++----------- .../IBlockParticipantHandler.cs | 4 +- .../UpdateBlockedFlag/UpdateBlockedFlag.cs | 4 +- 5 files changed, 23 insertions(+), 29 deletions(-) diff --git a/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs b/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs index f5b99653bb..089fe3bf8e 100644 --- a/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs +++ b/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs @@ -97,6 +97,6 @@ public class ManageNemsSubscriptionConfig /// /// Bool to set the function to be in stubbed mode. Simulated responses from NEMS /// - public bool IsStubbed = true; + public bool IsStubbed { get; set; } = true; } diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantDTO.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantDTO.cs index 139074e35c..57071a994b 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantDTO.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantDTO.cs @@ -1,6 +1,6 @@ namespace NHS.CohortManager.ParticipantManagementService; -public class BlockParticipantDTO +public class BlockParticipantDto { public required long NhsNumber { get; set; } public required string DateOfBirth { get; set; } diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs index 4a78ff7d48..b7d07b0fdb 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -29,7 +29,7 @@ IOptions config _config = config.Value; } - public async Task BlockParticipant(BlockParticipantDTO blockParticipantRequest) + public async Task BlockParticipant(BlockParticipantDto blockParticipantRequest) { if (!ValidationHelper.ValidateNHSNumber(blockParticipantRequest.NhsNumber.ToString())) { @@ -60,7 +60,7 @@ public async Task BlockParticipant(BlockParticipantDTO b return new BlockParticipantResult(false, "Participant Didn't pass three point check"); } - _logger.LogInformation("Participant has been blocked"); + _logger.LogInformation("Participant has been blocked"); return await BlockExistingParticipant(participantManagementRecord); @@ -107,49 +107,49 @@ public async Task UnblockParticipant(long nhsNumber) } - public async Task GetParticipant(BlockParticipantDTO dto) + public async Task GetParticipant(BlockParticipantDto blockParticipantRequest) { - if (!ValidationHelper.ValidateNHSNumber(dto.NhsNumber.ToString())) + 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 == dto.NhsNumber); + var participantDemographic = await _participantDemographicDataService.GetSingleByFilter(x => x.NhsNumber == blockParticipantRequest.NhsNumber); if (participantDemographic != null) { - var recordsMatch = ValidateRecordsMatch(participantDemographic, dto); - var responseBody = JsonSerializer.Serialize(new BlockParticipantDTO + var recordsMatch = ValidateRecordsMatch(participantDemographic, blockParticipantRequest); + var responseBody = JsonSerializer.Serialize(new BlockParticipantDto { NhsNumber = participantDemographic.NhsNumber, - FamilyName = participantDemographic.FamilyName, - DateOfBirth = participantDemographic.DateOfBirth, + FamilyName = participantDemographic.FamilyName!, + DateOfBirth = participantDemographic.DateOfBirth!, }); return new BlockParticipantResult(recordsMatch, responseBody); } - var pdsParticipant = await GetPDSParticipant(dto.NhsNumber); + var pdsParticipant = await GetPDSParticipant(blockParticipantRequest.NhsNumber); if (pdsParticipant == null) { return new BlockParticipantResult(false, "Participant Couldn't be found"); } - var pdsRecordsMatch = ValidateRecordsMatch(pdsParticipant, dto); - var pdsResponseBody = JsonSerializer.Serialize(new BlockParticipantDTO + var pdsRecordsMatch = ValidateRecordsMatch(pdsParticipant, blockParticipantRequest); + var pdsResponseBody = JsonSerializer.Serialize(new BlockParticipantDto { NhsNumber = pdsParticipant.NhsNumber, - FamilyName = pdsParticipant.FamilyName, - DateOfBirth = participantDemographic.DateOfBirth + FamilyName = pdsParticipant.FamilyName!, + DateOfBirth = pdsParticipant.DateOfBirth! }); return new BlockParticipantResult(pdsRecordsMatch, pdsResponseBody); } - private async Task BlockNewParticipant(BlockParticipantDTO blockParticipantRequest) + private async Task BlockNewParticipant(BlockParticipantDto blockParticipantRequest) { var pdsParticipant = await GetPDSParticipant(blockParticipantRequest.NhsNumber); @@ -223,10 +223,10 @@ private async Task GetPDSParticipant(long nhsNumber) var pdsResponse = await _httpClient.SendGet(_config.RetrievePdsDemographicURL, CreateNhsNumberQueryParams(nhsNumber)); var pdsDemographic = JsonSerializer.Deserialize(pdsResponse); - return pdsDemographic; + return pdsDemographic!; } - private static bool ValidateRecordsMatch(ParticipantDemographic participant, BlockParticipantDTO dto) + private static bool ValidateRecordsMatch(ParticipantDemographic participant, BlockParticipantDto dto) { if (!DateOnly.TryParseExact(dto.DateOfBirth, "yyyy-MM-dd", out var dtoDateOfBirth)) @@ -249,10 +249,4 @@ private static Dictionary CreateNhsNumberQueryParams(long nhsNum {"nhsNumber",nhsNumber.ToString()} }; - - - - - - } diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs index fbe4caa0d6..4870dd018d 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/IBlockParticipantHandler.cs @@ -2,7 +2,7 @@ namespace NHS.CohortManager.ParticipantManagementService; public interface IBlockParticipantHandler { - Task BlockParticipant(BlockParticipantDTO blockParticipantRequest); - Task GetParticipant(BlockParticipantDTO blockParticipantRequest); + Task BlockParticipant(BlockParticipantDto blockParticipantRequest); + Task GetParticipant(BlockParticipantDto blockParticipantRequest); Task UnblockParticipant(long nhsNumber); } diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs index a2a008cf3a..bed53b4f0d 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -42,7 +42,7 @@ public async Task BlockParticipant([HttpTrigger(AuthorizationL _logger.LogInformation("Block Participant Called"); try { - var blockParticipantDTO = await req.ReadFromJsonAsync(); + var blockParticipantDTO = await req.ReadFromJsonAsync(); var blockParticipantResult = await _blockParticipantHandler.BlockParticipant(blockParticipantDTO); @@ -75,7 +75,7 @@ public async Task GetParticipantDetails([HttpTrigger(Authoriza _logger.LogInformation("Get Participant Details Called"); try { - var blockParticipantDTO = await req.ReadFromJsonAsync(); + var blockParticipantDTO = await req.ReadFromJsonAsync(); var getParticipantResult = await _blockParticipantHandler.GetParticipant(blockParticipantDTO); return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, getParticipantResult.ResponseMessage); From a6f032841640d1138456055a2a2db6321a6768ec Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Wed, 6 Aug 2025 12:57:07 +0100 Subject: [PATCH 14/28] more sonar qube fixes --- .../UpdateBlockedFlag/BlockParticipantHandler.cs | 5 +++-- .../UpdateBlockedFlag/UpdateBlockedFlag.cs | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs index b7d07b0fdb..3d7a3900d8 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -1,6 +1,7 @@ namespace NHS.CohortManager.ParticipantManagementService; using System; +using System.Globalization; using System.Text.Json; using Common; using DataServices.Client; @@ -229,12 +230,12 @@ private async Task GetPDSParticipant(long nhsNumber) private static bool ValidateRecordsMatch(ParticipantDemographic participant, BlockParticipantDto dto) { - if (!DateOnly.TryParseExact(dto.DateOfBirth, "yyyy-MM-dd", out var dtoDateOfBirth)) + 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", out var parsedDob)) + if (!DateOnly.TryParseExact(participant.DateOfBirth, "yyyyMMdd",new CultureInfo("en-GB"),DateTimeStyles.None, out var parsedDob)) { return false; } diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs index bed53b4f0d..96d6a629ea 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -44,13 +44,18 @@ public async Task BlockParticipant([HttpTrigger(AuthorizationL { var blockParticipantDTO = await req.ReadFromJsonAsync(); + if (blockParticipantDTO == null) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, "Unable to parse request"); + } + var blockParticipantResult = await _blockParticipantHandler.BlockParticipant(blockParticipantDTO); if (blockParticipantResult.Success) { - return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, blockParticipantResult.ResponseMessage); + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, blockParticipantResult.ResponseMessage!); } - return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, blockParticipantResult.ResponseMessage); + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, blockParticipantResult.ResponseMessage!); } catch (JsonException jex) { @@ -76,6 +81,12 @@ public async Task GetParticipantDetails([HttpTrigger(Authoriza try { var blockParticipantDTO = await req.ReadFromJsonAsync(); + + if (blockParticipantDTO == null) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, "Unable to parse request"); + } + var getParticipantResult = await _blockParticipantHandler.GetParticipant(blockParticipantDTO); return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, getParticipantResult.ResponseMessage); From 1536ba8f8e1b7eca22435f17eaa780b95f610357 Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Wed, 6 Aug 2025 13:04:57 +0100 Subject: [PATCH 15/28] more sonar qube fixes --- .../Functions/Shared/Common/HttpClientFunction.cs | 2 +- .../Shared/Common/HttpClientFunctionMock.cs | 14 +++++++------- .../Shared/Common/StubbedNemsHttpClientFunction.cs | 4 ++-- .../Shared/Common/Utilities/HTTPStubUtilities.cs | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/application/CohortManager/src/Functions/Shared/Common/HttpClientFunction.cs b/application/CohortManager/src/Functions/Shared/Common/HttpClientFunction.cs index cf81bef98c..085e532992 100644 --- a/application/CohortManager/src/Functions/Shared/Common/HttpClientFunction.cs +++ b/application/CohortManager/src/Functions/Shared/Common/HttpClientFunction.cs @@ -41,7 +41,7 @@ public async Task SendPost(string url, Dictionary SendPost(string url, string data) { await Task.CompletedTask; - return HTTPStubUtilities.CreateFakeHttpResponse(url,""); + return HttpStubUtilities.CreateFakeHttpResponse(url,""); + } + public Task SendPost(string url, Dictionary parameters) + { + throw new NotImplementedException(); } public async Task SendGet(string url, Dictionary parameters) @@ -38,7 +42,7 @@ public async Task SendPdsGet(string url, string bearerToken { var patient = GetPatientMockObject("complete-patient.json"); await Task.CompletedTask; - return HTTPStubUtilities.CreateFakeHttpResponse(url, patient); + return HttpStubUtilities.CreateFakeHttpResponse(url, patient); } private static string GetPatientMockObject(string filename) @@ -59,7 +63,7 @@ private static string GetPatientMockObject(string filename) public async Task SendPut(string url, string data) { await Task.CompletedTask; - return HTTPStubUtilities.CreateFakeHttpResponse(url,""); + return HttpStubUtilities.CreateFakeHttpResponse(url,""); } public async Task SendDelete(string url) @@ -79,8 +83,4 @@ public async Task SendGetResponse(string url) throw new NotImplementedException(); } - public Task SendPost(string url, Dictionary parameters) - { - throw new NotImplementedException(); - } } diff --git a/application/CohortManager/src/Functions/Shared/Common/StubbedNemsHttpClientFunction.cs b/application/CohortManager/src/Functions/Shared/Common/StubbedNemsHttpClientFunction.cs index 1d8b9c6d97..cd254262d2 100644 --- a/application/CohortManager/src/Functions/Shared/Common/StubbedNemsHttpClientFunction.cs +++ b/application/CohortManager/src/Functions/Shared/Common/StubbedNemsHttpClientFunction.cs @@ -44,14 +44,14 @@ public string GenerateJwtToken(string asid, string audience, string scope) public async Task SendSubscriptionDelete(NemsSubscriptionRequest request, int timeoutSeconds = 300) { await Task.CompletedTask; - return HTTPStubUtilities.CreateFakeHttpResponse(request.Url, ""); + 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)); + 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 index b187d914b2..2411157367 100644 --- a/application/CohortManager/src/Functions/Shared/Common/Utilities/HTTPStubUtilities.cs +++ b/application/CohortManager/src/Functions/Shared/Common/Utilities/HTTPStubUtilities.cs @@ -2,7 +2,7 @@ namespace Common; using System.Net; -public static class HTTPStubUtilities +public static class HttpStubUtilities { /// /// takes in a fake string content and returns 200 OK response or a Given Response From 52ee1032d9dfb4d06421f2524ff9b3faa6aa56f2 Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Wed, 6 Aug 2025 13:30:29 +0100 Subject: [PATCH 16/28] more sonar qube --- .../UpdateBlockedFlag/UpdateBlockedFlag.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs index 96d6a629ea..3cdb924185 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -9,6 +9,7 @@ namespace NHS.CohortManager.ParticipantManagementService; using System.Net; using Model; using System.Text.Json; +using System.Reflection.Metadata; public class UpdateBlockedFlag { @@ -16,6 +17,8 @@ public class UpdateBlockedFlag private readonly ICreateResponse _createResponse; private readonly IBlockParticipantHandler _blockParticipantHandler; + private const string cannotBeDeserializedMessage = "Participant Block Dto couldn't be deserialized"; + public UpdateBlockedFlag(ILogger logger, ICreateResponse createResponse, IBlockParticipantHandler blockParticipantHandler) { @@ -59,12 +62,12 @@ public async Task BlockParticipant([HttpTrigger(AuthorizationL } catch (JsonException jex) { - _logger.LogError(jex, "Participant Block Dto couldn't be deserialized"); + _logger.LogError(jex, cannotBeDeserializedMessage); return _createResponse.CreateHttpResponse(HttpStatusCode.BadRequest, req); } catch (FormatException fex) { - _logger.LogError(fex, "Participant Block Dto couldn't be deserialized"); + _logger.LogError(fex, cannotBeDeserializedMessage); return _createResponse.CreateHttpResponse(HttpStatusCode.BadRequest, req); } catch (Exception ex) @@ -89,17 +92,17 @@ public async Task GetParticipantDetails([HttpTrigger(Authoriza var getParticipantResult = await _blockParticipantHandler.GetParticipant(blockParticipantDTO); - return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, getParticipantResult.ResponseMessage); + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, getParticipantResult.ResponseMessage!); } catch (JsonException jex) { - _logger.LogError(jex, "Participant Block Dto couldn't be deserialized"); + _logger.LogError(jex, cannotBeDeserializedMessage); return _createResponse.CreateHttpResponse(HttpStatusCode.BadRequest, req); } catch (FormatException fex) { - _logger.LogError(fex, "Participant Block Dto couldn't be deserialized"); + _logger.LogError(fex, cannotBeDeserializedMessage); return _createResponse.CreateHttpResponse(HttpStatusCode.BadRequest, req); } catch (Exception ex) @@ -140,7 +143,7 @@ public async Task UnblockParticipant([HttpTrigger(Authorizatio if (!unBlockParticipantResult.Success) { - return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, unBlockParticipantResult.ResponseMessage); + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, unBlockParticipantResult.ResponseMessage!); } return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, "Participant successfully unblocked"); From bdd0dddf36c04f6fab90647120d3aff4dd3092ba Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Wed, 6 Aug 2025 14:16:04 +0100 Subject: [PATCH 17/28] tests part one --- .../UpdateBlockedFlagTests.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs b/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs index 2de22138fd..ab59fd9e14 100644 --- a/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs +++ b/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs @@ -14,6 +14,7 @@ namespace NHS.CohortManager.Tests.ParticipantManagementServiceTests; using RulesEngine.Models; using System.Runtime.CompilerServices; using Microsoft.Extensions.Options; +using System.Text.Json; [TestClass] public class UpdateBlockedFlagTests @@ -27,6 +28,8 @@ public class UpdateBlockedFlagTests private readonly BlockParticipantHandler _blockParticipantHandler; private readonly Mock _mockHttpClient = new(); private readonly Mock> _mockConfig = new(); + private Mock _request; + private readonly SetupRequest _setupRequest = new(); public UpdateBlockedFlagTests() { @@ -43,5 +46,44 @@ public UpdateBlockedFlagTests() _sut = new UpdateBlockedFlag(_mockUpdateBlockedFlagLogger.Object, _mockCreateResponse.Object, _blockParticipantHandler); } + [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)); + + _mockParticipantManagementClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .ReturnsAsync(new ParticipantManagement + { + NHSNumber = 6427635034, + BlockedFlag = 0, + + }); + + _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); + + //asset + Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); + + } + + } From b8cb0f1ca1691ff7e303eba5b5335f9c91a6570c Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Wed, 6 Aug 2025 14:17:47 +0100 Subject: [PATCH 18/28] Correct Logging from PR Comment Co-authored-by: Sam Ainsworth --- .../UpdateBlockedFlag/BlockParticipantHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs index 3d7a3900d8..5df1ef3c5b 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -57,7 +57,7 @@ public async Task BlockParticipant(BlockParticipantDto b if (!ValidateRecordsMatch(participantDemographic, blockParticipantRequest)) { - _logger.LogWarning("Participant had an didn't pass three point check and cannot be blocked"); + _logger.LogWarning("Participant didn't pass three point check and cannot be blocked"); return new BlockParticipantResult(false, "Participant Didn't pass three point check"); } @@ -80,7 +80,7 @@ public async Task UnblockParticipant(long nhsNumber) if (participantManagementRecord.BlockedFlag != 1) { - _logger.LogInformation("Participant couldn't be unblocked as they are currently blocked"); + _logger.LogInformation("Participant couldn't be unblocked as they are not currently blocked"); return new BlockParticipantResult(false, "Participant is not blocked"); } From 2379d506dc382ec8f2be2977c4a5a626ff155269 Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Wed, 6 Aug 2025 14:19:12 +0100 Subject: [PATCH 19/28] Addressing comments --- .../ManageNemsSubscription/ManageNemsSubscriptionConfig.cs | 2 +- .../UpdateBlockedFlag/UpdateBlockedFlag.cs | 4 ---- .../UpdateBlockedFlag/UpdateBlockedFlagConfig.cs | 2 -- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs b/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs index 089fe3bf8e..fe7bd86acc 100644 --- a/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs +++ b/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs @@ -97,6 +97,6 @@ public class ManageNemsSubscriptionConfig /// /// Bool to set the function to be in stubbed mode. Simulated responses from NEMS /// - public bool IsStubbed { get; set; } = true; + public bool IsStubbed { get; set; } = false; } diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs index 3cdb924185..71b8cebb59 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -3,13 +3,9 @@ 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 System.Reflection.Metadata; public class UpdateBlockedFlag { diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlagConfig.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlagConfig.cs index 9ae9153bf3..ecef7df80c 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlagConfig.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlagConfig.cs @@ -1,8 +1,6 @@ namespace NHS.CohortManager.ParticipantManagementService; using System.ComponentModel.DataAnnotations; -using Azure.Core; -using Microsoft.Azure.Amqp; public class UpdateBlockedFlagConfig { From d32e4dfcfd26b8cbe87850302c0752c8247e8eb7 Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Wed, 6 Aug 2025 14:25:50 +0100 Subject: [PATCH 20/28] Incorrect Logic comment --- .../UpdateBlockedFlag/BlockParticipantHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs index 5df1ef3c5b..d2af39d5a1 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -91,7 +91,7 @@ public async Task UnblockParticipant(long nhsNumber) return new BlockParticipantResult(false, "Failed to unset blocked flag"); } - if (participantManagementRecord.EligibilityFlag == 1) + if (participantManagementRecord.EligibilityFlag == 0) { return new BlockParticipantResult(true, "Participant was unblocked but not resubscribed to Nems as they are ineligible"); } From 973ed23036e064681428ef0926192db9d1a6c8aa Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Wed, 6 Aug 2025 15:58:55 +0100 Subject: [PATCH 21/28] Block Participant Tests --- .../UpdateBlockedFlag/UpdateBlockedFlag.cs | 4 +- .../UpdateBlockedFlagTests.cs | 197 ++++++++++++++++++ 2 files changed, 199 insertions(+), 2 deletions(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs index 71b8cebb59..3fe6ca1ef4 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -41,8 +41,8 @@ public async Task BlockParticipant([HttpTrigger(AuthorizationL _logger.LogInformation("Block Participant Called"); try { - var blockParticipantDTO = await req.ReadFromJsonAsync(); - + var blockParticipantDTOJson = req.ReadAsString(); + var blockParticipantDTO = JsonSerializer.Deserialize(blockParticipantDTOJson); if (blockParticipantDTO == null) { return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, "Unable to parse request"); diff --git a/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs b/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs index ab59fd9e14..a5adb25e69 100644 --- a/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs +++ b/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs @@ -15,6 +15,7 @@ namespace NHS.CohortManager.Tests.ParticipantManagementServiceTests; using System.Runtime.CompilerServices; using Microsoft.Extensions.Options; using System.Text.Json; +using System.Text; [TestClass] public class UpdateBlockedFlagTests @@ -44,6 +45,17 @@ public UpdateBlockedFlagTests() }); _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.WriteString(responseBody); + return Task.FromResult(response); + }); } [TestMethod] @@ -66,6 +78,13 @@ public async Task BlockParticipant_ExistingParticipant_ReturnsSuccess() BlockedFlag = 0, }); + _mockParticipantDemographicClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .ReturnsAsync(new ParticipantDemographic + { + NhsNumber = 6427635034, + FamilyName = "Jones", + DateOfBirth = "19231012" + }); _mockParticipantManagementClient.Setup(x => x.Update(It.IsAny())) .ReturnsAsync(true); @@ -81,7 +100,185 @@ public async Task BlockParticipant_ExistingParticipant_ReturnsSuccess() //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_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.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 BlockParticipant_ExistingParticipantFailsThreePointCheck_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>>())) + .ReturnsAsync(new ParticipantManagement + { + NHSNumber = 6427635034, + BlockedFlag = 0, + }); + _mockParticipantDemographicClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .ReturnsAsync(new ParticipantDemographic + { + NhsNumber = 6427635034, + FamilyName = "Davies", + DateOfBirth = "19231012" + }); + + //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(); } From 7e79391af9cfbc94a14623502fee3a9e55f6c316 Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Wed, 6 Aug 2025 16:03:17 +0100 Subject: [PATCH 22/28] Addressing Comments --- .../UpdateBlockedFlag/BlockParticipantHandler.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs index d2af39d5a1..128c50a37a 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -78,6 +78,11 @@ 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"); From d22b07d7dd398cdd6b8a3b376a2e1eef4c690efa Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Wed, 6 Aug 2025 16:17:54 +0100 Subject: [PATCH 23/28] Sonar Qube --- .../UpdateBlockedFlag/UpdateBlockedFlag.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs index 3fe6ca1ef4..5c16a7ce8a 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -41,7 +41,12 @@ public async Task BlockParticipant([HttpTrigger(AuthorizationL _logger.LogInformation("Block Participant Called"); try { - var blockParticipantDTOJson = req.ReadAsString(); + var blockParticipantDTOJson = await req.ReadAsStringAsync(); + if (string.IsNullOrWhiteSpace(blockParticipantDTOJson)) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, "Unable to parse request"); + } + var blockParticipantDTO = JsonSerializer.Deserialize(blockParticipantDTOJson); if (blockParticipantDTO == null) { @@ -73,6 +78,13 @@ public async Task BlockParticipant([HttpTrigger(AuthorizationL } } + /// + /// 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) { From 801ea5529ae836d04d2cafa714ab176752ec40b6 Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Thu, 7 Aug 2025 12:50:39 +0100 Subject: [PATCH 24/28] Tests --- .../BlockParticipantHandler.cs | 14 +- .../UpdateBlockedFlag/UpdateBlockedFlag.cs | 36 ++- .../UpdateBlockedFlagTests.cs | 266 +++++++++++++++++- 3 files changed, 304 insertions(+), 12 deletions(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs index 128c50a37a..47fd72d4a6 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -5,6 +5,7 @@ namespace NHS.CohortManager.ParticipantManagementService; using System.Text.Json; using Common; using DataServices.Client; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Model; @@ -70,17 +71,12 @@ public async Task BlockParticipant(BlockParticipantDto b public async Task UnblockParticipant(long nhsNumber) { - if (!ValidationHelper.ValidateNHSNumber(nhsNumber.ToString())) - { - _logger.LogWarning("Invalid NHS Number and cannot be unblocked"); - return new BlockParticipantResult(false, "Invalid NHS Number"); - } var participantManagementRecord = await _participantManagementDataService.GetSingleByFilter(x => x.NHSNumber == nhsNumber); if (participantManagementRecord == null) { - return new BlockParticipantResult(false, "Participant couldn't be found"); + return new BlockParticipantResult(false, "Participant Couldn't be found"); } if (participantManagementRecord.BlockedFlag != 1) @@ -227,6 +223,12 @@ private async Task SetBlockedFlag(ParticipantManagement participant, bool 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!; diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs index 5c16a7ce8a..cd47bf8cd4 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -6,6 +6,7 @@ namespace NHS.CohortManager.ParticipantManagementService; using Common; using System.Net; using System.Text.Json; +using Azure.Messaging.EventGrid.SystemEvents; public class UpdateBlockedFlag { @@ -91,8 +92,13 @@ public async Task GetParticipantDetails([HttpTrigger(Authoriza _logger.LogInformation("Get Participant Details Called"); try { - var blockParticipantDTO = await req.ReadFromJsonAsync(); + var blockParticipantDTOJson = await req.ReadAsStringAsync(); + if (string.IsNullOrWhiteSpace(blockParticipantDTOJson)) + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, "Unable to parse request"); + } + var blockParticipantDTO = JsonSerializer.Deserialize(blockParticipantDTOJson); if (blockParticipantDTO == null) { return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, "Unable to parse request"); @@ -100,7 +106,21 @@ public async Task GetParticipantDetails([HttpTrigger(Authoriza var getParticipantResult = await _blockParticipantHandler.GetParticipant(blockParticipantDTO); - return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, getParticipantResult.ResponseMessage!); + 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, "Participant Not found"); + } + 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) @@ -149,12 +169,18 @@ public async Task UnblockParticipant([HttpTrigger(Authorizatio var unBlockParticipantResult = await _blockParticipantHandler.UnblockParticipant(nhsNumberParsed); - if (!unBlockParticipantResult.Success) + if (unBlockParticipantResult.Success) { - return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, unBlockParticipantResult.ResponseMessage!); + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, "Participant successfully unblocked"); } + if (unBlockParticipantResult.ResponseMessage == "Participant Couldn't be found") + { + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.NotFound, req, "Participant Not found"); + } + + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, unBlockParticipantResult.ResponseMessage!); + - return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.OK, req, "Participant successfully unblocked"); } diff --git a/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs b/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs index a5adb25e69..84e69b5763 100644 --- a/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs +++ b/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs @@ -238,7 +238,7 @@ public async Task BlockParticipant_ExistingParticipantFailsThreePointCheck_Retur _mockParticipantDemographicClient.Verify(x => x.GetSingleByFilter(It.IsAny>>()), Times.Once); _mockParticipantDemographicClient.VerifyNoOtherCalls(); } - [TestMethod] + [TestMethod] public async Task BlockParticipant_NonExistentParticipantFailsThreePointCheck_ReturnsFailure() { //arrange @@ -280,6 +280,270 @@ public async Task BlockParticipant_NonExistentParticipantFailsThreePointCheck_Re _mockParticipantManagementClient.VerifyNoOtherCalls(); _mockHttpClient.VerifyNoOtherCalls(); } + [TestMethod] + public async Task GetParticipant_ParticipantExistsInCM_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>>())) + .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 GetParticipant_ParticipantNotExists_ReturnsFailure() + { + //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 UnblockParticipant_ParticipantIsBlocked_ReturnsSuccess() + { + //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] + [DataRow("6427635035")] + [DataRow("642763")] + [DataRow("abs7635035")] + public async Task UnblockParticipant_InvalidNhsNumber_ReturnsFailure(string nhsNumber) + { + //arrange + var queryParams = new NameValueCollection + { + {"nhsNumber",nhsNumber} + }; + _request = _setupRequest.Setup("", queryParams, HttpMethod.Post); + + //act + var result = await _sut.UnblockParticipant(_request.Object); + + //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 UnblockParticipant_ParticipantNotBlocked_ReturnBadRequest() + { + //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 = 0, + EligibilityFlag = 1 + }); + + + //act + var result = await _sut.UnblockParticipant(_request.Object); + + //asset + Assert.AreEqual(HttpStatusCode.BadRequest, result.StatusCode); + _mockParticipantManagementClient.Verify(x => x.GetSingleByFilter(It.IsAny>>()), Times.Once); + _mockParticipantManagementClient.VerifyNoOtherCalls(); + _mockHttpClient.VerifyNoOtherCalls(); + } + // [TestMethod] + // public async Task UnblockParticipant_ParticipantNotBlocked_ReturnBadRequest() + // { + // //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 = 0, + // EligibilityFlag = 1 + // }); + + + // //act + // var result = await _sut.UnblockParticipant(_request.Object); + + // //asset + // Assert.AreEqual(HttpStatusCode.BadRequest, result.StatusCode); + // _mockParticipantManagementClient.Verify(x => x.GetSingleByFilter(It.IsAny>>()), Times.Once); + // _mockParticipantManagementClient.VerifyNoOtherCalls(); + // _mockHttpClient.VerifyNoOtherCalls(); + // } + From 6ace84be3441fb4fec78e90782f991d302802965 Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Thu, 7 Aug 2025 12:51:14 +0100 Subject: [PATCH 25/28] remove duplicate test --- .../UpdateBlockedFlagTests.cs | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs b/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs index 84e69b5763..217a4a6238 100644 --- a/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs +++ b/tests/UnitTests/ParticipantManagementServicesTests/UpdateBlockedFlagTests/UpdateBlockedFlagTests.cs @@ -515,34 +515,6 @@ public async Task UnblockParticipant_ParticipantNotBlocked_ReturnBadRequest() _mockParticipantManagementClient.VerifyNoOtherCalls(); _mockHttpClient.VerifyNoOtherCalls(); } - // [TestMethod] - // public async Task UnblockParticipant_ParticipantNotBlocked_ReturnBadRequest() - // { - // //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 = 0, - // EligibilityFlag = 1 - // }); - - - // //act - // var result = await _sut.UnblockParticipant(_request.Object); - - // //asset - // Assert.AreEqual(HttpStatusCode.BadRequest, result.StatusCode); - // _mockParticipantManagementClient.Verify(x => x.GetSingleByFilter(It.IsAny>>()), Times.Once); - // _mockParticipantManagementClient.VerifyNoOtherCalls(); - // _mockHttpClient.VerifyNoOtherCalls(); - // } From 1078949c92b2ce2463559bca8c61130b170f447f Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Thu, 7 Aug 2025 14:38:01 +0100 Subject: [PATCH 26/28] Addressing Comments --- .../UpdateBlockedFlag/UpdateBlockedFlag.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs index cd47bf8cd4..6ae62cdfa1 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -113,7 +113,7 @@ public async Task GetParticipantDetails([HttpTrigger(Authoriza if (getParticipantResult.ResponseMessage == "Participant Couldn't be found") { - return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.NotFound, req, "Participant Not found"); + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.NotFound, req, getParticipantResult.ResponseMessage); } if (getParticipantResult.ResponseMessage == "Invalid NHS Number") { @@ -175,7 +175,7 @@ public async Task UnblockParticipant([HttpTrigger(Authorizatio } if (unBlockParticipantResult.ResponseMessage == "Participant Couldn't be found") { - return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.NotFound, req, "Participant Not found"); + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.NotFound, req, unBlockParticipantResult.ResponseMessage); } return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, unBlockParticipantResult.ResponseMessage!); From f53c8ef725b8001e0aa5f2ee4ec507fe643e37a6 Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Thu, 7 Aug 2025 14:39:35 +0100 Subject: [PATCH 27/28] using const for cannot parse message --- .../UpdateBlockedFlag/UpdateBlockedFlag.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs index 6ae62cdfa1..b25aad8755 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/UpdateBlockedFlag.cs @@ -14,7 +14,8 @@ public class UpdateBlockedFlag private readonly ICreateResponse _createResponse; private readonly IBlockParticipantHandler _blockParticipantHandler; - private const string cannotBeDeserializedMessage = "Participant Block Dto couldn't be deserialized"; + private const string cannotBeDeserializedMessage = "Request couldn't be deserialized"; + public UpdateBlockedFlag(ILogger logger, ICreateResponse createResponse, IBlockParticipantHandler blockParticipantHandler) @@ -45,13 +46,13 @@ public async Task BlockParticipant([HttpTrigger(AuthorizationL var blockParticipantDTOJson = await req.ReadAsStringAsync(); if (string.IsNullOrWhiteSpace(blockParticipantDTOJson)) { - return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, "Unable to parse request"); + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, cannotBeDeserializedMessage); } var blockParticipantDTO = JsonSerializer.Deserialize(blockParticipantDTOJson); if (blockParticipantDTO == null) { - return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, "Unable to parse request"); + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, cannotBeDeserializedMessage); } var blockParticipantResult = await _blockParticipantHandler.BlockParticipant(blockParticipantDTO); @@ -95,13 +96,13 @@ public async Task GetParticipantDetails([HttpTrigger(Authoriza var blockParticipantDTOJson = await req.ReadAsStringAsync(); if (string.IsNullOrWhiteSpace(blockParticipantDTOJson)) { - return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, "Unable to parse request"); + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, cannotBeDeserializedMessage); } var blockParticipantDTO = JsonSerializer.Deserialize(blockParticipantDTOJson); if (blockParticipantDTO == null) { - return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, "Unable to parse request"); + return await _createResponse.CreateHttpResponseWithBodyAsync(HttpStatusCode.BadRequest, req, cannotBeDeserializedMessage); } var getParticipantResult = await _blockParticipantHandler.GetParticipant(blockParticipantDTO); From 227b73c78e3f124f6fa5fa91c8a58d481a094552 Mon Sep 17 00:00:00 2001 From: Michael Clayson Date: Thu, 7 Aug 2025 14:51:51 +0100 Subject: [PATCH 28/28] unused usings --- .../ManageNemsSubscription/ManageNemsSubscriptionConfig.cs | 1 - .../UpdateBlockedFlag/BlockParticipantHandler.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs b/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs index fe7bd86acc..852c375458 100644 --- a/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs +++ b/application/CohortManager/src/Functions/DemographicServices/ManageNemsSubscription/ManageNemsSubscriptionConfig.cs @@ -1,7 +1,6 @@ namespace NHS.CohortManager.DemographicServices; using System.ComponentModel.DataAnnotations; -using Hl7.Fhir.Specification.Navigation; /// /// Configuration settings for NEMS subscription management diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs index 47fd72d4a6..e469f46fe9 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/UpdateBlockedFlag/BlockParticipantHandler.cs @@ -5,7 +5,6 @@ namespace NHS.CohortManager.ParticipantManagementService; using System.Text.Json; using Common; using DataServices.Client; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Model;