From b902893408d23c1afad4c5c6256874b561e28554 Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Fri, 8 Aug 2025 11:55:09 +0100 Subject: [PATCH 1/7] fix: pds function now maganges all responses from the 3rd party pds API --- .../PdsErroResponseClasses/PDSCoding.cs | 2 +- .../PdsErroResponseClasses/PDSErrorRespone.cs | 2 +- .../PdsErroResponseClasses/PDSIssue.cs | 2 +- .../PdsErroResponseClasses/PdsErrorDetails.cs | 2 +- .../RetrievePDSDemographic/Program.cs | 3 ++ .../RetrievePDSDemographic.cs | 53 ++++++++++++++++++- .../RetrievePDSDemographicConfig.cs | 6 +++ .../ProcessNemsUpdate/ProcessNemsUpdate.cs | 27 +--------- .../RetrievePDSDemographicTests.cs | 8 ++- 9 files changed, 73 insertions(+), 32 deletions(-) rename application/CohortManager/src/Functions/{NemsSubscriptionService/ProcessNemsUpdate => DemographicServices/RetrievePDSDemographic}/PdsErroResponseClasses/PDSCoding.cs (79%) rename application/CohortManager/src/Functions/{NemsSubscriptionService/ProcessNemsUpdate => DemographicServices/RetrievePDSDemographic}/PdsErroResponseClasses/PDSErrorRespone.cs (72%) rename application/CohortManager/src/Functions/{NemsSubscriptionService/ProcessNemsUpdate => DemographicServices/RetrievePDSDemographic}/PdsErroResponseClasses/PDSIssue.cs (76%) rename application/CohortManager/src/Functions/{NemsSubscriptionService/ProcessNemsUpdate => DemographicServices/RetrievePDSDemographic}/PdsErroResponseClasses/PdsErrorDetails.cs (63%) diff --git a/application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/PdsErroResponseClasses/PDSCoding.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsErroResponseClasses/PDSCoding.cs similarity index 79% rename from application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/PdsErroResponseClasses/PDSCoding.cs rename to application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsErroResponseClasses/PDSCoding.cs index a0ba41b100..3ce1396932 100644 --- a/application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/PdsErroResponseClasses/PDSCoding.cs +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsErroResponseClasses/PDSCoding.cs @@ -1,4 +1,4 @@ -namespace NHS.Screening.ProcessNemsUpdate; +namespace NHS.CohortManager.DemographicServices; public class PdsCoding { diff --git a/application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/PdsErroResponseClasses/PDSErrorRespone.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsErroResponseClasses/PDSErrorRespone.cs similarity index 72% rename from application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/PdsErroResponseClasses/PDSErrorRespone.cs rename to application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsErroResponseClasses/PDSErrorRespone.cs index cf7ec3d16a..74afa94560 100644 --- a/application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/PdsErroResponseClasses/PDSErrorRespone.cs +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsErroResponseClasses/PDSErrorRespone.cs @@ -1,4 +1,4 @@ -namespace NHS.Screening.ProcessNemsUpdate; +namespace NHS.CohortManager.DemographicServices; public class PdsErrorResponse { diff --git a/application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/PdsErroResponseClasses/PDSIssue.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsErroResponseClasses/PDSIssue.cs similarity index 76% rename from application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/PdsErroResponseClasses/PDSIssue.cs rename to application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsErroResponseClasses/PDSIssue.cs index 262c2c90f6..90f0777916 100644 --- a/application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/PdsErroResponseClasses/PDSIssue.cs +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsErroResponseClasses/PDSIssue.cs @@ -1,4 +1,4 @@ -namespace NHS.Screening.ProcessNemsUpdate; +namespace NHS.CohortManager.DemographicServices; public class PdsIssue { diff --git a/application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/PdsErroResponseClasses/PdsErrorDetails.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsErroResponseClasses/PdsErrorDetails.cs similarity index 63% rename from application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/PdsErroResponseClasses/PdsErrorDetails.cs rename to application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsErroResponseClasses/PdsErrorDetails.cs index dae2b162e3..5dac03a7a9 100644 --- a/application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/PdsErroResponseClasses/PdsErrorDetails.cs +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsErroResponseClasses/PdsErrorDetails.cs @@ -1,4 +1,4 @@ -namespace NHS.Screening.ProcessNemsUpdate; +namespace NHS.CohortManager.DemographicServices; public class PdsErrorDetails { diff --git a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/Program.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/Program.cs index 79af6a3fb4..89ae9a09f1 100644 --- a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/Program.cs +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/Program.cs @@ -18,11 +18,14 @@ services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); // Register health checks services.AddBasicHealthCheck("RetrievePdsDemographic"); }) .AddJwtTokenSigning(config.UseFakePDSServices) .AddTelemetry() + .AddServiceBusClient(config.ServiceBusConnectionString) .AddHttpClient(config.UseFakePDSServices) .Build(); diff --git a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs index 9c2be44ccf..935670c06a 100644 --- a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs @@ -13,7 +13,7 @@ namespace NHS.CohortManager.DemographicServices; using System.Threading.Tasks; using Model; using System.Net.Http.Json; -using Hl7.Fhir.Support; +using System.Collections.Concurrent; public class RetrievePdsDemographic { @@ -24,6 +24,8 @@ public class RetrievePdsDemographic private readonly IFhirPatientDemographicMapper _fhirPatientDemographicMapper; private readonly IDataServiceClient _participantDemographicClient; private readonly IBearerTokenService _bearerTokenService; + private readonly ICreateBasicParticipantData _createBasicParticipantData; + private readonly IAddBatchToQueue _addBatchToQueue; private const string PdsParticipantUrlFormat = "{0}/{1}"; @@ -34,6 +36,8 @@ public RetrievePdsDemographic( IFhirPatientDemographicMapper fhirPatientDemographicMapper, IOptions retrievePDSDemographicConfig, IDataServiceClient participantDemographicClient, + ICreateBasicParticipantData createBasicParticipantData, + IAddBatchToQueue addBatchToQueue, IBearerTokenService bearerTokenService ) { @@ -43,7 +47,9 @@ IBearerTokenService bearerTokenService _fhirPatientDemographicMapper = fhirPatientDemographicMapper; _config = retrievePDSDemographicConfig.Value; _participantDemographicClient = participantDemographicClient; + _createBasicParticipantData = createBasicParticipantData; _bearerTokenService = bearerTokenService; + _addBatchToQueue = addBatchToQueue; } // TODO: Need to send an exception to the EXCEPTION_MANAGEMENT table whenever this function returns a non OK status. @@ -74,7 +80,8 @@ public async Task Run([HttpTrigger(AuthorizationLevel.Anonymou if (response.StatusCode == HttpStatusCode.NotFound) { var pdsErrorResponse = await response.Content.ReadAsStringAsync(); - return _createResponse.CreateHttpResponse(HttpStatusCode.NotFound, req, pdsErrorResponse); + await ProcessPdsResponse(response, nhsNumber); + return _createResponse.CreateHttpResponse(HttpStatusCode.NotFound, req, "PDS returned a 404 please database for details"); } response.EnsureSuccessStatusCode(); @@ -95,6 +102,48 @@ public async Task Run([HttpTrigger(AuthorizationLevel.Anonymou } } + private async Task ProcessPdsResponse(HttpResponseMessage pdsResponse, string nhsNumber) + { + var errorResponse = await pdsResponse!.Content.ReadFromJsonAsync(); + // we now create a record as an update record and send to the manage participant function. Reason for removal for date should be today and the reason for remove of ORR + if (errorResponse!.issue!.FirstOrDefault()!.details!.coding!.FirstOrDefault()!.code == "INVALIDATED_RESOURCE") + { + var pdsDemographic = new PdsDemographic() + { + NhsNumber = nhsNumber, + PrimaryCareProvider = null, + ReasonForRemoval = "ORR", + RemovalEffectiveFromDate = DateTime.UtcNow.Date.ToString("yyyyMMdd") + }; + var participant = new Participant(pdsDemographic); + participant.RecordType = Actions.Removed; + //sends record for an update + await ProcessRecord(participant); + return; + } + _logger.LogError("the PDS function has returned a 404 error. function now stopping processing"); + } + + + private async Task ProcessRecord(Participant participant) + { + var updateRecord = new ConcurrentQueue(); + participant.RecordType = participant.RecordType = Actions.Removed; + + var basicParticipantCsvRecord = new BasicParticipantCsvRecord + { + BasicParticipantData = _createBasicParticipantData.BasicParticipantData(participant), + FileName = "NemsMessages", + Participant = participant + }; + + updateRecord.Enqueue(basicParticipantCsvRecord); + + _logger.LogInformation("Sending record to the update queue."); + await _addBatchToQueue.ProcessBatch(updateRecord, _config.ParticipantManagementTopic); + } + + private async Task UpsertDemographicRecordFromPDS(ParticipantDemographic participantDemographic) { ParticipantDemographic oldParticipantDemographic = await _participantDemographicClient.GetSingleByFilter(i => i.NhsNumber == participantDemographic.NhsNumber); diff --git a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographicConfig.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographicConfig.cs index bb5a79a2f7..f1a8eb9f22 100644 --- a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographicConfig.cs +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographicConfig.cs @@ -20,6 +20,12 @@ public class RetrievePDSDemographicConfig [Required] public required string AuthTokenURL { get; set; } + [Required] + public required string ParticipantManagementTopic { get; set; } + + [Required] + public required string ServiceBusConnectionString { get; set; } + public required bool UseFakePDSServices { get; set; } = false; public string ClientId { get; set; } = string.Empty; } diff --git a/application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/ProcessNemsUpdate.cs b/application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/ProcessNemsUpdate.cs index 79d33d0fd4..fefcfa043e 100644 --- a/application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/ProcessNemsUpdate.cs +++ b/application/CohortManager/src/Functions/NemsSubscriptionService/ProcessNemsUpdate/ProcessNemsUpdate.cs @@ -75,7 +75,7 @@ public async Task Run([BlobTrigger("nems-updates/{name}", Connection = "nemsmesh var pdsResponse = await RetrievePdsRecord(nhsNumber!); if (pdsResponse!.StatusCode == System.Net.HttpStatusCode.NotFound) { - await ProcessPdsResponse(pdsResponse, nhsNumber!); + _logger.LogError("the PDS function has returned a 404 error. function now stopping processing"); // we can stop processing here as we know that not found means the participant ether needed an update or they were actually not found return; } @@ -102,29 +102,6 @@ public async Task Run([BlobTrigger("nems-updates/{name}", Connection = "nemsmesh } - private async Task ProcessPdsResponse(HttpResponseMessage pdsResponse, string nhsNumber) - { - var errorResponse = await pdsResponse!.Content.ReadFromJsonAsync(); - // we now create a record as an update record and send to the manage participant function. Reason for removal for date should be today and the reason for remove of ORR - if (errorResponse!.issue!.FirstOrDefault()!.details!.coding!.FirstOrDefault()!.code == "INVALIDATED_RESOURCE") - { - var pdsDemographic = new PdsDemographic() - { - NhsNumber = nhsNumber, - PrimaryCareProvider = null, - ReasonForRemoval = "ORR", - RemovalEffectiveFromDate = DateTime.UtcNow.Date.ToString("yyyyMMdd") - }; - var participant = new Participant(pdsDemographic); - participant.RecordType = Actions.Removed; - //sends record for an update - await ProcessRecord(participant); - return; - } - _logger.LogError("the PDS function has returned a 404 error. function now stopping processing"); - - } - private async Task UnsubscribeFromNems(string nhsNumber, PdsDemographic retrievedPdsRecord) { var supersededRecord = new PdsDemographic() @@ -207,7 +184,7 @@ private async Task ProcessRecord(Participant participant) } else { - participant.RecordType = participant.RecordType != Actions.Removed ? Actions.Amended : participant.RecordType; + participant.RecordType = Actions.Amended; _logger.LogWarning("The participant already exists in Cohort Manager. Existing record will get updated."); } diff --git a/tests/UnitTests/DemographicServicesTests/RetrievePDSDemographicTests/RetrievePDSDemographicTests.cs b/tests/UnitTests/DemographicServicesTests/RetrievePDSDemographicTests/RetrievePDSDemographicTests.cs index 6a8f325be1..c6338a692f 100644 --- a/tests/UnitTests/DemographicServicesTests/RetrievePDSDemographicTests/RetrievePDSDemographicTests.cs +++ b/tests/UnitTests/DemographicServicesTests/RetrievePDSDemographicTests/RetrievePDSDemographicTests.cs @@ -12,6 +12,7 @@ namespace NHS.CohortManager.Tests.UnitTests.DemographicServicesTests; using NHS.CohortManager.Tests.TestUtils; using System.Linq.Expressions; using Microsoft.Extensions.Caching.Memory; +using System.ComponentModel; [TestClass] public class RetrievePdsDemographicTests : DatabaseTestBaseSetup @@ -20,6 +21,8 @@ public class RetrievePdsDemographicTests : DatabaseTestBaseSetup> _mockConfig = new(); private static readonly Mock _mockFhirPatientDemographicMapper = new(); private static readonly Mock> _mockParticipantDemographicClient = new(); + private static readonly Mock _mockCreateBasicParticipantService = new(); + private static readonly Mock _mockAddBatchToQueue = new(); private static Mock _bearerTokenService = new(); @@ -35,7 +38,10 @@ public RetrievePdsDemographicTests() : base((conn, logger, transaction, command, _mockFhirPatientDemographicMapper.Object, _mockConfig.Object, _mockParticipantDemographicClient.Object, - _bearerTokenService.Object)) + _mockCreateBasicParticipantService.Object, + _mockAddBatchToQueue.Object, + _bearerTokenService.Object + )) { CreateHttpResponseMock(); } From 932be909c8bee3c3bb7699ae4871aa16db0db4dd Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Fri, 8 Aug 2025 12:36:26 +0100 Subject: [PATCH 2/7] chore: creating a new class that contains all the pds constraints --- .../RetrievePDSDemographic/PdsContraints.cs | 8 ++++++++ .../RetrievePDSDemographic/RetrievePDSDemographic.cs | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsContraints.cs diff --git a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsContraints.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsContraints.cs new file mode 100644 index 0000000000..5c1c2feff4 --- /dev/null +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsContraints.cs @@ -0,0 +1,8 @@ +namespace NHS.CohortManager.DemographicServices; + +public static class PdsConstants +{ + public const string InvalidatedResourceCode = "INVALIDATED_RESOURCE"; + public const string OrrRemovalReason = "ORR"; + public const string DefaultFileName = "NemsMessages"; +} \ No newline at end of file diff --git a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs index 935670c06a..4980fe6d52 100644 --- a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs @@ -106,13 +106,13 @@ private async Task ProcessPdsResponse(HttpResponseMessage pdsResponse, string nh { var errorResponse = await pdsResponse!.Content.ReadFromJsonAsync(); // we now create a record as an update record and send to the manage participant function. Reason for removal for date should be today and the reason for remove of ORR - if (errorResponse!.issue!.FirstOrDefault()!.details!.coding!.FirstOrDefault()!.code == "INVALIDATED_RESOURCE") + if (errorResponse!.issue!.FirstOrDefault()!.details!.coding!.FirstOrDefault()!.code == PdsConstants.InvalidatedResourceCode) { var pdsDemographic = new PdsDemographic() { NhsNumber = nhsNumber, PrimaryCareProvider = null, - ReasonForRemoval = "ORR", + ReasonForRemoval = PdsConstants.OrrRemovalReason, RemovalEffectiveFromDate = DateTime.UtcNow.Date.ToString("yyyyMMdd") }; var participant = new Participant(pdsDemographic); @@ -133,7 +133,7 @@ private async Task ProcessRecord(Participant participant) var basicParticipantCsvRecord = new BasicParticipantCsvRecord { BasicParticipantData = _createBasicParticipantData.BasicParticipantData(participant), - FileName = "NemsMessages", + FileName = PdsConstants.DefaultFileName, Participant = participant }; From d74c0bc2ddda777f99df023163ac43e94f418564 Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Fri, 8 Aug 2025 12:42:49 +0100 Subject: [PATCH 3/7] chore: removing unneeded codes --- .../RetrievePDSDemographic/RetrievePDSDemographic.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs index 4980fe6d52..2352042b7d 100644 --- a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs @@ -79,7 +79,6 @@ public async Task Run([HttpTrigger(AuthorizationLevel.Anonymou if (response.StatusCode == HttpStatusCode.NotFound) { - var pdsErrorResponse = await response.Content.ReadAsStringAsync(); await ProcessPdsResponse(response, nhsNumber); return _createResponse.CreateHttpResponse(HttpStatusCode.NotFound, req, "PDS returned a 404 please database for details"); } From 3a5e8014c6530071d6ce907b2ddbec7e5c485989 Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Fri, 8 Aug 2025 14:04:09 +0100 Subject: [PATCH 4/7] chore: renaming ProcessPdsResponse to ProcessPdsNotFoundResponse --- .../RetrievePDSDemographic/RetrievePDSDemographic.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs index 2352042b7d..e4bd86c9e0 100644 --- a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs @@ -79,7 +79,7 @@ public async Task Run([HttpTrigger(AuthorizationLevel.Anonymou if (response.StatusCode == HttpStatusCode.NotFound) { - await ProcessPdsResponse(response, nhsNumber); + await ProcessPdsNotFoundResponse(response, nhsNumber); return _createResponse.CreateHttpResponse(HttpStatusCode.NotFound, req, "PDS returned a 404 please database for details"); } @@ -101,7 +101,7 @@ public async Task Run([HttpTrigger(AuthorizationLevel.Anonymou } } - private async Task ProcessPdsResponse(HttpResponseMessage pdsResponse, string nhsNumber) + private async Task ProcessPdsNotFoundResponse(HttpResponseMessage pdsResponse, string nhsNumber) { var errorResponse = await pdsResponse!.Content.ReadFromJsonAsync(); // we now create a record as an update record and send to the manage participant function. Reason for removal for date should be today and the reason for remove of ORR From a1019232f9d4652dcb27a9ce3e764dd4ddc6dd30 Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Fri, 8 Aug 2025 14:21:02 +0100 Subject: [PATCH 5/7] fix: making sure to add service buss to pds --- .../tf-core/environments/development.tfvars | 24 ++++++++------- .../tf-core/environments/integration.tfvars | 24 ++++++++------- .../tf-core/environments/nft.tfvars | 24 ++++++++------- .../tf-core/environments/preprod.tfvars | 30 ++++++++++--------- .../tf-core/environments/sandbox.tfvars | 30 ++++++++++--------- 5 files changed, 71 insertions(+), 61 deletions(-) diff --git a/infrastructure/tf-core/environments/development.tfvars b/infrastructure/tf-core/environments/development.tfvars index 35b96dee85..ec089479ec 100644 --- a/infrastructure/tf-core/environments/development.tfvars +++ b/infrastructure/tf-core/environments/development.tfvars @@ -347,7 +347,7 @@ function_apps = { } ] env_vars_static = { - MeshCertName = "MeshCert" + MeshCertName = "MeshCert" ParticipantManagementTopic = "participant-management" } } @@ -932,10 +932,11 @@ function_apps = { } RetrievePDSDemographic = { - name_suffix = "retrieve-pds-demographic" - function_endpoint_name = "RetrievePDSDemographic" - app_service_plan_key = "DefaultPlan" - key_vault_url = "KeyVaultConnectionString" + name_suffix = "retrieve-pds-demographic" + function_endpoint_name = "RetrievePDSDemographic" + app_service_plan_key = "DefaultPlan" + service_bus_connections = ["internal"] + key_vault_url = "KeyVaultConnectionString" app_urls = [ { env_var_name = "ExceptionFunctionURL" @@ -951,12 +952,13 @@ function_apps = { } ] env_vars_static = { - RetrievePdsParticipantURL = "https://int.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient" - Kid = "RetrievePdsDemographic-DEV1" - Audience = "https://int.api.service.nhs.uk/oauth2/token" - AuthTokenURL = "https://int.api.service.nhs.uk/oauth2/token" - KeyNamePrivateKey = "PDSPRIVATEKEY" - UseFakePDSServices = "true" + RetrievePdsParticipantURL = "https://int.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient" + Kid = "RetrievePdsDemographic-DEV1" + Audience = "https://int.api.service.nhs.uk/oauth2/token" + AuthTokenURL = "https://int.api.service.nhs.uk/oauth2/token" + KeyNamePrivateKey = "PDSPRIVATEKEY" + UseFakePDSServices = "true" + ParticipantManagementTopic = "participant-management" } } diff --git a/infrastructure/tf-core/environments/integration.tfvars b/infrastructure/tf-core/environments/integration.tfvars index e18bdc4f13..ba825f7a87 100644 --- a/infrastructure/tf-core/environments/integration.tfvars +++ b/infrastructure/tf-core/environments/integration.tfvars @@ -347,7 +347,7 @@ function_apps = { } ] env_vars_static = { - MeshCertName = "MeshCert" + MeshCertName = "MeshCert" ParticipantManagementTopic = "participant-management" } } @@ -932,10 +932,11 @@ function_apps = { } RetrievePDSDemographic = { - name_suffix = "retrieve-pds-demographic" - function_endpoint_name = "RetrievePDSDemographic" - app_service_plan_key = "DefaultPlan" - key_vault_url = "KeyVaultConnectionString" + name_suffix = "retrieve-pds-demographic" + function_endpoint_name = "RetrievePDSDemographic" + app_service_plan_key = "DefaultPlan" + service_bus_connections = ["internal"] + key_vault_url = "KeyVaultConnectionString" app_urls = [ { env_var_name = "ExceptionFunctionURL" @@ -951,12 +952,13 @@ function_apps = { } ] env_vars_static = { - RetrievePdsParticipantURL = "https://int.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient" - Kid = "RetrievePdsDemographic-test1" - Audience = "https://int.api.service.nhs.uk/oauth2/token" - AuthTokenURL = "https://int.api.service.nhs.uk/oauth2/token" - KeyNamePrivateKey = "PDSPRIVATEKEY" - UseFakePDSServices = "false" + RetrievePdsParticipantURL = "https://int.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient" + Kid = "RetrievePdsDemographic-test1" + Audience = "https://int.api.service.nhs.uk/oauth2/token" + AuthTokenURL = "https://int.api.service.nhs.uk/oauth2/token" + KeyNamePrivateKey = "PDSPRIVATEKEY" + ParticipantManagementTopic = "participant-management" + UseFakePDSServices = "false" } } diff --git a/infrastructure/tf-core/environments/nft.tfvars b/infrastructure/tf-core/environments/nft.tfvars index 108afe5070..898f1eeb66 100644 --- a/infrastructure/tf-core/environments/nft.tfvars +++ b/infrastructure/tf-core/environments/nft.tfvars @@ -346,7 +346,7 @@ function_apps = { } ] env_vars_static = { - MeshCertName = "MeshCert" + MeshCertName = "MeshCert" ParticipantManagementTopic = "participant-management" } } @@ -931,10 +931,11 @@ function_apps = { } RetrievePDSDemographic = { - name_suffix = "retrieve-pds-demographic" - function_endpoint_name = "RetrievePDSDemographic" - app_service_plan_key = "DefaultPlan" - key_vault_url = "KeyVaultConnectionString" + name_suffix = "retrieve-pds-demographic" + function_endpoint_name = "RetrievePDSDemographic" + app_service_plan_key = "DefaultPlan" + service_bus_connections = ["internal"] + key_vault_url = "KeyVaultConnectionString" app_urls = [ { env_var_name = "ExceptionFunctionURL" @@ -950,12 +951,13 @@ function_apps = { } ] env_vars_static = { - RetrievePdsParticipantURL = "https://int.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient" - Kid = "RetrievePdsDemographic-test1" - Audience = "https://int.api.service.nhs.uk/oauth2/token" - AuthTokenURL = "https://int.api.service.nhs.uk/oauth2/token" - KeyNamePrivateKey = "PDSPRIVATEKEY" - UseFakePDSServices = "false" + RetrievePdsParticipantURL = "https://int.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient" + Kid = "RetrievePdsDemographic-test1" + Audience = "https://int.api.service.nhs.uk/oauth2/token" + AuthTokenURL = "https://int.api.service.nhs.uk/oauth2/token" + KeyNamePrivateKey = "PDSPRIVATEKEY" + ParticipantManagementTopic = "participant-management" + UseFakePDSServices = "false" } } diff --git a/infrastructure/tf-core/environments/preprod.tfvars b/infrastructure/tf-core/environments/preprod.tfvars index adc98e2ff4..588e2369c0 100644 --- a/infrastructure/tf-core/environments/preprod.tfvars +++ b/infrastructure/tf-core/environments/preprod.tfvars @@ -353,7 +353,7 @@ function_apps = { } ] env_vars_static = { - MeshCertName = "MeshCert" + MeshCertName = "MeshCert" ParticipantManagementTopic = "participant-management" } } @@ -862,9 +862,9 @@ function_apps = { key_vault_url = "KeyVaultConnectionString" service_bus_connections = ["internal"] env_vars_static = { - ServiceNowRefreshAccessTokenUrl = "" # TODO: Get value - ServiceNowUpdateUrl = "" # TODO: Get value - ServiceNowResolutionUrl = "" # TODO: Get value + ServiceNowRefreshAccessTokenUrl = "" # TODO: Get value + ServiceNowUpdateUrl = "" # TODO: Get value + ServiceNowResolutionUrl = "" # TODO: Get value ServiceNowParticipantManagementTopic = "servicenow-participant-management" # Sends messages to the servicenow participant manage topic } } @@ -938,10 +938,11 @@ function_apps = { } RetrievePDSDemographic = { - name_suffix = "retrieve-pds-demographic" - function_endpoint_name = "RetrievePDSDemographic" - app_service_plan_key = "DefaultPlan" - key_vault_url = "KeyVaultConnectionString" + name_suffix = "retrieve-pds-demographic" + function_endpoint_name = "RetrievePDSDemographic" + app_service_plan_key = "DefaultPlan" + service_bus_connections = ["internal"] + key_vault_url = "KeyVaultConnectionString" app_urls = [ { env_var_name = "ExceptionFunctionURL" @@ -957,12 +958,13 @@ function_apps = { } ] env_vars_static = { - RetrievePdsParticipantURL = "https://int.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient" - Kid = "RetrievePdsDemographic-prod" - Audience = "https://api.service.nhs.uk/oauth2/token" - AuthTokenURL = "https://api.service.nhs.uk/oauth2/token" - KeyNamePrivateKey = "PDSPRIVATEKEY" - UseFakePDSServices = "false" + RetrievePdsParticipantURL = "https://int.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient" + Kid = "RetrievePdsDemographic-prod" + Audience = "https://api.service.nhs.uk/oauth2/token" + AuthTokenURL = "https://api.service.nhs.uk/oauth2/token" + KeyNamePrivateKey = "PDSPRIVATEKEY" + ParticipantManagementTopic = "participant-management" + UseFakePDSServices = "false" } } diff --git a/infrastructure/tf-core/environments/sandbox.tfvars b/infrastructure/tf-core/environments/sandbox.tfvars index c017aebccd..33a93e7b67 100644 --- a/infrastructure/tf-core/environments/sandbox.tfvars +++ b/infrastructure/tf-core/environments/sandbox.tfvars @@ -361,7 +361,7 @@ function_apps = { } ] env_vars_static = { - MeshCertName = "MeshCert" + MeshCertName = "MeshCert" ParticipantManagementTopic = "participant-management" } } @@ -1252,10 +1252,11 @@ function_apps = { } RetrievePDSDemographic = { - name_suffix = "retrieve-pds-demographic" - function_endpoint_name = "RetrievePDSDemographic" - app_service_plan_key = "DefaultPlan" - key_vault_url = "KeyVaultConnectionString" + name_suffix = "retrieve-pds-demographic" + function_endpoint_name = "RetrievePDSDemographic" + app_service_plan_key = "DefaultPlan" + key_vault_url = "KeyVaultConnectionString" + service_bus_connections = ["internal"] app_urls = [ { env_var_name = "ExceptionFunctionURL" @@ -1271,12 +1272,13 @@ function_apps = { } ] env_vars_static = { - RetrievePdsParticipantURL = "https://int.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient" - Kid = "RetrievePdsDemographic-test1" - Audience = "https://int.api.service.nhs.uk/oauth2/token" - AuthTokenURL = "https://int.api.service.nhs.uk/oauth2/token" - KeyNamePrivateKey = "PDSPRIVATEKEY" - UseFakePDSServices = "true" + RetrievePdsParticipantURL = "https://int.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient" + Kid = "RetrievePdsDemographic-test1" + Audience = "https://int.api.service.nhs.uk/oauth2/token" + AuthTokenURL = "https://int.api.service.nhs.uk/oauth2/token" + KeyNamePrivateKey = "PDSPRIVATEKEY" + ParticipantManagementTopic = "participant-management" + UseFakePDSServices = "true" } } @@ -1415,9 +1417,9 @@ linux_web_app = { } from_key_vault = { # env_var_name = "key_vault_secret_name" - AUTH_CIS2_CLIENT_SECRET = "auth-cis2-client-secret" - COHORT_MANAGER_RBAC_CODE = "cohort-manager-users" - NEXTAUTH_SECRET = "nextauth-secret" + AUTH_CIS2_CLIENT_SECRET = "auth-cis2-client-secret" + COHORT_MANAGER_RBAC_CODE = "cohort-manager-users" + NEXTAUTH_SECRET = "nextauth-secret" } local_urls = { # %s becomes the environment and region prefix (e.g. dev-uks) From b677c1129519b95cf4a0e02441b95b2537c1e803 Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Mon, 11 Aug 2025 13:18:13 +0100 Subject: [PATCH 6/7] chor: adding unit tests for pds functions features --- .../RetrievePDSDemographic/IPdsProcessor.cs | 10 + .../RetrievePDSDemographic/PdsProcessor.cs | 110 +++++++ .../RetrievePDSDemographic/Program.cs | 1 + .../RetrievePDSDemographic.cs | 95 +------ .../CohortManager/src/Functions/Functions.sln | 17 ++ tests/PdsProcessorTests/PdsProcessorTests.cs | 269 ++++++++++++++++++ .../PdsProcessorTests.csproj | 29 ++ .../RetrievePDSDemographicTests.cs | 16 +- 8 files changed, 449 insertions(+), 98 deletions(-) create mode 100644 application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/IPdsProcessor.cs create mode 100644 application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsProcessor.cs create mode 100644 tests/PdsProcessorTests/PdsProcessorTests.cs create mode 100644 tests/PdsProcessorTests/PdsProcessorTests.csproj diff --git a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/IPdsProcessor.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/IPdsProcessor.cs new file mode 100644 index 0000000000..a257c6476e --- /dev/null +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/IPdsProcessor.cs @@ -0,0 +1,10 @@ +namespace NHS.CohortManager.DemographicServices; + +using Model; + +public interface IPdsProcessor +{ + Task ProcessPdsNotFoundResponse(HttpResponseMessage pdsResponse, string nhsNumber); + Task ProcessRecord(Participant participant); + Task UpsertDemographicRecordFromPDS(ParticipantDemographic participantDemographic); +} \ No newline at end of file diff --git a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsProcessor.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsProcessor.cs new file mode 100644 index 0000000000..670dbadfd3 --- /dev/null +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsProcessor.cs @@ -0,0 +1,110 @@ +namespace NHS.CohortManager.DemographicServices; + +using System.Collections.Concurrent; +using System.Net.Http.Json; +using Common; +using DataServices.Client; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Model; + +public class PdsProcessor : IPdsProcessor +{ + private readonly ILogger _logger; + + private readonly IDataServiceClient _participantDemographicClient; + private readonly ICreateBasicParticipantData _createBasicParticipantData; + private readonly RetrievePDSDemographicConfig _config; + private readonly IAddBatchToQueue _addBatchToQueue; + + + public PdsProcessor( + ILogger logger, + ICreateBasicParticipantData createBasicParticipantData, + IDataServiceClient participantDemographicClient, + IAddBatchToQueue addBatchToQueue, + IOptions retrievePDSDemographicConfig) + { + _logger = logger; + _participantDemographicClient = participantDemographicClient; + _createBasicParticipantData = createBasicParticipantData; + _addBatchToQueue = addBatchToQueue; + _config = retrievePDSDemographicConfig.Value; + } + + public async Task ProcessPdsNotFoundResponse(HttpResponseMessage pdsResponse, string nhsNumber) + { + var errorResponse = await pdsResponse!.Content.ReadFromJsonAsync(); + // we now create a record as an update record and send to the manage participant function. Reason for removal for date should be today and the reason for remove of ORR + if (errorResponse!.issue!.FirstOrDefault()!.details!.coding!.FirstOrDefault()!.code == PdsConstants.InvalidatedResourceCode) + { + var pdsDemographic = new PdsDemographic() + { + NhsNumber = nhsNumber, + PrimaryCareProvider = null, + ReasonForRemoval = PdsConstants.OrrRemovalReason, + RemovalEffectiveFromDate = DateTime.UtcNow.Date.ToString("yyyyMMdd") + }; + var participant = new Participant(pdsDemographic); + participant.RecordType = Actions.Removed; + //sends record for an update + await ProcessRecord(participant); + return; + } + _logger.LogError("the PDS function has returned a 404 error. function now stopping processing"); + } + + + public async Task ProcessRecord(Participant participant) + { + var updateRecord = new ConcurrentQueue(); + participant.RecordType = participant.RecordType = Actions.Removed; + + var basicParticipantCsvRecord = new BasicParticipantCsvRecord + { + BasicParticipantData = _createBasicParticipantData.BasicParticipantData(participant), + FileName = PdsConstants.DefaultFileName, + Participant = participant + }; + + updateRecord.Enqueue(basicParticipantCsvRecord); + + _logger.LogInformation("Sending record to the update queue."); + await _addBatchToQueue.ProcessBatch(updateRecord, _config.ParticipantManagementTopic); + } + + + public async Task UpsertDemographicRecordFromPDS(ParticipantDemographic participantDemographic) + { + ParticipantDemographic oldParticipantDemographic = await _participantDemographicClient.GetSingleByFilter(i => i.NhsNumber == participantDemographic.NhsNumber); + + if (oldParticipantDemographic == null) + { + _logger.LogInformation("Participant Demographic record not found, attemping to add Participant Demographic."); + bool addSuccess = await _participantDemographicClient.Add(participantDemographic); + + if (addSuccess) + { + _logger.LogInformation("Successfully added Participant Demographic."); + return true; + } + + _logger.LogError("Failed to add Participant Demographic."); + return false; + } + + _logger.LogInformation("Participant Demographic record found, attempting to update Participant Demographic."); + participantDemographic.ParticipantId = oldParticipantDemographic.ParticipantId; + bool updateSuccess = await _participantDemographicClient.Update(participantDemographic); + + if (updateSuccess) + { + _logger.LogInformation("Successfully updated Participant Demographic."); + return true; + } + + _logger.LogError("Failed to update Participant Demographic."); + return false; + } + +} \ No newline at end of file diff --git a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/Program.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/Program.cs index 89ae9a09f1..05f964c220 100644 --- a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/Program.cs +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/Program.cs @@ -19,6 +19,7 @@ services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); // Register health checks services.AddBasicHealthCheck("RetrievePdsDemographic"); diff --git a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs index e4bd86c9e0..1491c71211 100644 --- a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/RetrievePDSDemographic.cs @@ -12,8 +12,6 @@ namespace NHS.CohortManager.DemographicServices; using Microsoft.Extensions.Options; using System.Threading.Tasks; using Model; -using System.Net.Http.Json; -using System.Collections.Concurrent; public class RetrievePdsDemographic { @@ -22,10 +20,8 @@ public class RetrievePdsDemographic private readonly IHttpClientFunction _httpClientFunction; private readonly RetrievePDSDemographicConfig _config; private readonly IFhirPatientDemographicMapper _fhirPatientDemographicMapper; - private readonly IDataServiceClient _participantDemographicClient; private readonly IBearerTokenService _bearerTokenService; - private readonly ICreateBasicParticipantData _createBasicParticipantData; - private readonly IAddBatchToQueue _addBatchToQueue; + private readonly IPdsProcessor _pdsProcessor; private const string PdsParticipantUrlFormat = "{0}/{1}"; @@ -35,10 +31,8 @@ public RetrievePdsDemographic( IHttpClientFunction httpClientFunction, IFhirPatientDemographicMapper fhirPatientDemographicMapper, IOptions retrievePDSDemographicConfig, - IDataServiceClient participantDemographicClient, - ICreateBasicParticipantData createBasicParticipantData, - IAddBatchToQueue addBatchToQueue, - IBearerTokenService bearerTokenService + IBearerTokenService bearerTokenService, + IPdsProcessor pdsProcessor ) { _logger = logger; @@ -46,10 +40,8 @@ IBearerTokenService bearerTokenService _httpClientFunction = httpClientFunction; _fhirPatientDemographicMapper = fhirPatientDemographicMapper; _config = retrievePDSDemographicConfig.Value; - _participantDemographicClient = participantDemographicClient; - _createBasicParticipantData = createBasicParticipantData; _bearerTokenService = bearerTokenService; - _addBatchToQueue = addBatchToQueue; + _pdsProcessor = pdsProcessor; } // TODO: Need to send an exception to the EXCEPTION_MANAGEMENT table whenever this function returns a non OK status. @@ -79,7 +71,7 @@ public async Task Run([HttpTrigger(AuthorizationLevel.Anonymou if (response.StatusCode == HttpStatusCode.NotFound) { - await ProcessPdsNotFoundResponse(response, nhsNumber); + await _pdsProcessor.ProcessPdsNotFoundResponse(response, nhsNumber); return _createResponse.CreateHttpResponse(HttpStatusCode.NotFound, req, "PDS returned a 404 please database for details"); } @@ -88,7 +80,7 @@ public async Task Run([HttpTrigger(AuthorizationLevel.Anonymou jsonResponse = await _httpClientFunction.GetResponseText(response); var pdsDemographic = _fhirPatientDemographicMapper.ParseFhirJson(jsonResponse); var participantDemographic = pdsDemographic.ToParticipantDemographic(); - var upsertResult = await UpsertDemographicRecordFromPDS(participantDemographic); + var upsertResult = await _pdsProcessor.UpsertDemographicRecordFromPDS(participantDemographic); return upsertResult ? _createResponse.CreateHttpResponse(HttpStatusCode.OK, req, JsonSerializer.Serialize(participantDemographic)) : @@ -100,79 +92,4 @@ public async Task Run([HttpTrigger(AuthorizationLevel.Anonymou return _createResponse.CreateHttpResponse(HttpStatusCode.InternalServerError, req); } } - - private async Task ProcessPdsNotFoundResponse(HttpResponseMessage pdsResponse, string nhsNumber) - { - var errorResponse = await pdsResponse!.Content.ReadFromJsonAsync(); - // we now create a record as an update record and send to the manage participant function. Reason for removal for date should be today and the reason for remove of ORR - if (errorResponse!.issue!.FirstOrDefault()!.details!.coding!.FirstOrDefault()!.code == PdsConstants.InvalidatedResourceCode) - { - var pdsDemographic = new PdsDemographic() - { - NhsNumber = nhsNumber, - PrimaryCareProvider = null, - ReasonForRemoval = PdsConstants.OrrRemovalReason, - RemovalEffectiveFromDate = DateTime.UtcNow.Date.ToString("yyyyMMdd") - }; - var participant = new Participant(pdsDemographic); - participant.RecordType = Actions.Removed; - //sends record for an update - await ProcessRecord(participant); - return; - } - _logger.LogError("the PDS function has returned a 404 error. function now stopping processing"); - } - - - private async Task ProcessRecord(Participant participant) - { - var updateRecord = new ConcurrentQueue(); - participant.RecordType = participant.RecordType = Actions.Removed; - - var basicParticipantCsvRecord = new BasicParticipantCsvRecord - { - BasicParticipantData = _createBasicParticipantData.BasicParticipantData(participant), - FileName = PdsConstants.DefaultFileName, - Participant = participant - }; - - updateRecord.Enqueue(basicParticipantCsvRecord); - - _logger.LogInformation("Sending record to the update queue."); - await _addBatchToQueue.ProcessBatch(updateRecord, _config.ParticipantManagementTopic); - } - - - private async Task UpsertDemographicRecordFromPDS(ParticipantDemographic participantDemographic) - { - ParticipantDemographic oldParticipantDemographic = await _participantDemographicClient.GetSingleByFilter(i => i.NhsNumber == participantDemographic.NhsNumber); - - if (oldParticipantDemographic == null) - { - _logger.LogInformation("Participant Demographic record not found, attemping to add Participant Demographic."); - bool addSuccess = await _participantDemographicClient.Add(participantDemographic); - - if (addSuccess) - { - _logger.LogInformation("Successfully added Participant Demographic."); - return true; - } - - _logger.LogError("Failed to add Participant Demographic."); - return false; - } - - _logger.LogInformation("Participant Demographic record found, attempting to update Participant Demographic."); - participantDemographic.ParticipantId = oldParticipantDemographic.ParticipantId; - bool updateSuccess = await _participantDemographicClient.Update(participantDemographic); - - if (updateSuccess) - { - _logger.LogInformation("Successfully updated Participant Demographic."); - return true; - } - - _logger.LogError("Failed to update Participant Demographic."); - return false; - } } diff --git a/application/CohortManager/src/Functions/Functions.sln b/application/CohortManager/src/Functions/Functions.sln index 8891d8edd4..701c5f0fd8 100644 --- a/application/CohortManager/src/Functions/Functions.sln +++ b/application/CohortManager/src/Functions/Functions.sln @@ -229,6 +229,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JWTTokenServiceTests", "..\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthClientCredentialsTests", "..\..\..\..\tests\UnitTests\AuthClientCredentialsTests\AuthClientCredentialsTests.csproj", "{59CBDBE5-29BE-F38C-80E6-40843F2F8AF6}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PdsProcesserTests", "PdsProcesserTests", "{5555D2A1-8C8F-5B64-9F84-08EFE0FC7CD8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdsProcessorTests", "..\..\..\..\tests\PdsProcessorTests\PdsProcessorTests.csproj", "{FC22C311-57DD-B069-4041-AD2AC8F80B5D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1367,6 +1371,18 @@ Global {59CBDBE5-29BE-F38C-80E6-40843F2F8AF6}.Release|x64.Build.0 = Release|Any CPU {59CBDBE5-29BE-F38C-80E6-40843F2F8AF6}.Release|x86.ActiveCfg = Release|Any CPU {59CBDBE5-29BE-F38C-80E6-40843F2F8AF6}.Release|x86.Build.0 = Release|Any CPU + {FC22C311-57DD-B069-4041-AD2AC8F80B5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC22C311-57DD-B069-4041-AD2AC8F80B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC22C311-57DD-B069-4041-AD2AC8F80B5D}.Debug|x64.ActiveCfg = Debug|Any CPU + {FC22C311-57DD-B069-4041-AD2AC8F80B5D}.Debug|x64.Build.0 = Debug|Any CPU + {FC22C311-57DD-B069-4041-AD2AC8F80B5D}.Debug|x86.ActiveCfg = Debug|Any CPU + {FC22C311-57DD-B069-4041-AD2AC8F80B5D}.Debug|x86.Build.0 = Debug|Any CPU + {FC22C311-57DD-B069-4041-AD2AC8F80B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC22C311-57DD-B069-4041-AD2AC8F80B5D}.Release|Any CPU.Build.0 = Release|Any CPU + {FC22C311-57DD-B069-4041-AD2AC8F80B5D}.Release|x64.ActiveCfg = Release|Any CPU + {FC22C311-57DD-B069-4041-AD2AC8F80B5D}.Release|x64.Build.0 = Release|Any CPU + {FC22C311-57DD-B069-4041-AD2AC8F80B5D}.Release|x86.ActiveCfg = Release|Any CPU + {FC22C311-57DD-B069-4041-AD2AC8F80B5D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1428,5 +1444,6 @@ Global {52C72C2E-9A76-4ECF-A210-C3F7C584C193} = {19500E0D-AAAB-6F02-E24F-82619ACA2290} {4BD680A2-1ACB-7D6B-B2FD-8EBE9AEB5050} = {E8E33C5F-F9FB-3ACA-2B58-298ED48517C1} {59CBDBE5-29BE-F38C-80E6-40843F2F8AF6} = {E8E33C5F-F9FB-3ACA-2B58-298ED48517C1} + {FC22C311-57DD-B069-4041-AD2AC8F80B5D} = {5555D2A1-8C8F-5B64-9F84-08EFE0FC7CD8} EndGlobalSection EndGlobal diff --git a/tests/PdsProcessorTests/PdsProcessorTests.cs b/tests/PdsProcessorTests/PdsProcessorTests.cs new file mode 100644 index 0000000000..7d9f561127 --- /dev/null +++ b/tests/PdsProcessorTests/PdsProcessorTests.cs @@ -0,0 +1,269 @@ +namespace NHS.CohortManager.Tests.PdsProcessorTests; + +using System.Collections.Concurrent; +using System.Data.Services.Client; +using System.Runtime.CompilerServices; +using System.Text.Json; +using Common; +using DataServices.Client; +using Hl7.Fhir.ElementModel.Types; +using Hl7.Fhir.Model; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Model; +using Moq; +using NHS.CohortManager.DemographicServices; + +[TestClass] +public class PdsProcessorTests +{ + private readonly Mock> _mockLogger = new(); + private readonly Mock _mockCreateBasicParticipantData = new(); + private readonly Mock> _dataServiceClient = new(); + private readonly Mock _addBatchToQueue = new(); + private readonly Mock> _retrievePDSDemographicConfig = new(); + string nhsNumber = "1111110662"; + + private readonly PdsProcessor _pdsProcessor; + public PdsProcessorTests() + { + var testConfig = new RetrievePDSDemographicConfig + { + RetrievePdsParticipantURL = "", + DemographicDataServiceURL = "", + Audience = "", + KId = "", + AuthTokenURL = "", + ParticipantManagementTopic = "some-fake-topic", + ServiceBusConnectionString = "", + UseFakePDSServices = false + }; + + + _mockCreateBasicParticipantData.Setup(x => x.BasicParticipantData(It.IsAny())) + .Returns(new BasicParticipantData()); + + _retrievePDSDemographicConfig.Setup(x => x.Value).Returns(testConfig); + + _dataServiceClient.Setup(x => x.Update(It.IsAny())).ReturnsAsync(true); + _dataServiceClient.Setup(x => x.Add(It.IsAny())).ReturnsAsync(true); + _pdsProcessor = new PdsProcessor(_mockLogger.Object, _mockCreateBasicParticipantData.Object, _dataServiceClient.Object, _addBatchToQueue.Object, _retrievePDSDemographicConfig.Object); + + } + + [TestMethod] + public async System.Threading.Tasks.Task ProcessPdsNotFoundResponse_ProcessesResponse_SendsForDistribution() + { + var errorResponse = new PdsErrorResponse() + { + issue = new List() + { + new PdsIssue() + { + code = "", + details = new PdsErrorDetails() + { + coding = new List() + { + new PdsCoding() + { + code = "INVALIDATED_RESOURCE" + } + } + } + } + } + }; + + HttpResponseMessage httpResponseMessage = new HttpResponseMessage(); + httpResponseMessage.Content = new StringContent(JsonSerializer.Serialize(errorResponse)); + + await _pdsProcessor.ProcessPdsNotFoundResponse(httpResponseMessage, nhsNumber); + + _mockLogger.Verify(x => x.Log(It.Is(l => l == LogLevel.Information), + It.IsAny(), + It.Is((v, t) => v.ToString().Contains($"Sending record to the update queue.")), + It.IsAny(), + It.IsAny>()), + Times.Once); + + _mockLogger.Verify(x => x.Log(It.Is(l => l == LogLevel.Error), + It.IsAny(), + It.Is((v, t) => v.ToString().Contains($"the PDS function has returned a 404 error. function now stopping processing")), + It.IsAny(), + It.IsAny>()), + Times.Never); + } + + [TestMethod] + public async System.Threading.Tasks.Task ProcessPdsNotFoundResponse_ProcessesResponse_Logs404Happened() + { + var errorResponse = new PdsErrorResponse() + { + issue = new List() + { + new PdsIssue() + { + code = "", + details = new PdsErrorDetails() + { + coding = new List() + { + new PdsCoding() + { + code = "" + } + } + } + } + } + }; + + HttpResponseMessage httpResponseMessage = new HttpResponseMessage(); + httpResponseMessage.Content = new StringContent(JsonSerializer.Serialize(errorResponse)); + + await _pdsProcessor.ProcessPdsNotFoundResponse(httpResponseMessage, nhsNumber); + + _mockLogger.Verify(x => x.Log(It.Is(l => l == LogLevel.Information), + It.IsAny(), + It.Is((v, t) => v.ToString().Contains($"Sending record to the update queue.")), + It.IsAny(), + It.IsAny>()), + Times.Never); + + _mockLogger.Verify(x => x.Log(It.Is(l => l == LogLevel.Error), + It.IsAny(), + It.Is((v, t) => v.ToString().Contains($"the PDS function has returned a 404 error. function now stopping processing")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } + + + [TestMethod] + public async System.Threading.Tasks.Task UpsertDemographicRecordFromPDS_Updatesrecord_true() + { + _dataServiceClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())).ReturnsAsync(new ParticipantDemographic() + { + NhsNumber = 1111110662 + }); + + var res = await _pdsProcessor.UpsertDemographicRecordFromPDS(new ParticipantDemographic()); + + _mockLogger.Verify(x => x.Log(It.Is(l => l == LogLevel.Information), + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("Successfully updated Participant Demographic.")), + It.IsAny(), + It.IsAny>()), + Times.Once); + + _mockLogger.Verify(x => x.Log(It.Is(l => l == LogLevel.Information), + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("Participant Demographic record found, attempting to update Participant Demographic.")), + It.IsAny(), + It.IsAny>()), + Times.Once); + + Assert.IsTrue(res); + } + + + [TestMethod] + public async System.Threading.Tasks.Task UpsertDemographicRecordFromPDS_AddsNewrecord_true() + { + var res = await _pdsProcessor.UpsertDemographicRecordFromPDS(new ParticipantDemographic()); + + _mockLogger.Verify(x => x.Log(It.Is(l => l == LogLevel.Information), + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("Successfully updated Participant Demographic.")), + It.IsAny(), + It.IsAny>()), + Times.Never); + + _mockLogger.Verify(x => x.Log(It.Is(l => l == LogLevel.Information), + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("Participant Demographic record found, attempting to update Participant Demographic.")), + It.IsAny(), + It.IsAny>()), + Times.Never); + + _mockLogger.Verify(x => x.Log(It.Is(l => l == LogLevel.Information), + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("Successfully added Participant Demographic.")), + It.IsAny(), + It.IsAny>()), + Times.Once); + + + Assert.IsTrue(res); + } + + [TestMethod] + public async System.Threading.Tasks.Task UpsertDemographicRecordFromPDS_FailsToAddNewRecord_False() + { + _dataServiceClient.Setup(x => x.Add(It.IsAny())).ReturnsAsync(false); + var res = await _pdsProcessor.UpsertDemographicRecordFromPDS(new ParticipantDemographic()); + + _mockLogger.Verify(x => x.Log(It.Is(l => l == LogLevel.Information), + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("Successfully updated Participant Demographic.")), + It.IsAny(), + It.IsAny>()), + Times.Never); + + _mockLogger.Verify(x => x.Log(It.Is(l => l == LogLevel.Information), + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("Participant Demographic record found, attempting to update Participant Demographic.")), + It.IsAny(), + It.IsAny>()), + Times.Never); + + _mockLogger.Verify(x => x.Log(It.Is(l => l == LogLevel.Information), + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("Successfully added Participant Demographic.")), + It.IsAny(), + It.IsAny>()), + Times.Never); + + Assert.IsFalse(res); + } + + [TestMethod] + public async System.Threading.Tasks.Task UpsertDemographicRecordFromPDS_UpdateRecordFails_False() + { + _dataServiceClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())).ReturnsAsync(new ParticipantDemographic() + { + NhsNumber = 1111110662 + }); + _dataServiceClient.Setup(x => x.Update(It.IsAny())).ReturnsAsync(false); + + + var res = await _pdsProcessor.UpsertDemographicRecordFromPDS(new ParticipantDemographic()); + + _mockLogger.Verify(x => x.Log(It.Is(l => l == LogLevel.Information), + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("Successfully updated Participant Demographic.")), + It.IsAny(), + It.IsAny>()), + Times.Never); + + _mockLogger.Verify(x => x.Log(It.Is(l => l == LogLevel.Information), + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("Participant Demographic record found, attempting to update Participant Demographic.")), + It.IsAny(), + It.IsAny>()), + Times.Once); + + _mockLogger.Verify(x => x.Log(It.Is(l => l == LogLevel.Error), + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("Failed to update Participant Demographic.")), + It.IsAny(), + It.IsAny>()), + Times.Once); + + + Assert.IsFalse(res); + } + + +} \ No newline at end of file diff --git a/tests/PdsProcessorTests/PdsProcessorTests.csproj b/tests/PdsProcessorTests/PdsProcessorTests.csproj new file mode 100644 index 0000000000..e35c6763c2 --- /dev/null +++ b/tests/PdsProcessorTests/PdsProcessorTests.csproj @@ -0,0 +1,29 @@ + + + + {F2D368CE-F338-42E1-8083-7052F9DAA3F7} + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + diff --git a/tests/UnitTests/DemographicServicesTests/RetrievePDSDemographicTests/RetrievePDSDemographicTests.cs b/tests/UnitTests/DemographicServicesTests/RetrievePDSDemographicTests/RetrievePDSDemographicTests.cs index c6338a692f..59796cc211 100644 --- a/tests/UnitTests/DemographicServicesTests/RetrievePDSDemographicTests/RetrievePDSDemographicTests.cs +++ b/tests/UnitTests/DemographicServicesTests/RetrievePDSDemographicTests/RetrievePDSDemographicTests.cs @@ -20,12 +20,12 @@ public class RetrievePdsDemographicTests : DatabaseTestBaseSetup _mockHttpClientFunction = new(); private static readonly Mock> _mockConfig = new(); private static readonly Mock _mockFhirPatientDemographicMapper = new(); - private static readonly Mock> _mockParticipantDemographicClient = new(); - private static readonly Mock _mockCreateBasicParticipantService = new(); - private static readonly Mock _mockAddBatchToQueue = new(); - private static Mock _bearerTokenService = new(); + private static Mock _httpClientFunction = new(); + + private static readonly Mock _mockPdsProcessor = new(); + private const string _validNhsNumber = "3112728165"; private const long _validNhsNumberLong = 3112728165; @@ -34,13 +34,11 @@ public RetrievePdsDemographicTests() : base((conn, logger, transaction, command, new RetrievePdsDemographic( logger, response, - _mockHttpClientFunction.Object, + _httpClientFunction.Object, _mockFhirPatientDemographicMapper.Object, _mockConfig.Object, - _mockParticipantDemographicClient.Object, - _mockCreateBasicParticipantService.Object, - _mockAddBatchToQueue.Object, - _bearerTokenService.Object + _bearerTokenService.Object, + _mockPdsProcessor.Object )) { CreateHttpResponseMock(); From 6e127306c483d7fc77c56fa7d0d61a7996787595 Mon Sep 17 00:00:00 2001 From: SamRobinson75684 Date: Mon, 11 Aug 2025 14:27:45 +0100 Subject: [PATCH 7/7] chore: addressing comments on pr --- .../RetrievePDSDemographic/PdsProcessor.cs | 20 ++++++++++++++++--- tests/PdsProcessorTests/PdsProcessorTests.cs | 17 ++++++---------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsProcessor.cs b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsProcessor.cs index 670dbadfd3..73ccb6bdeb 100644 --- a/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsProcessor.cs +++ b/application/CohortManager/src/Functions/DemographicServices/RetrievePDSDemographic/PdsProcessor.cs @@ -32,6 +32,12 @@ public PdsProcessor( _config = retrievePDSDemographicConfig.Value; } + /// + /// processes pds error responses. Sends a record to distribute participant via service bus + /// + /// + /// + /// public async Task ProcessPdsNotFoundResponse(HttpResponseMessage pdsResponse, string nhsNumber) { var errorResponse = await pdsResponse!.Content.ReadFromJsonAsync(); @@ -54,11 +60,15 @@ public async Task ProcessPdsNotFoundResponse(HttpResponseMessage pdsResponse, st _logger.LogError("the PDS function has returned a 404 error. function now stopping processing"); } - + /// + /// sends a participant record to the distribute service bus topic + /// + /// + /// public async Task ProcessRecord(Participant participant) { var updateRecord = new ConcurrentQueue(); - participant.RecordType = participant.RecordType = Actions.Removed; + participant.RecordType = Actions.Removed; var basicParticipantCsvRecord = new BasicParticipantCsvRecord { @@ -73,7 +83,11 @@ public async Task ProcessRecord(Participant participant) await _addBatchToQueue.ProcessBatch(updateRecord, _config.ParticipantManagementTopic); } - + /// + /// adds or updates a demographic record depending on if an record already exists in the database + /// + /// + /// public async Task UpsertDemographicRecordFromPDS(ParticipantDemographic participantDemographic) { ParticipantDemographic oldParticipantDemographic = await _participantDemographicClient.GetSingleByFilter(i => i.NhsNumber == participantDemographic.NhsNumber); diff --git a/tests/PdsProcessorTests/PdsProcessorTests.cs b/tests/PdsProcessorTests/PdsProcessorTests.cs index 7d9f561127..92bf4dec9e 100644 --- a/tests/PdsProcessorTests/PdsProcessorTests.cs +++ b/tests/PdsProcessorTests/PdsProcessorTests.cs @@ -1,13 +1,8 @@ namespace NHS.CohortManager.Tests.PdsProcessorTests; -using System.Collections.Concurrent; -using System.Data.Services.Client; -using System.Runtime.CompilerServices; using System.Text.Json; using Common; using DataServices.Client; -using Hl7.Fhir.ElementModel.Types; -using Hl7.Fhir.Model; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Model; @@ -52,7 +47,7 @@ public PdsProcessorTests() } [TestMethod] - public async System.Threading.Tasks.Task ProcessPdsNotFoundResponse_ProcessesResponse_SendsForDistribution() + public async Task ProcessPdsNotFoundResponse_ProcessesResponse_SendsForDistribution() { var errorResponse = new PdsErrorResponse() { @@ -96,7 +91,7 @@ public async System.Threading.Tasks.Task ProcessPdsNotFoundResponse_ProcessesRes } [TestMethod] - public async System.Threading.Tasks.Task ProcessPdsNotFoundResponse_ProcessesResponse_Logs404Happened() + public async Task ProcessPdsNotFoundResponse_WithNonInvalidatedResource_LogsError() { var errorResponse = new PdsErrorResponse() { @@ -141,7 +136,7 @@ public async System.Threading.Tasks.Task ProcessPdsNotFoundResponse_ProcessesRes [TestMethod] - public async System.Threading.Tasks.Task UpsertDemographicRecordFromPDS_Updatesrecord_true() + public async Task UpsertDemographicRecordFromPDS_WithExistingRecord_ReturnsTrue() { _dataServiceClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())).ReturnsAsync(new ParticipantDemographic() { @@ -169,7 +164,7 @@ public async System.Threading.Tasks.Task UpsertDemographicRecordFromPDS_Updatesr [TestMethod] - public async System.Threading.Tasks.Task UpsertDemographicRecordFromPDS_AddsNewrecord_true() + public async Task UpsertDemographicRecordFromPDS_WithNewRecord_ReturnsTrue() { var res = await _pdsProcessor.UpsertDemographicRecordFromPDS(new ParticipantDemographic()); @@ -199,7 +194,7 @@ public async System.Threading.Tasks.Task UpsertDemographicRecordFromPDS_AddsNewr } [TestMethod] - public async System.Threading.Tasks.Task UpsertDemographicRecordFromPDS_FailsToAddNewRecord_False() + public async Task UpsertDemographicRecordFromPDS_FailsToAddNewRecord_ReturnsFalse() { _dataServiceClient.Setup(x => x.Add(It.IsAny())).ReturnsAsync(false); var res = await _pdsProcessor.UpsertDemographicRecordFromPDS(new ParticipantDemographic()); @@ -229,7 +224,7 @@ public async System.Threading.Tasks.Task UpsertDemographicRecordFromPDS_FailsToA } [TestMethod] - public async System.Threading.Tasks.Task UpsertDemographicRecordFromPDS_UpdateRecordFails_False() + public async Task UpsertDemographicRecordFromPDS_UpdateRecordFails_ReturnsFalse() { _dataServiceClient.Setup(x => x.GetSingleByFilter(It.IsAny>>())).ReturnsAsync(new ParticipantDemographic() {