From f4c3d7b2c78b6d94fa1439c7ab973ebcd4eb4500 Mon Sep 17 00:00:00 2001 From: Tim Stephenson <231503406+tstephen-nhs@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:35:02 +0000 Subject: [PATCH 1/6] chore: upgrade service search api to v3 --- packages/serviceSearchClient/src/live-serviceSearch-client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/serviceSearchClient/src/live-serviceSearch-client.ts b/packages/serviceSearchClient/src/live-serviceSearch-client.ts index 536a806a7..eb8ae31ff 100644 --- a/packages/serviceSearchClient/src/live-serviceSearch-client.ts +++ b/packages/serviceSearchClient/src/live-serviceSearch-client.ts @@ -88,7 +88,7 @@ export class LiveServiceSearchClient implements ServiceSearchClient { "Subscription-Key": process.env.ServiceSearchApiKey } this.baseQueryParams = { - "api-version": 2, + "api-version": 3, "searchFields": "ODSCode", "$filter": "OrganisationTypeId eq 'PHA' and OrganisationSubType eq 'DistanceSelling'", "$select": "URL,OrganisationSubType", From f0a2bd2a10725a9574b917ff58c6029069140dc8 Mon Sep 17 00:00:00 2001 From: Tim Stephenson <231503406+tstephen-nhs@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:58:12 +0000 Subject: [PATCH 2/6] chore: service search v3 in dev --- .github/workflows/pull_request.yml | 2 +- .../serviceSearchClient/src/live-serviceSearch-client.ts | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b007d1ff3..2e912cc71 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -121,7 +121,7 @@ jobs: REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} TARGET_SPINE_SERVER: ${{ secrets.DEV_TARGET_SPINE_SERVER }} - TARGET_SERVICE_SEARCH_SERVER: ${{ secrets.DEV_TARGET_SERVICE_SEARCH_SERVER }} + TARGET_SERVICE_SEARCH_SERVER: ${{ secrets.DEV_TARGET_SERVICE_SEARCH_v3_SERVER }} release_sandbox_code: needs: [get_issue_number, package_code, get_commit_id] diff --git a/packages/serviceSearchClient/src/live-serviceSearch-client.ts b/packages/serviceSearchClient/src/live-serviceSearch-client.ts index eb8ae31ff..cc60d8818 100644 --- a/packages/serviceSearchClient/src/live-serviceSearch-client.ts +++ b/packages/serviceSearchClient/src/live-serviceSearch-client.ts @@ -164,6 +164,12 @@ export class LiveServiceSearchClient implements ServiceSearchClient { } private getServiceSearchEndpoint() { - return `${this.SERVICE_SEARCH_URL_SCHEME}://${this.SERVICE_SEARCH_ENDPOINT}/service-search` + const baseUrl = `${this.SERVICE_SEARCH_URL_SCHEME}://${this.SERVICE_SEARCH_ENDPOINT}` + if (this.SERVICE_SEARCH_ENDPOINT?.toLowerCase().includes("api.service.nhs.uk")) { + // service search v3 + return `${baseUrl}/service-search-api/` + } + // service search v2 + return `${baseUrl}/service-search` } } From 58ebd8b3a744e658d5807fc811beef83cbda1f66 Mon Sep 17 00:00:00 2001 From: Tim Stephenson <231503406+tstephen-nhs@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:14:41 +0000 Subject: [PATCH 3/6] chore: DRY tests & use service search v3 --- .../tests/statusUpdate.test.ts | 6 +-- .../tests/test-handler.test.ts | 32 +++--------- packages/getMyPrescriptions/tests/utils.ts | 14 +++--- packages/serviceSearchClient/src/index.ts | 3 +- .../src/live-serviceSearch-client.ts | 49 ++++++++----------- .../tests/live-serviceSearch-client.test.ts | 7 +-- 6 files changed, 45 insertions(+), 66 deletions(-) diff --git a/packages/getMyPrescriptions/tests/statusUpdate.test.ts b/packages/getMyPrescriptions/tests/statusUpdate.test.ts index 9bf72f8b6..673a5e060 100644 --- a/packages/getMyPrescriptions/tests/statusUpdate.test.ts +++ b/packages/getMyPrescriptions/tests/statusUpdate.test.ts @@ -29,7 +29,7 @@ import { newHandler, stateMachineEventHandler } from "../src/getMyPrescriptions" -import {EXPECTED_TRACE_IDS, SERVICE_SEARCH_PARAMS} from "./utils" +import {EXPECTED_TRACE_IDS, SERVICE_SEARCH_PARAMS, getServiceSearchEndpoint} from "./utils" import {createMockedPfPConfig, MockedPfPConfig, setupTestEnvironment} from "@pfp-common/testing" import {LogLevel} from "@aws-lambda-powertools/logger/types" import {Logger} from "@aws-lambda-powertools/logger" @@ -143,10 +143,10 @@ describe("Unit tests for statusUpdate, via handler", function () { const event: GetMyPrescriptionsEvent = JSON.parse(exampleEvent) mock - .onGet("https://service-search/service-search", {params: {...SERVICE_SEARCH_PARAMS, search: "flm49"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "flm49"}}) .reply(200, JSON.parse(pharmacy2uResponse)) mock - .onGet("https://service-search/service-search", {params: {...SERVICE_SEARCH_PARAMS, search: "few08"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "few08"}}) .reply(200, JSON.parse(pharmicaResponse)) mock.onGet("https://spine/mm/patientfacingprescriptions").reply(200, JSON.parse(exampleInteractionResponse)) diff --git a/packages/getMyPrescriptions/tests/test-handler.test.ts b/packages/getMyPrescriptions/tests/test-handler.test.ts index dcca05001..a5d431593 100644 --- a/packages/getMyPrescriptions/tests/test-handler.test.ts +++ b/packages/getMyPrescriptions/tests/test-handler.test.ts @@ -27,7 +27,7 @@ import { import {HEADERS, StateMachineFunctionResponseBody, TIMEOUT_RESPONSE} from "../src/responses" import "./toMatchJsonLogMessage" -import {EXPECTED_TRACE_IDS} from "./utils" +import {EXPECTED_TRACE_IDS, getServiceSearchEndpoint, SERVICE_SEARCH_PARAMS} from "./utils" import {LogLevel} from "@aws-lambda-powertools/logger/types" import {createSpineClient} from "@NHSDigital/eps-spine-client" import {MiddyfiedHandler} from "@middy/core" @@ -355,14 +355,6 @@ describe("Unit tests for app handler including service search", function () { let testEnv: ReturnType let mockedConfig: MockedPfPConfig - const queryParams = { - "api-version": 2, - searchFields: "ODSCode", - $filter: "OrganisationTypeId eq 'PHA' and OrganisationSubType eq 'DistanceSelling'", - $select: "URL,OrganisationSubType", - $top: 1 - } - beforeEach(() => { testEnv = setupTestEnvironment() mockedConfig = createMockedPfPConfig([TC008_NHS_NUMBER]) @@ -394,11 +386,11 @@ describe("Unit tests for app handler including service search", function () { const event: GetMyPrescriptionsEvent = JSON.parse(exampleStateMachineEvent) mock - .onGet("https://service-search/service-search", {params: {...queryParams, search: "flm49"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "flm49"}}) .reply(200, JSON.parse(pharmacy2uResponse)) mock - .onGet("https://service-search/service-search", {params: {...queryParams, search: "few08"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "few08"}}) .reply(200, JSON.parse(pharmicaResponse)) mock.onGet("https://spine/mm/patientfacingprescriptions").reply(200, JSON.parse(exampleInteractionResponse)) @@ -434,11 +426,11 @@ describe("Unit tests for app handler including service search", function () { mock.onGet("https://spine/mm/patientfacingprescriptions").reply(200, interactionResponse) mock - .onGet("https://service-search/service-search", {params: {...queryParams, search: "flm49"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "flm49"}}) .reply(200, JSON.parse(pharmacy2uResponse)) mock - .onGet("https://service-search/service-search", {params: {...queryParams, search: "few08"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "few08"}}) .reply(200, JSON.parse(pharmicaResponse)) const event: GetMyPrescriptionsEvent = JSON.parse(exampleStateMachineEvent) @@ -465,7 +457,7 @@ describe("Unit tests for app handler including service search", function () { mock.onGet("https://spine/mm/patientfacingprescriptions").reply(200, exampleResponse) // eslint-disable-next-line @typescript-eslint/no-unused-vars - mock.onGet("https://service-search/service-search").reply(function (config) { + mock.onGet(getServiceSearchEndpoint()).reply(function (config) { return new Promise((resolve) => setTimeout(() => resolve([200, {}]), 15_000)) }) @@ -502,14 +494,6 @@ describe("Unit tests for logging functionality", function () { let testEnv: ReturnType let mockedConfig: MockedPfPConfig - const queryParams = { - "api-version": 2, - searchFields: "ODSCode", - $filter: "OrganisationTypeId eq 'PHA' and OrganisationSubType eq 'DistanceSelling'", - $select: "URL,OrganisationSubType", - $top: 1 - } - beforeEach(() => { testEnv = setupTestEnvironment() mockedConfig = createMockedPfPConfig([TC008_NHS_NUMBER]) @@ -590,11 +574,11 @@ describe("Unit tests for logging functionality", function () { mock.onGet("https://spine/mm/patientfacingprescriptions").reply(200, interactionResponse) mock - .onGet("https://service-search/service-search", {params: {...queryParams, search: "flm49"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "flm49"}}) .reply(200, JSON.parse(pharmacy2uResponse)) mock - .onGet("https://service-search/service-search", {params: {...queryParams, search: "few08"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "few08"}}) .reply(200, JSON.parse(pharmicaResponse)) const event: GetMyPrescriptionsEvent = JSON.parse(exampleStateMachineEvent) diff --git a/packages/getMyPrescriptions/tests/utils.ts b/packages/getMyPrescriptions/tests/utils.ts index 32c784675..e93bd872d 100644 --- a/packages/getMyPrescriptions/tests/utils.ts +++ b/packages/getMyPrescriptions/tests/utils.ts @@ -1,6 +1,10 @@ import {jest} from "@jest/globals" import {Organization} from "fhir/r4" import {TraceIDs} from "../src/responses" +import { + SERVICE_SEARCH_BASE_QUERY_PARAMS, + getServiceSearchEndpoint +} from "@prescriptionsforpatients/serviceSearchClient" // Uses unstable jest method to enable mocking while using ESM. To be replaced in future. export function mockInternalDependency(modulePath: string, module: object, dependency: string) { @@ -12,13 +16,9 @@ export function mockInternalDependency(modulePath: string, module: object, depen return mockDependency } -export const SERVICE_SEARCH_PARAMS = { - "api-version": 2, - searchFields: "ODSCode", - $filter: "OrganisationTypeId eq 'PHA' and OrganisationSubType eq 'DistanceSelling'", - $select: "URL,OrganisationSubType", - $top: 1 -} +// Re-export for convenience in tests +export const SERVICE_SEARCH_PARAMS = SERVICE_SEARCH_BASE_QUERY_PARAMS +export {getServiceSearchEndpoint} export const EXPECTED_TRACE_IDS: TraceIDs = { "apigw-request-id": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", diff --git a/packages/serviceSearchClient/src/index.ts b/packages/serviceSearchClient/src/index.ts index d0875767b..14a7d21c4 100644 --- a/packages/serviceSearchClient/src/index.ts +++ b/packages/serviceSearchClient/src/index.ts @@ -1,3 +1,4 @@ import {createServiceSearchClient, ServiceSearchClient} from "./serviceSearch-client" +import {SERVICE_SEARCH_BASE_QUERY_PARAMS, getServiceSearchEndpoint} from "./live-serviceSearch-client" -export {createServiceSearchClient, ServiceSearchClient} +export {createServiceSearchClient, ServiceSearchClient, SERVICE_SEARCH_BASE_QUERY_PARAMS, getServiceSearchEndpoint} diff --git a/packages/serviceSearchClient/src/live-serviceSearch-client.ts b/packages/serviceSearchClient/src/live-serviceSearch-client.ts index cc60d8818..6cf86f87c 100644 --- a/packages/serviceSearchClient/src/live-serviceSearch-client.ts +++ b/packages/serviceSearchClient/src/live-serviceSearch-client.ts @@ -18,19 +18,29 @@ export type ServiceSearchData = { "value": Array } +export const SERVICE_SEARCH_BASE_QUERY_PARAMS = { + "api-version": 3, + "searchFields": "ODSCode", + "$filter": "OrganisationTypeId eq 'PHA' and OrganisationSubType eq 'DistanceSelling'", + "$select": "URL,OrganisationSubType", + "$top": 1 +} as const + +export function getServiceSearchEndpoint(targetServer?: string): string { + const endpoint = targetServer || process.env.TargetServiceSearchServer || "service-search" + const baseUrl = `https://${endpoint}` + if (endpoint.toLowerCase().includes("api.service.nhs.uk")) { + // service search v3 + return `${baseUrl}/service-search-api/` + } + // service search v2 + return `${baseUrl}/service-search` +} + export class LiveServiceSearchClient implements ServiceSearchClient { - private readonly SERVICE_SEARCH_URL_SCHEME = "https" - private readonly SERVICE_SEARCH_ENDPOINT = process.env.TargetServiceSearchServer private readonly axiosInstance: AxiosInstance private readonly logger: Logger private readonly outboundHeaders: {"Subscription-Key": string | undefined} - private readonly baseQueryParams: { - "api-version": number, - "searchFields": string, - "$filter": string, - "$select": string, - "$top": number - } constructor(logger: Logger) { this.logger = logger @@ -87,19 +97,12 @@ export class LiveServiceSearchClient implements ServiceSearchClient { this.outboundHeaders = { "Subscription-Key": process.env.ServiceSearchApiKey } - this.baseQueryParams = { - "api-version": 3, - "searchFields": "ODSCode", - "$filter": "OrganisationTypeId eq 'PHA' and OrganisationSubType eq 'DistanceSelling'", - "$select": "URL,OrganisationSubType", - "$top": 1 - } } async searchService(odsCode: string): Promise { try { - const address = this.getServiceSearchEndpoint() - const queryParams = {...this.baseQueryParams, search: odsCode} + const address = getServiceSearchEndpoint() + const queryParams = {...SERVICE_SEARCH_BASE_QUERY_PARAMS, search: odsCode} this.logger.info(`making request to ${address} with ods code ${odsCode}`, {odsCode: odsCode}) const response = await this.axiosInstance.get(address, { @@ -162,14 +165,4 @@ export class LiveServiceSearchClient implements ServiceSearchClient { delete error.request.headers[headerKey] } } - - private getServiceSearchEndpoint() { - const baseUrl = `${this.SERVICE_SEARCH_URL_SCHEME}://${this.SERVICE_SEARCH_ENDPOINT}` - if (this.SERVICE_SEARCH_ENDPOINT?.toLowerCase().includes("api.service.nhs.uk")) { - // service search v3 - return `${baseUrl}/service-search-api/` - } - // service search v2 - return `${baseUrl}/service-search` - } } diff --git a/packages/serviceSearchClient/tests/live-serviceSearch-client.test.ts b/packages/serviceSearchClient/tests/live-serviceSearch-client.test.ts index cc8219193..39decbfc3 100644 --- a/packages/serviceSearchClient/tests/live-serviceSearch-client.test.ts +++ b/packages/serviceSearchClient/tests/live-serviceSearch-client.test.ts @@ -28,9 +28,10 @@ describe("live serviceSearch client", () => { jest.restoreAllMocks() }) - // Private helper tests - test("getServiceSearchEndpoint returns correct URL", () => { - const endpoint = client["getServiceSearchEndpoint"]() + // Helper function tests + test("getServiceSearchEndpoint returns correct URL", async () => { + const {getServiceSearchEndpoint} = await import("../src/live-serviceSearch-client.js") + const endpoint = getServiceSearchEndpoint() expect(endpoint).toBe(serviceSearchUrl) }) From 59c44bb7b0404a6ac764a84f9cf84a4e976aebee Mon Sep 17 00:00:00 2001 From: Tim Stephenson <231503406+tstephen-nhs@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:08:30 +0000 Subject: [PATCH 4/6] chore: placate SQ --- .../src/getMyPrescriptions.ts | 4 +- packages/getMyPrescriptions/src/utils.ts | 4 -- .../tests/statusUpdate.test.ts | 26 ++++++----- .../tests/test-handler.test.ts | 44 +++++++++++-------- packages/getMyPrescriptions/tests/utils.ts | 8 ---- packages/serviceSearchClient/src/index.ts | 6 +-- 6 files changed, 45 insertions(+), 47 deletions(-) diff --git a/packages/getMyPrescriptions/src/getMyPrescriptions.ts b/packages/getMyPrescriptions/src/getMyPrescriptions.ts index 8ca4c9664..07676f4c5 100644 --- a/packages/getMyPrescriptions/src/getMyPrescriptions.ts +++ b/packages/getMyPrescriptions/src/getMyPrescriptions.ts @@ -24,7 +24,7 @@ import { ResponseFunc } from "./responses" import {extractNHSNumber, NHSNumberValidationError, validateNHSNumber} from "./extractNHSNumber" -import {deepCopy, hasTimedOut, jobWithTimeout} from "./utils" +import {hasTimedOut, jobWithTimeout} from "./utils" import {buildStatusUpdateData, shouldGetStatusUpdates} from "./statusUpdate" import {extractOdsCodes, isolateOperationOutcome} from "./fhirUtils" import {pfpConfig, PfPConfig} from "@pfp-common/utilities" @@ -130,7 +130,7 @@ async function eventHandler( const statusUpdateData = includeStatusUpdateData ? buildStatusUpdateData(logger, searchsetBundle) : undefined const distanceSelling = new DistanceSelling(servicesCache, logger) - const distanceSellingBundle = deepCopy(searchsetBundle) + const distanceSellingBundle = structuredClone(searchsetBundle) const distanceSellingCallout = distanceSelling.search(distanceSellingBundle) const distanceSellingResponse = await jobWithTimeout(params.serviceSearchTimeoutMs, distanceSellingCallout) diff --git a/packages/getMyPrescriptions/src/utils.ts b/packages/getMyPrescriptions/src/utils.ts index a7d5f15a1..326df9b6d 100644 --- a/packages/getMyPrescriptions/src/utils.ts +++ b/packages/getMyPrescriptions/src/utils.ts @@ -1,7 +1,3 @@ -export function deepCopy(obj: T): T { - return JSON.parse(JSON.stringify(obj)) -} - export interface Timeout { isTimeout: true } diff --git a/packages/getMyPrescriptions/tests/statusUpdate.test.ts b/packages/getMyPrescriptions/tests/statusUpdate.test.ts index 673a5e060..1d195a81e 100644 --- a/packages/getMyPrescriptions/tests/statusUpdate.test.ts +++ b/packages/getMyPrescriptions/tests/statusUpdate.test.ts @@ -7,18 +7,29 @@ import { jest } from "@jest/globals" import axios from "axios" +import MockAdapter from "axios-mock-adapter" import {Bundle, MedicationRequest} from "fhir/r4" import {APIGatewayProxyResult as LambdaResult, Context} from "aws-lambda" -import MockAdapter from "axios-mock-adapter" +import {LogLevel} from "@aws-lambda-powertools/logger/types" +import {Logger} from "@aws-lambda-powertools/logger" +import {createSpineClient} from "@NHSDigital/eps-spine-client" +import {MiddyfiedHandler} from "@middy/core" import { + createMockedPfPConfig, helloworldContext, mockAPIResponseBody as mockResponseBody, mockInteractionResponseBody, mockPharmacy2uResponse, mockPharmicaResponse, - mockStateMachineInputEvent + mockStateMachineInputEvent, + MockedPfPConfig, + setupTestEnvironment } from "@pfp-common/testing" +import { + SERVICE_SEARCH_BASE_QUERY_PARAMS, + getServiceSearchEndpoint +} from "@prescriptionsforpatients/serviceSearchClient" import {buildStatusUpdateData} from "../src/statusUpdate" import {StateMachineFunctionResponseBody} from "../src/responses" @@ -29,12 +40,7 @@ import { newHandler, stateMachineEventHandler } from "../src/getMyPrescriptions" -import {EXPECTED_TRACE_IDS, SERVICE_SEARCH_PARAMS, getServiceSearchEndpoint} from "./utils" -import {createMockedPfPConfig, MockedPfPConfig, setupTestEnvironment} from "@pfp-common/testing" -import {LogLevel} from "@aws-lambda-powertools/logger/types" -import {Logger} from "@aws-lambda-powertools/logger" -import {createSpineClient} from "@NHSDigital/eps-spine-client" -import {MiddyfiedHandler} from "@middy/core" +import {EXPECTED_TRACE_IDS} from "./utils" const exampleEvent = JSON.stringify(mockStateMachineInputEvent) const exampleInteractionResponse = JSON.stringify(mockInteractionResponseBody) @@ -143,10 +149,10 @@ describe("Unit tests for statusUpdate, via handler", function () { const event: GetMyPrescriptionsEvent = JSON.parse(exampleEvent) mock - .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "flm49"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_BASE_QUERY_PARAMS, search: "flm49"}}) .reply(200, JSON.parse(pharmacy2uResponse)) mock - .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "few08"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_BASE_QUERY_PARAMS, search: "few08"}}) .reply(200, JSON.parse(pharmicaResponse)) mock.onGet("https://spine/mm/patientfacingprescriptions").reply(200, JSON.parse(exampleInteractionResponse)) diff --git a/packages/getMyPrescriptions/tests/test-handler.test.ts b/packages/getMyPrescriptions/tests/test-handler.test.ts index a5d431593..8ad14bb6c 100644 --- a/packages/getMyPrescriptions/tests/test-handler.test.ts +++ b/packages/getMyPrescriptions/tests/test-handler.test.ts @@ -1,12 +1,6 @@ import {APIGatewayProxyResult as LambdaResult, Context} from "aws-lambda" -import { - DEFAULT_HANDLER_PARAMS, - newHandler, - GetMyPrescriptionsEvent, - stateMachineEventHandler, - STATE_MACHINE_MIDDLEWARE -} from "../src/getMyPrescriptions" import {Logger} from "@aws-lambda-powertools/logger" +import {LogLevel} from "@aws-lambda-powertools/logger/types" import axios from "axios" import MockAdapter from "axios-mock-adapter" import { @@ -15,23 +9,35 @@ import { it, jest } from "@jest/globals" +import {createSpineClient} from "@NHSDigital/eps-spine-client" +import {MiddyfiedHandler} from "@middy/core" import { + createMockedPfPConfig, mockAPIResponseBody as mockResponseBody, mockInteractionResponseBody, mockPharmacy2uResponse, mockPharmicaResponse, helloworldContext, - mockStateMachineInputEvent + mockStateMachineInputEvent, + MockedPfPConfig, + setupTestEnvironment } from "@pfp-common/testing" +import { + SERVICE_SEARCH_BASE_QUERY_PARAMS, + getServiceSearchEndpoint +} from "@prescriptionsforpatients/serviceSearchClient" +import { + DEFAULT_HANDLER_PARAMS, + newHandler, + GetMyPrescriptionsEvent, + stateMachineEventHandler, + STATE_MACHINE_MIDDLEWARE +} from "../src/getMyPrescriptions" import {HEADERS, StateMachineFunctionResponseBody, TIMEOUT_RESPONSE} from "../src/responses" import "./toMatchJsonLogMessage" -import {EXPECTED_TRACE_IDS, getServiceSearchEndpoint, SERVICE_SEARCH_PARAMS} from "./utils" -import {LogLevel} from "@aws-lambda-powertools/logger/types" -import {createSpineClient} from "@NHSDigital/eps-spine-client" -import {MiddyfiedHandler} from "@middy/core" -import {createMockedPfPConfig, MockedPfPConfig, setupTestEnvironment} from "@pfp-common/testing" +import {EXPECTED_TRACE_IDS} from "./utils" const TC008_NHS_NUMBER = "9992387920" @@ -386,11 +392,11 @@ describe("Unit tests for app handler including service search", function () { const event: GetMyPrescriptionsEvent = JSON.parse(exampleStateMachineEvent) mock - .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "flm49"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_BASE_QUERY_PARAMS, search: "flm49"}}) .reply(200, JSON.parse(pharmacy2uResponse)) mock - .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "few08"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_BASE_QUERY_PARAMS, search: "few08"}}) .reply(200, JSON.parse(pharmicaResponse)) mock.onGet("https://spine/mm/patientfacingprescriptions").reply(200, JSON.parse(exampleInteractionResponse)) @@ -426,11 +432,11 @@ describe("Unit tests for app handler including service search", function () { mock.onGet("https://spine/mm/patientfacingprescriptions").reply(200, interactionResponse) mock - .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "flm49"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_BASE_QUERY_PARAMS, search: "flm49"}}) .reply(200, JSON.parse(pharmacy2uResponse)) mock - .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "few08"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_BASE_QUERY_PARAMS, search: "few08"}}) .reply(200, JSON.parse(pharmicaResponse)) const event: GetMyPrescriptionsEvent = JSON.parse(exampleStateMachineEvent) @@ -574,11 +580,11 @@ describe("Unit tests for logging functionality", function () { mock.onGet("https://spine/mm/patientfacingprescriptions").reply(200, interactionResponse) mock - .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "flm49"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_BASE_QUERY_PARAMS, search: "flm49"}}) .reply(200, JSON.parse(pharmacy2uResponse)) mock - .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_PARAMS, search: "few08"}}) + .onGet(getServiceSearchEndpoint(), {params: {...SERVICE_SEARCH_BASE_QUERY_PARAMS, search: "few08"}}) .reply(200, JSON.parse(pharmicaResponse)) const event: GetMyPrescriptionsEvent = JSON.parse(exampleStateMachineEvent) diff --git a/packages/getMyPrescriptions/tests/utils.ts b/packages/getMyPrescriptions/tests/utils.ts index e93bd872d..61b9ef8cd 100644 --- a/packages/getMyPrescriptions/tests/utils.ts +++ b/packages/getMyPrescriptions/tests/utils.ts @@ -1,10 +1,6 @@ import {jest} from "@jest/globals" import {Organization} from "fhir/r4" import {TraceIDs} from "../src/responses" -import { - SERVICE_SEARCH_BASE_QUERY_PARAMS, - getServiceSearchEndpoint -} from "@prescriptionsforpatients/serviceSearchClient" // Uses unstable jest method to enable mocking while using ESM. To be replaced in future. export function mockInternalDependency(modulePath: string, module: object, dependency: string) { @@ -16,10 +12,6 @@ export function mockInternalDependency(modulePath: string, module: object, depen return mockDependency } -// Re-export for convenience in tests -export const SERVICE_SEARCH_PARAMS = SERVICE_SEARCH_BASE_QUERY_PARAMS -export {getServiceSearchEndpoint} - export const EXPECTED_TRACE_IDS: TraceIDs = { "apigw-request-id": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", "nhsd-correlation-id": "test-request-id.test-correlation-id.rrt-5789322914740101037-b-aet2-20145-482635-2", diff --git a/packages/serviceSearchClient/src/index.ts b/packages/serviceSearchClient/src/index.ts index 14a7d21c4..a5e903bd4 100644 --- a/packages/serviceSearchClient/src/index.ts +++ b/packages/serviceSearchClient/src/index.ts @@ -1,4 +1,2 @@ -import {createServiceSearchClient, ServiceSearchClient} from "./serviceSearch-client" -import {SERVICE_SEARCH_BASE_QUERY_PARAMS, getServiceSearchEndpoint} from "./live-serviceSearch-client" - -export {createServiceSearchClient, ServiceSearchClient, SERVICE_SEARCH_BASE_QUERY_PARAMS, getServiceSearchEndpoint} +export {createServiceSearchClient, ServiceSearchClient} from "./serviceSearch-client" +export {SERVICE_SEARCH_BASE_QUERY_PARAMS, getServiceSearchEndpoint} from "./live-serviceSearch-client" From 264e2b76d19a5a3bb4be5b53946f0269e4fede03 Mon Sep 17 00:00:00 2001 From: Tim Stephenson <231503406+tstephen-nhs@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:08:30 +0000 Subject: [PATCH 5/6] chore: placate SQ --- .../serviceSearchClient/src/live-serviceSearch-client.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/serviceSearchClient/src/live-serviceSearch-client.ts b/packages/serviceSearchClient/src/live-serviceSearch-client.ts index 6cf86f87c..6eed794a2 100644 --- a/packages/serviceSearchClient/src/live-serviceSearch-client.ts +++ b/packages/serviceSearchClient/src/live-serviceSearch-client.ts @@ -49,17 +49,17 @@ export class LiveServiceSearchClient implements ServiceSearchClient { axiosRetry(this.axiosInstance, {retries: 3}) this.axiosInstance.interceptors.request.use((config) => { - config.headers["request-startTime"] = new Date().getTime() + config.headers["request-startTime"] = Date.now() return config }) this.axiosInstance.interceptors.response.use((response) => { - const currentTime = new Date().getTime() + const currentTime = Date.now() const startTime = response.config.headers["request-startTime"] this.logger.info("serviceSearch request duration", {serviceSearch_duration: currentTime - startTime}) return response }, (error) => { - const currentTime = new Date().getTime() + const currentTime = Date.now() const startTime = error.config?.headers["request-startTime"] this.logger.info("serviceSearch request duration", {serviceSearch_duration: currentTime - startTime}) From 40c9d560a456e32a0431b62ca54926dddcb6d5b5 Mon Sep 17 00:00:00 2001 From: Tim Stephenson <231503406+tstephen-nhs@users.noreply.github.com> Date: Mon, 22 Dec 2025 11:39:33 +0000 Subject: [PATCH 6/6] chore: service search v3 apikey in hdrs --- .../src/live-serviceSearch-client.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/serviceSearchClient/src/live-serviceSearch-client.ts b/packages/serviceSearchClient/src/live-serviceSearch-client.ts index 6eed794a2..0376c6199 100644 --- a/packages/serviceSearchClient/src/live-serviceSearch-client.ts +++ b/packages/serviceSearchClient/src/live-serviceSearch-client.ts @@ -40,7 +40,7 @@ export function getServiceSearchEndpoint(targetServer?: string): string { export class LiveServiceSearchClient implements ServiceSearchClient { private readonly axiosInstance: AxiosInstance private readonly logger: Logger - private readonly outboundHeaders: {"Subscription-Key": string | undefined} + private readonly outboundHeaders: {"apikey": string | undefined, "Subscription-Key": string | undefined} constructor(logger: Logger) { this.logger = logger @@ -95,7 +95,8 @@ export class LiveServiceSearchClient implements ServiceSearchClient { }) this.outboundHeaders = { - "Subscription-Key": process.env.ServiceSearchApiKey + "Subscription-Key": process.env.ServiceSearchApiKey, + "apikey": process.env.ServiceSearch3ApiKey } } @@ -157,12 +158,14 @@ export class LiveServiceSearchClient implements ServiceSearchClient { } stripApiKeyFromHeaders(error: AxiosError) { - const headerKey = "subscription-key" - if (error.response?.headers) { - delete error.response.headers[headerKey] - } - if (error.request?.headers) { - delete error.request.headers[headerKey] - } + const headerKeys = ["subscription-key", "apikey"] + headerKeys.forEach((key) => { + if (error.response?.headers) { + delete error.response.headers[key] + } + if (error.request?.headers) { + delete error.request.headers[key] + } + }) } }