From 0b7330bcd14b6938c67b5e32f9d832b106c37265 Mon Sep 17 00:00:00 2001 From: Mikolaj Miotk Date: Thu, 16 Apr 2026 15:29:32 +0200 Subject: [PATCH 1/2] [HOTE-803] fix: pre-commit all files --- .github/PULL_REQUEST_TEMPLATE.md | 1 + .github/instructions/tests.instructions.md | 17 ++- .github/instructions/ui.instructions.md | 13 +- .markdownlint.yaml | 21 ++- docs/developer-guides/Scripting_Docker.md | 24 +-- docs/developer-guides/Scripting_Terraform.md | 26 ++-- docs/user-guides/Scan_dependencies.md | 1 - docs/user-guides/Sign_Git_commits.md | 7 +- lambdas/eslint.config.mjs | 2 +- lambdas/scripts/build.ts | 2 +- lambdas/scripts/package.ts | 46 +++--- .../la-lookup.test.ts | 16 +- lambdas/src/get-order-lambda/index.test.ts | 11 +- .../get-order-lambda/order-bundle-builder.ts | 8 +- lambdas/src/hello-world-lambda/index.ts | 12 +- .../src/lib/auth/auth-token-service.test.ts | 4 +- lambdas/src/lib/auth/auth-token-service.ts | 1 + .../src/lib/auth/auth-token-verifier.test.ts | 4 +- lambdas/src/lib/auth/auth-token-verifier.ts | 1 + lambdas/src/lib/auth/auth-utils.ts | 29 ++-- lambdas/src/lib/commons.ts | 24 +-- lambdas/src/lib/db/db-client.test.ts | 47 +++--- lambdas/src/lib/db/db-client.ts | 15 +- lambdas/src/lib/db/rds-iam-auth.ts | 7 +- .../src/lib/db/test-result-db-client.test.ts | 29 +--- lambdas/src/lib/db/test-result-db-client.ts | 15 +- lambdas/src/lib/db/transaction-db-client.ts | 2 +- lambdas/src/lib/fhir-response.ts | 2 +- lambdas/src/lib/http/http-client.test.ts | 4 +- lambdas/src/lib/http/login-http-client.ts | 39 ++--- lambdas/src/lib/http/security-headers.ts | 4 +- lambdas/src/lib/login/nhs-login-client.ts | 3 +- .../lib/login/nhs-login-jwt-helper.test.ts | 4 +- lambdas/src/lib/login/nhs-login-jwt-helper.ts | 3 +- lambdas/src/lib/login/test-user-mapping.ts | 50 +++---- lambdas/src/lib/login/token-service.test.ts | 8 +- lambdas/src/lib/login/token-service.ts | 3 +- .../models/postcode-lookup-response.ts | 3 +- .../postcode-lookup-service.test.ts | 88 +++++------ .../postcode-lookup-service.ts | 2 +- .../lib/postcode-lookup/stub/stub-client.ts | 3 +- .../secrets/secrets-manager-client.test.ts | 12 +- .../component-integration-helpers.ts | 4 +- .../test-utils/environment-test-helpers.ts | 4 +- .../src/lib/utils/validation-utils.test.ts | 103 ++++++------- lambdas/src/lib/utils/validation-utils.ts | 4 +- .../validators/observation-validation.test.ts | 16 +- .../src/login-lambda/login-service.test.ts | 4 +- lambdas/src/login-lambda/login-service.ts | 3 +- lambdas/src/order-result-lambda/models.ts | 29 ++-- .../validation-service.test.ts | 13 +- .../order-result-lambda/validation-service.ts | 14 +- .../order-service-lambda/fhir-mapper.test.ts | 36 +---- .../src/order-service-lambda/fhir-mapper.ts | 9 +- .../src/order-service-lambda/index.test.ts | 3 +- .../order-service-request-schema.ts | 3 +- .../src/postcode-lookup-lambda/index.test.ts | 81 ++++++----- project.code-workspace | 6 +- scripts/config/markdownlint.yaml | 2 +- scripts/nhslogin/sign_jwt.js | 27 ++-- tests/api/clients/OrderApiResource.ts | 27 ++-- tests/api/clients/OrderStatusApiResource.ts | 5 +- tests/db/TestResultDbClient.ts | 2 +- tests/docs/guides/PullRequestGuidelines.md | 89 +++++++----- tests/eslint.config.mjs | 2 +- tests/fixtures/accessibilityFixture.ts | 5 +- tests/fixtures/apiFixture.ts | 1 + tests/fixtures/configurationFixture.ts | 7 +- tests/fixtures/pageObjectsFixture.ts | 30 ++-- tests/models/CreateOrderResponse.ts | 6 +- tests/models/TestResult.ts | 2 +- tests/page-objects/BasePage.ts | 3 +- tests/page-objects/BloodSampleGuidePage.ts | 1 + tests/page-objects/ConfirmMobileNumberPage.ts | 3 +- .../page-objects/EnterDeliveryAddressPage.ts | 3 +- tests/page-objects/GoToClinicPage.ts | 1 + .../KitNotAvailableInYourAreaPage.ts | 4 +- .../page-objects/NHSLogin/CodeSecurityPage.ts | 20 ++- .../NHSLogin/NHSEmailAndPasswordPage.ts | 1 + tests/page-objects/NhsLoginHelper.ts | 22 ++- tests/page-objects/OrderSubmittedPage.ts | 1 + tests/page-objects/PrivacyPolicyPage.ts | 1 + tests/page-objects/TermsOfUsePage.ts | 1 + tests/test-data/GetOrderRequestParams.ts | 2 +- tests/tests/ui/GoToClinicTest.spec.ts | 1 + tests/tests/ui/KitUnavailableTest.spec.ts | 1 + tests/tests/ui/ServiceNavigationTest.spec.ts | 5 +- tests/utils/AccessibilityModule.ts | 63 +++----- tests/utils/users/SandBoxUserManager.ts | 7 +- tests/utils/users/index.ts | 10 +- ui/README.md | 10 +- ui/content/ContentService.ts | 13 +- ui/content/README.md | 2 +- ui/content/content.json | 40 ++--- ui/content/hometest-privacy-policy.json | 12 +- ui/content/hometest-terms-of-use.json | 10 +- ui/content/index.ts | 10 +- ui/content/schema.ts | 3 +- ui/eslint.config.mjs | 5 +- ui/hooks/useContent.ts | 3 +- ui/hooks/useThrowError.ts | 2 +- ui/jest.config.ts | 37 +++-- .../components/AboutService.test.tsx | 2 +- .../components/ErrorRedirect.test.tsx | 4 +- .../MedicalAbbreviationsHelp.test.tsx | 4 +- .../MoreOptionsAndInformation.test.tsx | 5 +- .../SupplierLegalDocumentContent.test.tsx | 1 - .../order-status/OrderStatus.test.tsx | 4 +- .../order-status/OrderStatusHeader.test.tsx | 2 +- ui/src/__tests__/hooks/useThrowError.test.tsx | 5 +- ui/src/__tests__/layouts/PageLayout.test.tsx | 1 - .../services/order-details-service.test.ts | 137 ++++++++++-------- .../lib/services/order-service.test.ts | 16 +- .../lib/services/test-results-service.test.ts | 10 +- .../__tests__/lib/utils/AppDevtools.test.tsx | 2 +- .../lib/utils/JourneyDevtools.test.tsx | 36 +++-- ui/src/__tests__/lib/utils/debug.test.ts | 64 ++++++-- ui/src/__tests__/lib/utils/fhir-utils.test.ts | 25 +--- .../validation/mobileNumberValidation.test.ts | 82 +++-------- .../routes/ServiceErrorPage.test.tsx | 9 +- .../SuppliersPrivacyPolicyPage.test.tsx | 3 +- .../SuppliersTermsConditionsPage.test.tsx | 3 +- .../__tests__/routes/TestResultsPage.test.tsx | 15 +- .../ConfirmMobileNumberPage.test.tsx | 7 +- .../EnterDeliveryAddressPage.test.tsx | 7 +- .../EnterMobileNumberPage.test.tsx | 4 +- .../FormSuppliersPrivacyPolicyPage.test.tsx | 1 - .../FormSuppliersTermsConditionsPage.test.tsx | 1 - .../GetSelfTestKitPage.test.tsx | 5 +- .../GoToClinicPage.test.tsx | 3 +- .../HowComfortablePrickingFingerPage.test.tsx | 7 +- .../KitNotAvailableInAreaPage.test.tsx | 3 +- ui/src/app/[[...slug]]/page.tsx | 5 +- ui/src/components/ErrorRedirect.tsx | 1 + .../FindAnotherSexualHealthClinicLink.tsx | 1 + .../NearestSexualHealthClinicSection.tsx | 4 +- ui/src/layouts/JourneyLayout.tsx | 4 +- ui/src/layouts/MainLayout.tsx | 7 +- ui/src/lib/mappers/order-details-mapper.ts | 1 + ui/src/lib/queries/test-results-query.ts | 3 +- ui/src/lib/services/order-details-service.ts | 12 +- ui/src/lib/services/order-service.ts | 8 +- ui/src/lib/services/test-results-service.ts | 12 +- ui/src/lib/utils/AppDevtools.tsx | 2 + ui/src/lib/utils/JourneyDevtools.tsx | 2 + ui/src/lib/utils/debug.ts | 4 +- ui/src/lib/utils/fhir-utils.ts | 18 +-- ui/src/routes/CallbackPage.tsx | 10 +- ui/src/routes/ServiceErrorPage.tsx | 5 +- ui/src/routes/SuppliersPrivacyPolicyPage.tsx | 2 +- .../routes/SuppliersTermsConditionsPage.tsx | 2 +- ui/src/routes/TestResultsPage.tsx | 10 +- .../BloodSampleGuidePage.tsx | 2 +- .../FormSuppliersPrivacyPolicyPage.tsx | 5 +- .../FormSuppliersTermsConditionsPage.tsx | 5 +- .../GoToClinicPage.tsx | 9 +- .../HowComfortablePrickingFingerPage.tsx | 6 +- .../KitNotAvailableInAreaPage.tsx | 7 +- .../NoAddressFoundPage.tsx | 5 +- ui/src/state/index.ts | 2 +- 160 files changed, 970 insertions(+), 1099 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 40a69b2d9..e449e13cb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,5 @@ + ## Description diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md index be51601c5..073c2629b 100644 --- a/.github/instructions/tests.instructions.md +++ b/.github/instructions/tests.instructions.md @@ -97,7 +97,7 @@ class wraps one backend endpoint group (e.g. `OrderApiResource`, `HIVResultsApiR ### Writing API Tests ```typescript -import { test, expect } from "../../fixtures/IntegrationFixture"; +import { expect, test } from "../../fixtures/IntegrationFixture"; import { OrderTestData } from "../../test-data/OrderTestData"; import { headersOrder } from "../../utils/ApiRequestHelper"; @@ -145,6 +145,7 @@ All page interactions go through Page Object classes in `page-objects/`, each ex ```typescript import { Locator, Page } from "@playwright/test"; + import { ConfigFactory } from "../configuration/EnvironmentConfiguration"; import { BasePage } from "./BasePage"; @@ -200,9 +201,10 @@ page.locator(".nhsuk-button"); ### Writing Frontend Tests ```typescript -import { test } from "../../fixtures/CombinedTestFixture"; import { expect } from "@playwright/test"; +import { test } from "../../fixtures/CombinedTestFixture"; + test.describe("Order journey — delivery address", { tag: "@ui" }, () => { test.beforeEach(async ({ homeTestStartPage }) => { await homeTestStartPage.navigate(); @@ -237,12 +239,14 @@ directly in the test file and connected via `beforeAll`. ### Writing Integration Tests ```typescript +import { randomUUID } from "crypto"; + import { expect } from "@playwright/test"; -import { test } from "../../fixtures/CombinedTestFixture"; + import { TestOrderDbClient } from "../../db/TestOrderDbClient"; +import { test } from "../../fixtures/CombinedTestFixture"; import { OrderBuilder } from "../../test-data/OrderBuilder"; import { headersTestResults } from "../../utils/ApiRequestHelper"; -import { randomUUID } from "crypto"; const dbClient = new TestOrderDbClient(); @@ -264,7 +268,10 @@ test.describe("Results flow — order status update", { tag: "@integration" }, ( hivResultsApi, }) => { const correlationId = randomUUID(); - const response = await hivResultsApi.submitTestResults(testData, headersTestResults(correlationId)); + const response = await hivResultsApi.submitTestResults( + testData, + headersTestResults(correlationId), + ); expect(response.status()).toBe(201); expect(await dbClient.getLatestOrderStatusByOrderUid(orderId)).toEqual("COMPLETE"); }); diff --git a/.github/instructions/ui.instructions.md b/.github/instructions/ui.instructions.md index d98ab1d90..a5b8f7002 100644 --- a/.github/instructions/ui.instructions.md +++ b/.github/instructions/ui.instructions.md @@ -51,7 +51,7 @@ error summaries, back links, etc.). Do not create custom implementations of comp exist in the NHS component library. ```typescript -import { Button, Input, ErrorSummary, BackLink } from "nhsuk-react-components"; +import { BackLink, Button, ErrorSummary, Input } from "nhsuk-react-components"; ``` For custom layouts or spacing not covered by NHS components, use **Tailwind CSS utility @@ -62,12 +62,12 @@ classes**. Never use inline `style={{...}}` props. The application uses React Context for shared state. Providers are composed in layout components. The existing providers are: -| Provider | File | Purpose | -|---|---|---| +| Provider | File | Purpose | +| --------------------------- | -------- | ----------------------------------- | | `JourneyNavigationProvider` | `state/` | Multi-step journey navigation state | -| `CreateOrderProvider` | `state/` | Order creation form state | -| `PostcodeLookupProvider` | `state/` | Postcode lookup state | -| `AuthProvider` | `state/` | NHS Login authentication state | +| `CreateOrderProvider` | `state/` | Order creation form state | +| `PostcodeLookupProvider` | `state/` | Postcode lookup state | +| `AuthProvider` | `state/` | NHS Login authentication state | New providers should follow the same pattern: a context object, a typed interface, and a `use` hook that asserts the context is not null. @@ -131,6 +131,7 @@ When wrapping a service call in a React component, use **TanStack React Query** ```typescript import { useQuery } from "@tanstack/react-query"; + import orderDetailsService from "@/lib/services/order-details-service"; const { data, isLoading, error } = useQuery({ diff --git a/.markdownlint.yaml b/.markdownlint.yaml index 7be147512..2b21487e4 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -4,33 +4,32 @@ default: true # https://github.com/DavidAnson/markdownlint/blob/main/doc/md010.md -MD010: # no-hard-tabs - ignore_code_languages: - - make - - console +MD010: # no-hard-tabs + ignore_code_languages: + - make + - console # MD013 - Line length MD013: - line_length: 1000 - heading_line_length: 80 - code_block_line_length: 1200 - tables: false + line_length: 1000 + heading_line_length: 80 + code_block_line_length: 1200 + tables: false # MD024 - Multiple headings with the same content MD024: - siblings_only: true + siblings_only: true # MD033 - Inline HTML # https://github.com/DavidAnson/markdownlint/blob/main/doc/md033.md MD033: false - # MD041 - First line should be a top-level heading MD041: false # MD046 - Code block style MD046: - style: fenced + style: fenced # MD059 - Link text should be descriptive # https://github.com/DavidAnson/markdownlint/blob/main/doc/md059.md diff --git a/docs/developer-guides/Scripting_Docker.md b/docs/developer-guides/Scripting_Docker.md index df1b3183f..32855d2ef 100644 --- a/docs/developer-guides/Scripting_Docker.md +++ b/docs/developer-guides/Scripting_Docker.md @@ -68,9 +68,9 @@ Here are some key features built into this repository's Docker module: ### Quick start -The Repository Template assumes that you will want to build more than one docker image as part of your project. As such, we do not use a `Dockerfile` at the root of the project. Instead, each docker image that you create should go in its own folder under `infrastructure/images`. So, if your application has a docker image called `my-shiny-app`, you should create the file `infrastructure/images/my-shiny-app/Dockerfile`. Let's do that. +The Repository Template assumes that you will want to build more than one docker image as part of your project. As such, we do not use a `Dockerfile` at the root of the project. Instead, each docker image that you create should go in its own folder under `infrastructure/images`. So, if your application has a docker image called `my-shiny-app`, you should create the file `infrastructure/images/my-shiny-app/Dockerfile`. Let's do that. -First, we need an application to package. Let's do the simplest possible thing, and create a file called `main.py` in the root of the template with a familiar command in it: +First, we need an application to package. Let's do the simplest possible thing, and create a file called `main.py` in the root of the template with a familiar command in it: ```python print("hello world") @@ -92,9 +92,9 @@ COPY ./main.py . CMD ["python", "main.py"] ``` -Note the paths in the `COPY` command. The `Dockerfile` is stored in a subdirectory, but when `docker` runs it is executed in the root of the repository so that's where all paths are relative to. This is because you can't `COPY` from parent directories. `COPY ../../main.py .` wouldn't work. +Note the paths in the `COPY` command. The `Dockerfile` is stored in a subdirectory, but when `docker` runs it is executed in the root of the repository so that's where all paths are relative to. This is because you can't `COPY` from parent directories. `COPY ../../main.py .` wouldn't work. -The name of the folder is also significant. It should match the name of the docker image that you want to create. With that name, you can run the following `make` task to run `hadolint` over your `Dockerfile` to check for common anti-patterns: +The name of the folder is also significant. It should match the name of the docker image that you want to create. With that name, you can run the following `make` task to run `hadolint` over your `Dockerfile` to check for common anti-patterns: ```shell $ DOCKER_IMAGE=my-shiny-app make docker-lint @@ -105,7 +105,7 @@ make: *** [scripts/docker/docker.mk:20: docker-lint] Error 2 All the provided docker `make` tasks take the `DOCKER_IMAGE` parameter. -`hadolint` found a problem, so let's fix that. It's complaining that we've not specified which version of the `python` docker container we want. Change the first line of the `Dockerfile` to: +`hadolint` found a problem, so let's fix that. It's complaining that we've not specified which version of the `python` docker container we want. Change the first line of the `Dockerfile` to: ```dockerfile FROM python:3.12-slim-bookworm @@ -113,7 +113,7 @@ FROM python:3.12-slim-bookworm Run `DOCKER_IMAGE=my-shiny-app make docker-lint` again, and you will see that it is silent. -Now let's actually build the image. Run the following: +Now let's actually build the image. Run the following: ```shell DOCKER_IMAGE=my-shiny-app make docker-build @@ -136,7 +136,7 @@ docker.io/library/python 3.12-slim-bookworm d9f1825e4d49 5 weeks ago 13 localhost/hadolint/hadolint 2.12.0-alpine 19b38dcec411 16 months ago 8.3 MB ``` -Your process might want to add specific tag formats so you can identify docker images by date-stamps, or git hashes. The Repository Template supports that with a `VERSION` file. Create a new file called `infrastructure/images/my-shiny-app/VERSION`, and put the following into it: +Your process might want to add specific tag formats so you can identify docker images by date-stamps, or git hashes. The Repository Template supports that with a `VERSION` file. Create a new file called `infrastructure/images/my-shiny-app/VERSION`, and put the following into it: ```text ${yyyy}${mm}${dd}-${hash} @@ -148,9 +148,9 @@ Now, run the `docker-build` command again, and towards the end of the output you Successfully tagged localhost/my-shiny-app:20240314-07ee679 ``` -Obviously the specific values will be different for you. See the Versioning section below for more on this. +Obviously the specific values will be different for you. See the Versioning section below for more on this. -It is usually the case that there is a specific image that you will most often want to build, run, and deploy. You should edit the root-level `Makefile` to document this and to provide shortcuts. Edit `Makefile`, and change the `build` task to look like this: +It is usually the case that there is a specific image that you will most often want to build, run, and deploy. You should edit the root-level `Makefile` to document this and to provide shortcuts. Edit `Makefile`, and change the `build` task to look like this: ```make build: # Build the project artefact @Pipeline @@ -158,13 +158,13 @@ build: # Build the project artefact @Pipeline make docker-build ``` -Now when you run `make build`, it will do the right thing. Keeping this convention consistent across projects means that new starters can be on-boarded quickly, without needing to learn a new set of conventions each time. +Now when you run `make build`, it will do the right thing. Keeping this convention consistent across projects means that new starters can be on-boarded quickly, without needing to learn a new set of conventions each time. ### Your image implementation Always follow [Docker best practices](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) while developing images. -Here is a step-by-step guide for an image which packages a third-party tool. It is mostly similar to the example above, but demonstrates the `.tool-versions` mechanism. +Here is a step-by-step guide for an image which packages a third-party tool. It is mostly similar to the example above, but demonstrates the `.tool-versions` mechanism. 1. Create `infrastructure/images/cypress/Dockerfile` @@ -274,7 +274,7 @@ For cross-platform image support, the `--platform linux/amd64` flag is used to b ### `Dockerignore` file -If you need to exclude files from a `COPY` command, put a [`Dockerfile.dockerignore`](https://docs.docker.com/build/building/context/#filename-and-location) file next to the relevant `Dockerfile`. They do not live in the root directory. Any paths within `Dockerfile.dockerignore` must be relative to the repository root. +If you need to exclude files from a `COPY` command, put a [`Dockerfile.dockerignore`](https://docs.docker.com/build/building/context/#filename-and-location) file next to the relevant `Dockerfile`. They do not live in the root directory. Any paths within `Dockerfile.dockerignore` must be relative to the repository root. ## FAQ diff --git a/docs/developer-guides/Scripting_Terraform.md b/docs/developer-guides/Scripting_Terraform.md index 837288f63..1a1087f16 100644 --- a/docs/developer-guides/Scripting_Terraform.md +++ b/docs/developer-guides/Scripting_Terraform.md @@ -62,7 +62,7 @@ Here are some key features built into this repository's Terraform module: ### Quick start -The Repository Template assumes that you will be constructing the bulk of your infrastructure in `infrastructure/modules` as generic deployment configuration, which you will then compose into environment-specific modules, each stored in their own directory under `infrastructure/environments`. Let's create a simple deployable thing, and configure an S3 bucket. We'll make the name of the bucket a variable, so that each environment can have its own. +The Repository Template assumes that you will be constructing the bulk of your infrastructure in `infrastructure/modules` as generic deployment configuration, which you will then compose into environment-specific modules, each stored in their own directory under `infrastructure/environments`. Let's create a simple deployable thing, and configure an S3 bucket. We'll make the name of the bucket a variable, so that each environment can have its own. Open the file `infrastructure/modules/private_s3_bucket/main.tf`, and put this in it: @@ -82,9 +82,9 @@ resource "aws_s3_bucket" "my_bucket" { } ``` -Note that the variable has been given no value. This is intentional, and allows us to pass the bucket name in as a parameter from the environment. +Note that the variable has been given no value. This is intentional, and allows us to pass the bucket name in as a parameter from the environment. -Now, we're going to define two deployment environments: `dev`, and `test`. Run this: +Now, we're going to define two deployment environments: `dev`, and `test`. Run this: ```bash mkdir -p infrastructure/environments/{dev,test} @@ -92,7 +92,7 @@ mkdir -p infrastructure/environments/{dev,test} It is important that the directory names match your environment names. -Now, let's create the environment definition files. Open `infrastructure/environments/dev/main.tf` and copy in: +Now, let's create the environment definition files. Open `infrastructure/environments/dev/main.tf` and copy in: ```terraform module "dev_environment" { @@ -103,11 +103,11 @@ module "dev_environment" { Some things to note: -- The `source` path is relative to the directory that the `main.tf` file is in. When `terraform` runs, it will `chdir` to that directory first, before doing anything else. -- The `module` name, `"dev_environment"` here, can be anything. Module names are only scoped to the file they're in, so you don't need to follow any particular convention here. -- The `bucket_name` is going to end up as the bucket name in AWS. It wants to be meaningful to you, and you need to pick your own. The framework doesn't constrain your choice, but remember that AWS needs them to be globally unique and if you steal `"nhse-ee-my-fancy-bucket"` then I can't test these docs and then I will be sad. +- The `source` path is relative to the directory that the `main.tf` file is in. When `terraform` runs, it will `chdir` to that directory first, before doing anything else. +- The `module` name, `"dev_environment"` here, can be anything. Module names are only scoped to the file they're in, so you don't need to follow any particular convention here. +- The `bucket_name` is going to end up as the bucket name in AWS. It wants to be meaningful to you, and you need to pick your own. The framework doesn't constrain your choice, but remember that AWS needs them to be globally unique and if you steal `"nhse-ee-my-fancy-bucket"` then I can't test these docs and then I will be sad. -Let's create our `test` environment now. Open `infrastructure/environments/test/main.tf` and copy in: +Let's create our `test` environment now. Open `infrastructure/environments/test/main.tf` and copy in: ```terraform module "test_environment" { @@ -116,20 +116,20 @@ module "test_environment" { } ``` -We have changed the bucket name here. In this example, I am making no assumptions as to how your AWS accounts are set up. If you intend for your development and test infrastructure to be in the same AWS account (perhaps by necessity, for organisational reasons) and you need to separate them by a naming convention, the framework can support that. +We have changed the bucket name here. In this example, I am making no assumptions as to how your AWS accounts are set up. If you intend for your development and test infrastructure to be in the same AWS account (perhaps by necessity, for organisational reasons) and you need to separate them by a naming convention, the framework can support that. -Now we have our modules and our environments configured, we need to initialise each of them. Run these two commands: +Now we have our modules and our environments configured, we need to initialise each of them. Run these two commands: ```bash TF_ENV=dev make terraform-init TF_ENV=test make terraform-init ``` -Each invocation will download the `terraform` dependencies we need. The `TF_ENV` name we give to each invocation is the name of the environment, and must match the directory name we chose under `infrastructure/environments` so that `make` gives the right parameters to `terraform`. +Each invocation will download the `terraform` dependencies we need. The `TF_ENV` name we give to each invocation is the name of the environment, and must match the directory name we chose under `infrastructure/environments` so that `make` gives the right parameters to `terraform`. We are now ready to try deploying to AWS, from our local environment. -I am going to assume that you have an `~/.aws/credentials` file set up with a separate profile for each environment that you want to use, called `my-test-environment` and `my-dev-environment`. They might have the same credential values in them, in which case `terraform` will create the resources in the same account; or you might have them set up to deploy to different accounts. Either would work. +I am going to assume that you have an `~/.aws/credentials` file set up with a separate profile for each environment that you want to use, called `my-test-environment` and `my-dev-environment`. They might have the same credential values in them, in which case `terraform` will create the resources in the same account; or you might have them set up to deploy to different accounts. Either would work. Run the following: @@ -221,7 +221,7 @@ Apply complete! Resources: 1 added, 0 changed, 0 destroyed. ``` -You will notice here that I needed to confirm the action to `terraform` manually. If you don't want to do that, you can pass the `-auto-approve` option to `terraform` like this: +You will notice here that I needed to confirm the action to `terraform` manually. If you don't want to do that, you can pass the `-auto-approve` option to `terraform` like this: ```shell TF_ENV=dev AWS_PROFILE=my-dev-environment make terraform-apply opts="-auto-approve" diff --git a/docs/user-guides/Scan_dependencies.md b/docs/user-guides/Scan_dependencies.md index 4145897e5..ed92582e9 100644 --- a/docs/user-guides/Scan_dependencies.md +++ b/docs/user-guides/Scan_dependencies.md @@ -65,7 +65,6 @@ cat vulnerabilities-repository-reportc.json | jq 3. _Is it feasible to consolidate this functionality into a custom GitHub Action?_ Although consolidating this functionality into a custom GitHub Action seems like an optimal approach, this functionality also needs to run as a Git hook. Hence, shell scripting is a more suitable method as it makes less assumptions about local environment configuration or rely on third-party runners, providing quicker feedback. Additionally, incorporating this functionality directly into the repository has several advantages, including: - - Improved transparency and visibility of the implementation - Easier investigation of CVEs found in the repository, eliminating dependence on a third party like GitHub - Enhanced portability and flexibility, allowing the scans to run in diverse environments diff --git a/docs/user-guides/Sign_Git_commits.md b/docs/user-guides/Sign_Git_commits.md index 0ad07281c..3d4ce961f 100644 --- a/docs/user-guides/Sign_Git_commits.md +++ b/docs/user-guides/Sign_Git_commits.md @@ -25,6 +25,7 @@ There are two ways to sign commits in GitHub, using a GPG or an SSH signature. D ### Generate GPG key + If you do not have it already generate a new pair of GPG keys. Please change the passphrase (pleaseChooseYourKeyPassphrase) below and save it in your password manager. ```shell @@ -93,9 +94,9 @@ gpg --delete-keys $ID Use the [following commands](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key#telling-git-about-your-gpg-key) to set your default signing key in Git to the ID of the GPG key you generated. Replace `$ID` with your actual GPG key ID from the script above. - ```shell - git config --global user.signingkey $ID - ``` +```shell +git config --global user.signingkey $ID +``` Then enable automatic signing of Git commits by running: diff --git a/lambdas/eslint.config.mjs b/lambdas/eslint.config.mjs index d40e3f65b..4469d8633 100644 --- a/lambdas/eslint.config.mjs +++ b/lambdas/eslint.config.mjs @@ -1,5 +1,5 @@ -import { defineConfig } from "eslint/config"; import eslint from "@eslint/js"; +import { defineConfig } from "eslint/config"; import globals from "globals"; import tseslint from "typescript-eslint"; diff --git a/lambdas/scripts/build.ts b/lambdas/scripts/build.ts index 0e8ac5798..681e65aab 100644 --- a/lambdas/scripts/build.ts +++ b/lambdas/scripts/build.ts @@ -1,9 +1,9 @@ #!/usr/bin/env node import { existsSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "fs"; +import { join } from "path"; import { build } from "esbuild"; -import { join } from "path"; interface BuildOptions { specificLambda?: string; diff --git a/lambdas/scripts/package.ts b/lambdas/scripts/package.ts index 37b47be1f..2fb0f70d9 100644 --- a/lambdas/scripts/package.ts +++ b/lambdas/scripts/package.ts @@ -1,16 +1,17 @@ #!/usr/bin/env node -import {createWriteStream, existsSync, readdirSync, rmSync, statSync} from 'fs'; -import {join} from 'path'; -import archiver from 'archiver'; +import { createWriteStream, existsSync, readdirSync, rmSync, statSync } from "fs"; +import { join } from "path"; + +import archiver from "archiver"; interface PackageOptions { specificLambda?: string; } const LAMBDAS_DIR = process.cwd(); -const DIST_DIR = join(LAMBDAS_DIR, 'dist'); -const IS_PRODUCTION = process.env.NODE_ENV === 'production'; +const DIST_DIR = join(LAMBDAS_DIR, "dist"); +const IS_PRODUCTION = process.env.NODE_ENV === "production"; function parseArgs(): PackageOptions { const args = process.argv.slice(2); @@ -18,26 +19,24 @@ function parseArgs(): PackageOptions { let specificLambda: string | undefined; for (let i = 0; i < args.length; i++) { switch (args[i]) { - case '--lambda': + case "--lambda": specificLambda = args[++i]; if (!specificLambda) { - console.error('Error: --lambda requires a lambda name'); + console.error("Error: --lambda requires a lambda name"); process.exit(1); } break; default: console.error(`Unknown option: ${args[i]}`); - console.error('Usage: package.ts [--lambda ]'); + console.error("Usage: package.ts [--lambda ]"); process.exit(1); } - } - return {specificLambda}; - + return { specificLambda }; } -const LAMBDA_INDEX = 'index.js'; -const LAMBDA_INDEX_MAP = 'index.js.map'; +const LAMBDA_INDEX = "index.js"; +const LAMBDA_INDEX_MAP = "index.js.map"; async function createLambdaZip(lambda: string): Promise { console.log(`Creating deployment zip for ${lambda}...`); @@ -58,20 +57,20 @@ async function createLambdaZip(lambda: string): Promise { return new Promise((resolve, reject) => { const output = createWriteStream(zipPath); - const archive = archiver('zip', {zlib: {level: 9}}); + const archive = archiver("zip", { zlib: { level: 9 } }); - output.on('close', () => { + output.on("close", () => { console.log(`Created ${zipPath} (${archive.pointer()} bytes)`); resolve(); }); - archive.on('error', reject); + archive.on("error", reject); archive.pipe(output); - archive.file(indexPath, {name: LAMBDA_INDEX}); + archive.file(indexPath, { name: LAMBDA_INDEX }); if (!IS_PRODUCTION && existsSync(sourcemapPath)) { - archive.file(sourcemapPath, {name: LAMBDA_INDEX_MAP}); + archive.file(sourcemapPath, { name: LAMBDA_INDEX_MAP }); console.log(`Including sourcemap for debugging`); } @@ -81,8 +80,8 @@ async function createLambdaZip(lambda: string): Promise { function getLambdas(): string[] { return readdirSync(DIST_DIR) - .filter(name => name.endsWith('lambda')) - .filter(name => statSync(join(DIST_DIR, name)).isDirectory()); + .filter((name) => name.endsWith("lambda")) + .filter((name) => statSync(join(DIST_DIR, name)).isDirectory()); } async function main(): Promise { @@ -97,7 +96,7 @@ async function main(): Promise { const lambdas: string[] = options.specificLambda ? [options.specificLambda] : getLambdas(); if (lambdas.length === 0) { - console.log('No lambda directories found'); + console.log("No lambda directories found"); return; } @@ -105,12 +104,11 @@ async function main(): Promise { await createLambdaZip(lambdaDir); } - console.log(`Deployment packages ready in ${DIST_DIR}`); } catch (error) { - console.error('Packaging failed:', error); + console.error("Packaging failed:", error); process.exit(1); } } -await main() +await main(); diff --git a/lambdas/src/eligibility-lookup-lambda/la-lookup.test.ts b/lambdas/src/eligibility-lookup-lambda/la-lookup.test.ts index e8b8dc5e5..8877da2f2 100644 --- a/lambdas/src/eligibility-lookup-lambda/la-lookup.test.ts +++ b/lambdas/src/eligibility-lookup-lambda/la-lookup.test.ts @@ -1,11 +1,15 @@ import { LaLookupService } from "./la-lookup"; -jest.mock("../__mocks__/postcode-la-mapping.json", () => ({ - TN377PT: { localAuthorityCode: "1440", region: "East Sussex" }, - BN108QN: { localAuthorityCode: "1440", region: "East Sussex" }, - M503UQ: { localAuthorityCode: "4230", region: "Salford" }, - M275AW: { localAuthorityCode: "4230", region: "Salford" }, -}), { virtual: true }); +jest.mock( + "../__mocks__/postcode-la-mapping.json", + () => ({ + TN377PT: { localAuthorityCode: "1440", region: "East Sussex" }, + BN108QN: { localAuthorityCode: "1440", region: "East Sussex" }, + M503UQ: { localAuthorityCode: "4230", region: "Salford" }, + M275AW: { localAuthorityCode: "4230", region: "Salford" }, + }), + { virtual: true }, +); describe("LaLookupService", () => { let service: LaLookupService; diff --git a/lambdas/src/get-order-lambda/index.test.ts b/lambdas/src/get-order-lambda/index.test.ts index ea11e0979..561383890 100644 --- a/lambdas/src/get-order-lambda/index.test.ts +++ b/lambdas/src/get-order-lambda/index.test.ts @@ -206,9 +206,7 @@ describe("Get Order Lambda Handler", () => { code: "invalid", }); expect(responseBody.issue[0].diagnostics).toContain("order_id"); - expect(responseBody.issue[0].diagnostics).toContain( - "Invalid order id format", - ); + expect(responseBody.issue[0].diagnostics).toContain("Invalid order id format"); }); test("should return 400 when order_id is empty string", async () => { @@ -482,14 +480,11 @@ describe("Get Order Lambda Handler", () => { const businessStatusExtension = serviceRequest.extension.find( (ext: any) => - ext.url === - "https://fhir.hometest.nhs.uk/StructureDefinition/business-status", + ext.url === "https://fhir.hometest.nhs.uk/StructureDefinition/business-status", ); expect(businessStatusExtension).toBeDefined(); - expect( - businessStatusExtension.valueCodeableConcept.coding[0], - ).toMatchObject({ + expect(businessStatusExtension.valueCodeableConcept.coding[0]).toMatchObject({ system: "https://fhir.hometest.nhs.uk/CodeSystem/order-business-status", code: "DISPATCHED", display: "Order dispatched", diff --git a/lambdas/src/get-order-lambda/order-bundle-builder.ts b/lambdas/src/get-order-lambda/order-bundle-builder.ts index 241324938..d8156b093 100644 --- a/lambdas/src/get-order-lambda/order-bundle-builder.ts +++ b/lambdas/src/get-order-lambda/order-bundle-builder.ts @@ -72,8 +72,7 @@ export class OrderBundleBuilder { valueCodeableConcept: { coding: [ { - system: - "https://fhir.hometest.nhs.uk/CodeSystem/order-business-status", + system: "https://fhir.hometest.nhs.uk/CodeSystem/order-business-status", code: order.status_code, display: order.status_description, }, @@ -117,10 +116,7 @@ export class OrderBundleBuilder { }; } - private static buildBundleEntry( - order: Order, - serviceRequest: ServiceRequest, - ): BundleEntry { + private static buildBundleEntry(order: Order, serviceRequest: ServiceRequest): BundleEntry { return { fullUrl: `urn:uuid:${order.id}`, resource: serviceRequest, diff --git a/lambdas/src/hello-world-lambda/index.ts b/lambdas/src/hello-world-lambda/index.ts index 846bef75b..ed1016b1b 100644 --- a/lambdas/src/hello-world-lambda/index.ts +++ b/lambdas/src/hello-world-lambda/index.ts @@ -1,13 +1,11 @@ -import {APIGatewayProxyEvent, APIGatewayProxyResult} from "aws-lambda"; +import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; -export const handler = async ( - event: APIGatewayProxyEvent -): Promise => { - console.log(event) +export const handler = async (event: APIGatewayProxyEvent): Promise => { + console.log(event); return { statusCode: 200, body: JSON.stringify({ - message: 'Hello world!', - }) + message: "Hello world!", + }), }; }; diff --git a/lambdas/src/lib/auth/auth-token-service.test.ts b/lambdas/src/lib/auth/auth-token-service.test.ts index 9b08f5a79..d62b36fa8 100644 --- a/lambdas/src/lib/auth/auth-token-service.test.ts +++ b/lambdas/src/lib/auth/auth-token-service.test.ts @@ -1,3 +1,5 @@ +import { AuthTokenService } from "./auth-token-service"; + const mockSign = jest.fn(); const mockCleanupKey = jest.fn(); @@ -12,8 +14,6 @@ jest.mock("./auth-utils", () => ({ cleanupKey: mockCleanupKey, })); -import { AuthTokenService } from "./auth-token-service"; - describe("AuthTokenService", () => { const authConfig = { keyId: "test-key-id", diff --git a/lambdas/src/lib/auth/auth-token-service.ts b/lambdas/src/lib/auth/auth-token-service.ts index 633426fe8..b745fb7b7 100644 --- a/lambdas/src/lib/auth/auth-token-service.ts +++ b/lambdas/src/lib/auth/auth-token-service.ts @@ -1,4 +1,5 @@ import jwt, { type SignOptions } from "jsonwebtoken"; + import { type IAuthConfig } from "../models/auth/auth-config"; import { type AuthAccessTokenConfig, diff --git a/lambdas/src/lib/auth/auth-token-verifier.test.ts b/lambdas/src/lib/auth/auth-token-verifier.test.ts index b6c7709d4..7bef0ed34 100644 --- a/lambdas/src/lib/auth/auth-token-verifier.test.ts +++ b/lambdas/src/lib/auth/auth-token-verifier.test.ts @@ -1,3 +1,5 @@ +import { AuthTokenVerifier } from "./auth-token-verifier"; + const mockDecode = jest.fn(); const mockVerify = jest.fn(); const mockCleanupKey = jest.fn(); @@ -14,8 +16,6 @@ jest.mock("./auth-utils", () => ({ cleanupKey: mockCleanupKey, })); -import { AuthTokenVerifier } from "./auth-token-verifier"; - describe("AuthTokenVerifier", () => { const authConfig = { keyId: "default-key-id", diff --git a/lambdas/src/lib/auth/auth-token-verifier.ts b/lambdas/src/lib/auth/auth-token-verifier.ts index 8b167f7de..46de0a4d4 100644 --- a/lambdas/src/lib/auth/auth-token-verifier.ts +++ b/lambdas/src/lib/auth/auth-token-verifier.ts @@ -1,4 +1,5 @@ import jwt, { type JwtPayload, type VerifyOptions } from "jsonwebtoken"; + import { cleanupKey } from "./auth-utils"; // ALPHA: This file will need revisiting. diff --git a/lambdas/src/lib/auth/auth-utils.ts b/lambdas/src/lib/auth/auth-utils.ts index 9fdae41dc..28f5c19c9 100644 --- a/lambdas/src/lib/auth/auth-utils.ts +++ b/lambdas/src/lib/auth/auth-utils.ts @@ -1,35 +1,28 @@ -import cookieParser from 'cookie'; +import cookieParser from "cookie"; -function getCookieFromRequest( - cookie: string | undefined | null, - cookieName: string -): string { +function getCookieFromRequest(cookie: string | undefined | null, cookieName: string): string { if (cookie === null || cookie === undefined) { - return ''; + return ""; } const parsedCookie = cookieParser.parse(cookie); const targetCookie: string | undefined = parsedCookie[cookieName]; - return targetCookie ?? ''; + return targetCookie ?? ""; } -export function getAuthCookieFromRequest( - cookie: string | undefined | null -): string { - return getCookieFromRequest(cookie, 'auth'); +export function getAuthCookieFromRequest(cookie: string | undefined | null): string { + return getCookieFromRequest(cookie, "auth"); } -export function getAuthRefreshCookieFromRequest( - cookie: string | undefined | null -): string { - return getCookieFromRequest(cookie, 'auth_refresh'); +export function getAuthRefreshCookieFromRequest(cookie: string | undefined | null): string { + return getCookieFromRequest(cookie, "auth_refresh"); } export function cleanupKey(key: string): string | undefined { return key - .replace(/(-----BEGIN [A-Z ]+ KEY-----)/, '$1\n') // Ensure the beginning of the key has a newline - .replace(/(-----END [A-Z ]+ KEY-----)/, '\n$1') // Ensure the end of the key has a newline + .replace(/(-----BEGIN [A-Z ]+ KEY-----)/, "$1\n") // Ensure the beginning of the key has a newline + .replace(/(-----END [A-Z ]+ KEY-----)/, "\n$1") // Ensure the end of the key has a newline .match(/.{1,64}/g) // break into 64-character lines (PEM format spec) - ?.join('\n'); + ?.join("\n"); } diff --git a/lambdas/src/lib/commons.ts b/lambdas/src/lib/commons.ts index a987366f0..9e9d9ca48 100644 --- a/lambdas/src/lib/commons.ts +++ b/lambdas/src/lib/commons.ts @@ -1,17 +1,17 @@ export interface Commons { - logInfo(module: string, msg: string, details?: Record): void - logDebug(module: string, msg: string, details?: Record): void - logError(module: string, msg: string, details?: Record): void + logInfo(module: string, msg: string, details?: Record): void; + logDebug(module: string, msg: string, details?: Record): void; + logError(module: string, msg: string, details?: Record): void; } export class ConsoleCommons implements Commons { - logInfo(module: string, msg: string, details?: Record): void { - console.log(`[${new Date().toISOString()}][INFO][${module}] - ${msg}`, details) - } - logDebug(module: string, msg: string, details?: Record): void { - console.debug(`[${new Date().toISOString()}][INFO][${module}] - ${msg}`, details) - } - logError(module: string, msg: string, details?: Record): void { - console.error(`[${new Date().toISOString()}][INFO][${module}] - ${msg}`, details) - } + logInfo(module: string, msg: string, details?: Record): void { + console.log(`[${new Date().toISOString()}][INFO][${module}] - ${msg}`, details); + } + logDebug(module: string, msg: string, details?: Record): void { + console.debug(`[${new Date().toISOString()}][INFO][${module}] - ${msg}`, details); + } + logError(module: string, msg: string, details?: Record): void { + console.error(`[${new Date().toISOString()}][INFO][${module}] - ${msg}`, details); + } } diff --git a/lambdas/src/lib/db/db-client.test.ts b/lambdas/src/lib/db/db-client.test.ts index 9e9c11d13..c9636ca61 100644 --- a/lambdas/src/lib/db/db-client.test.ts +++ b/lambdas/src/lib/db/db-client.test.ts @@ -1,6 +1,7 @@ -import { PostgresDbClient } from "./db-client"; import { Pool } from "pg"; +import { PostgresDbClient } from "./db-client"; + jest.mock("pg", () => { const mPool = { query: jest.fn(), @@ -15,15 +16,13 @@ describe("PostgresDbClient", () => { let mockPool: jest.Mocked; beforeEach(() => { - client = new PostgresDbClient( - { - user: "test-user", - host: "test-host", - port: 5432, - database: "test-db", - password: "test-password", - } - ); + client = new PostgresDbClient({ + user: "test-user", + host: "test-host", + port: 5432, + database: "test-db", + password: "test-password", + }); mockPool = (client as any).pool; }); @@ -39,19 +38,13 @@ describe("PostgresDbClient", () => { }; (mockPool.query as jest.Mock).mockResolvedValue(mockResult); - const result = await client.query( - "SELECT * FROM users WHERE id = $1", - [1], - ); + const result = await client.query("SELECT * FROM users WHERE id = $1", [1]); expect(result).toEqual({ rows: [{ id: 1, name: "Test" }], rowCount: 1, }); - expect(mockPool.query).toHaveBeenCalledWith( - "SELECT * FROM users WHERE id = $1", - [1], - ); + expect(mockPool.query).toHaveBeenCalledWith("SELECT * FROM users WHERE id = $1", [1]); }); it("should handle queries with no values", async () => { @@ -64,10 +57,7 @@ describe("PostgresDbClient", () => { const result = await client.query("SELECT COUNT(*) FROM users"); expect(result.rows).toEqual([{ count: 5 }]); - expect(mockPool.query).toHaveBeenCalledWith( - "SELECT COUNT(*) FROM users", - undefined as any, - ); + expect(mockPool.query).toHaveBeenCalledWith("SELECT COUNT(*) FROM users", undefined as any); }); it("should handle empty result sets", async () => { @@ -77,10 +67,7 @@ describe("PostgresDbClient", () => { }; (mockPool.query as jest.Mock).mockResolvedValue(mockResult); - const result = await client.query( - "SELECT * FROM users WHERE id = $1", - [999], - ); + const result = await client.query("SELECT * FROM users WHERE id = $1", [999]); expect(result.rows).toEqual([]); expect(result.rowCount).toBe(0); @@ -90,9 +77,7 @@ describe("PostgresDbClient", () => { const error = new Error("Connection failed"); (mockPool.query as jest.Mock).mockRejectedValue(error); - await expect(client.query("SELECT * FROM users")).rejects.toThrow( - "Connection failed", - ); + await expect(client.query("SELECT * FROM users")).rejects.toThrow("Connection failed"); }); }); @@ -155,7 +140,9 @@ describe("PostgresDbClient", () => { ).rejects.toThrow("Query failed"); expect(mockClient.query).toHaveBeenNthCalledWith(1, "BEGIN"); - expect(mockClient.query).toHaveBeenNthCalledWith(2, "INSERT INTO users (name) VALUES ($1)", ["Test"]); + expect(mockClient.query).toHaveBeenNthCalledWith(2, "INSERT INTO users (name) VALUES ($1)", [ + "Test", + ]); expect(mockClient.query).toHaveBeenNthCalledWith(3, "ROLLBACK"); expect(mockClient.release).toHaveBeenCalledTimes(1); }); diff --git a/lambdas/src/lib/db/db-client.ts b/lambdas/src/lib/db/db-client.ts index e1db8635e..fd3c4e9ae 100644 --- a/lambdas/src/lib/db/db-client.ts +++ b/lambdas/src/lib/db/db-client.ts @@ -1,4 +1,4 @@ -import { Pool, ClientConfig } from "pg"; +import { ClientConfig, Pool } from "pg"; /** * A library-agnostic representation of a database result. @@ -9,10 +9,7 @@ export interface DbResult { } export interface DBClient { - query( - text: string, - values?: I, - ): Promise>; + query(text: string, values?: I): Promise>; withTransaction(fn: (client: DBClient) => Promise): Promise; close(): Promise; } @@ -36,10 +33,7 @@ export class PostgresDbClient implements DBClient { }); } - async query( - text: string, - values?: I, - ): Promise> { + async query(text: string, values?: I): Promise> { const result = await this.pool.query(text, values as any[]); return { rows: result.rows as T[], @@ -60,8 +54,7 @@ export class PostgresDbClient implements DBClient { rowCount: result.rowCount, }; }, - withTransaction: async (nestedFn: (client: DBClient) => Promise) => - nestedFn(txClient), + withTransaction: async (nestedFn: (client: DBClient) => Promise) => nestedFn(txClient), close: async () => undefined, }; diff --git a/lambdas/src/lib/db/rds-iam-auth.ts b/lambdas/src/lib/db/rds-iam-auth.ts index 4a52511f6..cea285698 100644 --- a/lambdas/src/lib/db/rds-iam-auth.ts +++ b/lambdas/src/lib/db/rds-iam-auth.ts @@ -15,12 +15,7 @@ export interface RdsIamAuthClient { export class AwsRdsIamAuthClient implements RdsIamAuthClient { private readonly signer: Signer; - constructor(options: { - hostname: string; - port: number; - username: string; - region: string; - }) { + constructor(options: { hostname: string; port: number; username: string; region: string }) { this.signer = new Signer(options); } diff --git a/lambdas/src/lib/db/test-result-db-client.test.ts b/lambdas/src/lib/db/test-result-db-client.test.ts index 0df4c500b..c1e7c28bf 100644 --- a/lambdas/src/lib/db/test-result-db-client.test.ts +++ b/lambdas/src/lib/db/test-result-db-client.test.ts @@ -1,5 +1,5 @@ -import { TestResultDbClient, type TestResult } from "./test-result-db-client"; import { type DBClient } from "./db-client"; +import { type TestResult, TestResultDbClient } from "./test-result-db-client"; describe("TestResultDbClient", () => { let testResultDbClient: TestResultDbClient; @@ -36,11 +36,7 @@ describe("TestResultDbClient", () => { rowCount: 1, }); - const result = await testResultDbClient.getResult( - orderId, - nhsNumber, - dateOfBirth, - ); + const result = await testResultDbClient.getResult(orderId, nhsNumber, dateOfBirth); expect(result).toEqual(mockTestResult); expect(mockDbClient.query).toHaveBeenCalledWith(expect.any(String), [ @@ -56,11 +52,7 @@ describe("TestResultDbClient", () => { rowCount: 0, }); - const result = await testResultDbClient.getResult( - orderId, - nhsNumber, - dateOfBirth, - ); + const result = await testResultDbClient.getResult(orderId, nhsNumber, dateOfBirth); expect(result).toBeNull(); expect(mockDbClient.query).toHaveBeenCalledWith(expect.any(String), [ @@ -82,11 +74,7 @@ describe("TestResultDbClient", () => { rowCount: 1, }); - const result = await testResultDbClient.getResult( - orderId, - nhsNumber, - dateOfBirth, - ); + const result = await testResultDbClient.getResult(orderId, nhsNumber, dateOfBirth); expect(result).toEqual(mockTestResult); expect(result?.status).toBe("RESULT_WITHHELD"); @@ -125,8 +113,7 @@ describe("TestResultDbClient", () => { const actualQuery = mockDbClient.query.mock.calls[0][0]; // Normalize whitespace for comparison - const normalizeQuery = (query: string) => - query.replace(/\s+/g, " ").trim(); + const normalizeQuery = (query: string) => query.replace(/\s+/g, " ").trim(); expect(normalizeQuery(actualQuery)).toBe(normalizeQuery(expectedQuery)); expect(mockDbClient.query).toHaveBeenCalledWith(expect.any(String), [ @@ -159,9 +146,9 @@ describe("TestResultDbClient", () => { const dbError = new Error("Database connection failed"); mockDbClient.query.mockRejectedValue(dbError); - await expect( - testResultDbClient.getResult(orderId, nhsNumber, dateOfBirth), - ).rejects.toThrow("Database connection failed"); + await expect(testResultDbClient.getResult(orderId, nhsNumber, dateOfBirth)).rejects.toThrow( + "Database connection failed", + ); }); }); }); diff --git a/lambdas/src/lib/db/test-result-db-client.ts b/lambdas/src/lib/db/test-result-db-client.ts index 0a5b4735b..f5135639a 100644 --- a/lambdas/src/lib/db/test-result-db-client.ts +++ b/lambdas/src/lib/db/test-result-db-client.ts @@ -11,11 +11,7 @@ export interface TestResult { export class TestResultDbClient { constructor(private readonly dbClient: DBClient) {} - public async getResult( - orderId: string, - nhsNumber: string, - dateOfBirth: Date, - ) { + public async getResult(orderId: string, nhsNumber: string, dateOfBirth: Date) { const query = ` SELECT rs.result_id AS id, @@ -39,10 +35,11 @@ export class TestResultDbClient { LIMIT 1; `; - const result = await this.dbClient.query< - TestResult, - [string, string, Date] - >(query, [orderId, nhsNumber, dateOfBirth]); + const result = await this.dbClient.query(query, [ + orderId, + nhsNumber, + dateOfBirth, + ]); return result?.rows[0] ?? null; } diff --git a/lambdas/src/lib/db/transaction-db-client.ts b/lambdas/src/lib/db/transaction-db-client.ts index 89f2b1a3c..d1fb17933 100644 --- a/lambdas/src/lib/db/transaction-db-client.ts +++ b/lambdas/src/lib/db/transaction-db-client.ts @@ -1,6 +1,6 @@ +import { ConsentService } from "./consent-db"; import { DBClient } from "./db-client"; import { OrderStatusCodes, OrderStatusService } from "./order-status-db"; -import { ConsentService } from "./consent-db"; export interface TransactionServiceProperties { dbClient: DBClient; diff --git a/lambdas/src/lib/fhir-response.ts b/lambdas/src/lib/fhir-response.ts index 57c571e29..adfc3af40 100644 --- a/lambdas/src/lib/fhir-response.ts +++ b/lambdas/src/lib/fhir-response.ts @@ -1,5 +1,5 @@ import { APIGatewayProxyResult } from "aws-lambda"; -import { Resource, OperationOutcome, OperationOutcomeIssue } from "fhir/r4"; +import { OperationOutcome, OperationOutcomeIssue, Resource } from "fhir/r4"; const FHIR_CONTENT_TYPE = "application/fhir+json"; diff --git a/lambdas/src/lib/http/http-client.test.ts b/lambdas/src/lib/http/http-client.test.ts index 2d5361b09..9e3bb9dd3 100644 --- a/lambdas/src/lib/http/http-client.test.ts +++ b/lambdas/src/lib/http/http-client.test.ts @@ -88,9 +88,7 @@ describe("FetchHttpClient", () => { text: async () => "bad gateway", }); - await expect( - client.postRaw("https://example.com", "payload"), - ).rejects.toEqual( + await expect(client.postRaw("https://example.com", "payload")).rejects.toEqual( expect.objectContaining({ name: "HttpError", message: "HTTP POST request failed with status: 502", diff --git a/lambdas/src/lib/http/login-http-client.ts b/lambdas/src/lib/http/login-http-client.ts index 972f3e748..78a7888e7 100644 --- a/lambdas/src/lib/http/login-http-client.ts +++ b/lambdas/src/lib/http/login-http-client.ts @@ -1,10 +1,11 @@ +import { Resolver } from "node:dns"; + import axios, { - type RawAxiosResponseHeaders, type AddressFamily, type LookupAddress, type RawAxiosRequestHeaders, + type RawAxiosResponseHeaders, } from "axios"; -import { Resolver } from "node:dns"; export interface HttpErrorDetails { httpCode?: number; @@ -33,10 +34,7 @@ export class HttpClient { // ALPHA: Removed commons use. To be reintroduced for logging later. constructor(options?: HttpClientOptions) { this.options = options; - if ( - options?.additionalDnsServers !== undefined && - options.additionalDnsServers.length > 0 - ) { + if (options?.additionalDnsServers !== undefined && options.additionalDnsServers.length > 0) { this.customLookup = this.getCustomLookup(options.additionalDnsServers); } } @@ -112,14 +110,10 @@ export class HttpClient { headers: RawAxiosRequestHeaders = {}, ): Promise { const defaultHeaders = this.getDefaultRequestHeaders(); - const response = await this.doPutRequestWithStatus( - endpointUrl, - body, - { - ...defaultHeaders, - ...headers, - }, - ); + const response = await this.doPutRequestWithStatus(endpointUrl, body, { + ...defaultHeaders, + ...headers, + }); return response.data; } @@ -161,10 +155,10 @@ export class HttpClient { ): Promise { try { const defaultHeaders = this.getDefaultRequestHeaders(); - const response = await this.doGetRequestWithHeaders( - endpointUrl, - { ...defaultHeaders, ...headers }, - ); + const response = await this.doGetRequestWithHeaders(endpointUrl, { + ...defaultHeaders, + ...headers, + }); return response.data; } catch (error: any) { @@ -234,10 +228,7 @@ export class HttpClient { }; } - private createErrorWithDetails( - apiMethod: string, - errorDetails: Record, - ): Error { + private createErrorWithDetails(apiMethod: string, errorDetails: Record): Error { return new Error(`${apiMethod} API call failure`, { cause: { details: errorDetails, @@ -246,9 +237,7 @@ export class HttpClient { } static isHttpError(error: any): boolean { - return ( - axios.isAxiosError(error) || error.cause?.details?.isHttpError === true - ); + return axios.isAxiosError(error) || error.cause?.details?.isHttpError === true; } static getHttpErrorDetails(error: any): HttpErrorDetails { diff --git a/lambdas/src/lib/http/security-headers.ts b/lambdas/src/lib/http/security-headers.ts index 45df4ac57..537261a35 100644 --- a/lambdas/src/lib/http/security-headers.ts +++ b/lambdas/src/lib/http/security-headers.ts @@ -2,7 +2,7 @@ export const securityHeaders = { strictTransportSecurity: { maxAge: 31536000, includeSubDomains: true, - preload: false + preload: false, }, - contentTypeOptions: { action: 'nosniff' } + contentTypeOptions: { action: "nosniff" }, }; diff --git a/lambdas/src/lib/login/nhs-login-client.ts b/lambdas/src/lib/login/nhs-login-client.ts index 812bfa203..bbff8c938 100644 --- a/lambdas/src/lib/login/nhs-login-client.ts +++ b/lambdas/src/lib/login/nhs-login-client.ts @@ -1,8 +1,9 @@ +import { type JwksClient } from "jwks-rsa"; + import { type HttpClient } from "../http/login-http-client"; import { type INhsLoginConfig } from "../models/nhs-login/nhs-login-config"; import { type INhsTokenResponseModel } from "../models/nhs-login/nhs-login-token-response-model"; import { type INhsUserInfoResponseModel } from "../models/nhs-login/nhs-login-user-info-response-model"; -import { type JwksClient } from "jwks-rsa"; import { type NhsLoginJwtHelper } from "./nhs-login-jwt-helper"; import { enrichUserInfoWithTestFirstName } from "./test-user-mapping"; diff --git a/lambdas/src/lib/login/nhs-login-jwt-helper.test.ts b/lambdas/src/lib/login/nhs-login-jwt-helper.test.ts index 0e9cbc2d8..8ca3aad9e 100644 --- a/lambdas/src/lib/login/nhs-login-jwt-helper.test.ts +++ b/lambdas/src/lib/login/nhs-login-jwt-helper.test.ts @@ -1,3 +1,5 @@ +import { NhsLoginJwtHelper } from "./nhs-login-jwt-helper"; + const mockSign = jest.fn(); const mockUuid = jest.fn(); @@ -12,8 +14,6 @@ jest.mock("uuid", () => ({ v4: mockUuid, })); -import { NhsLoginJwtHelper } from "./nhs-login-jwt-helper"; - describe("NhsLoginJwtHelper", () => { const nhsLoginConfig = { clientId: "client-id", diff --git a/lambdas/src/lib/login/nhs-login-jwt-helper.ts b/lambdas/src/lib/login/nhs-login-jwt-helper.ts index d951acd6d..427935934 100644 --- a/lambdas/src/lib/login/nhs-login-jwt-helper.ts +++ b/lambdas/src/lib/login/nhs-login-jwt-helper.ts @@ -1,7 +1,8 @@ import jwt, { type SignOptions } from "jsonwebtoken"; -import { type INhsLoginConfig } from "../models/nhs-login/nhs-login-config"; import { v4 as uuidv4 } from "uuid"; +import { type INhsLoginConfig } from "../models/nhs-login/nhs-login-config"; + export interface INhsLoginJwtHelper { createClientAuthJwt: () => string; } diff --git a/lambdas/src/lib/login/test-user-mapping.ts b/lambdas/src/lib/login/test-user-mapping.ts index ed7423a3d..2d472fa68 100644 --- a/lambdas/src/lib/login/test-user-mapping.ts +++ b/lambdas/src/lib/login/test-user-mapping.ts @@ -1,4 +1,4 @@ -import { type INhsUserInfoResponseModel } from '../models/nhs-login/nhs-login-user-info-response-model'; +import { type INhsUserInfoResponseModel } from "../models/nhs-login/nhs-login-user-info-response-model"; /** * Test user first names lookup (temporary workaround for missing scope in NHS Login) @@ -6,29 +6,29 @@ import { type INhsUserInfoResponseModel } from '../models/nhs-login/nhs-login-us * TODO: Remove this when the given_name (profile_extended) scope is available from NHS Login */ export const TEST_FIRST_NAMES: Record = { - MILLAR: 'Mona', - HUGHES: 'Iain', - MEAKIN: 'Mike', - LEACH: 'Kevin', - OLLEY: 'Arnold', - LEECH: 'Mina', - CORR: 'Lauren', - BRAY: 'Cassie Leona', - GRIGG: 'Kim', - KEWN: 'Emilie', - CURRIE: 'Amanda', - BEARDSLEY: 'TYRIQ', - 'BISSOON-LAL': 'MISBAAH', - RICK: 'JULIE', - WHONE: 'JOHAN', - 'POWELL-CID': 'Lee', - EDELSTEIN: 'GAVRIEL', - TABERT: 'ADELA', - 'BARKER-CID': 'Gail', - WRIGHT: 'GARTH', - ONIONS: 'JULIET', - PRYDE: 'Toni', - 'KELSO-CID': 'Huberto', + MILLAR: "Mona", + HUGHES: "Iain", + MEAKIN: "Mike", + LEACH: "Kevin", + OLLEY: "Arnold", + LEECH: "Mina", + CORR: "Lauren", + BRAY: "Cassie Leona", + GRIGG: "Kim", + KEWN: "Emilie", + CURRIE: "Amanda", + BEARDSLEY: "TYRIQ", + "BISSOON-LAL": "MISBAAH", + RICK: "JULIE", + WHONE: "JOHAN", + "POWELL-CID": "Lee", + EDELSTEIN: "GAVRIEL", + TABERT: "ADELA", + "BARKER-CID": "Gail", + WRIGHT: "GARTH", + ONIONS: "JULIET", + PRYDE: "Toni", + "KELSO-CID": "Huberto", }; /** @@ -39,7 +39,7 @@ export const TEST_FIRST_NAMES: Record = { * @returns Enriched user info with given_name populated if missing */ export function enrichUserInfoWithTestFirstName( - userInfo: INhsUserInfoResponseModel + userInfo: INhsUserInfoResponseModel, ): INhsUserInfoResponseModel { if (userInfo.given_name) { return userInfo; diff --git a/lambdas/src/lib/login/token-service.test.ts b/lambdas/src/lib/login/token-service.test.ts index e2cc5e16c..47104c5d1 100644 --- a/lambdas/src/lib/login/token-service.test.ts +++ b/lambdas/src/lib/login/token-service.test.ts @@ -1,3 +1,7 @@ +import { type INhsLoginConfig } from "../models/nhs-login/nhs-login-config"; +import { type INhsLoginClient } from "./nhs-login-client"; +import { TokenService } from "./token-service"; + const mockDecode = jest.fn(); const mockVerify = jest.fn(); @@ -9,10 +13,6 @@ jest.mock("jsonwebtoken", () => ({ }, })); -import { TokenService } from "./token-service"; -import { type INhsLoginClient } from "./nhs-login-client"; -import { type INhsLoginConfig } from "../models/nhs-login/nhs-login-config"; - describe("TokenService", () => { const nhsLoginConfig: INhsLoginConfig = { clientId: "client-id", diff --git a/lambdas/src/lib/login/token-service.ts b/lambdas/src/lib/login/token-service.ts index 73fce468c..8878b0035 100644 --- a/lambdas/src/lib/login/token-service.ts +++ b/lambdas/src/lib/login/token-service.ts @@ -1,6 +1,7 @@ import jwt, { type Jwt } from "jsonwebtoken"; -import { type INhsLoginClient } from "./nhs-login-client"; + import { type INhsLoginConfig } from "../models/nhs-login/nhs-login-config"; +import { type INhsLoginClient } from "./nhs-login-client"; export interface ITokenService { verifyToken: (encodedToken: string) => Promise; diff --git a/lambdas/src/lib/postcode-lookup/models/postcode-lookup-response.ts b/lambdas/src/lib/postcode-lookup/models/postcode-lookup-response.ts index 5df1460e2..8372c4de4 100644 --- a/lambdas/src/lib/postcode-lookup/models/postcode-lookup-response.ts +++ b/lambdas/src/lib/postcode-lookup/models/postcode-lookup-response.ts @@ -9,9 +9,8 @@ export interface Address { fullAddress: string; } - export interface PostcodeLookupResponse { postcode: string; addresses: Address[] | null; - status: 'found' | 'not_found' | 'error'; + status: "found" | "not_found" | "error"; } diff --git a/lambdas/src/lib/postcode-lookup/postcode-lookup-service.test.ts b/lambdas/src/lib/postcode-lookup/postcode-lookup-service.test.ts index 1d06f90dc..6bd182258 100644 --- a/lambdas/src/lib/postcode-lookup/postcode-lookup-service.test.ts +++ b/lambdas/src/lib/postcode-lookup/postcode-lookup-service.test.ts @@ -1,8 +1,8 @@ -import { PostcodeLookupService } from './postcode-lookup-service'; -import { PostcodeLookupClient } from './postcode-lookup-client-interface'; -import { PostcodeLookupResponse } from './models/postcode-lookup-response'; +import { PostcodeLookupResponse } from "./models/postcode-lookup-response"; +import { PostcodeLookupClient } from "./postcode-lookup-client-interface"; +import { PostcodeLookupService } from "./postcode-lookup-service"; -describe('PostcodeLookupService', () => { +describe("PostcodeLookupService", () => { let service: PostcodeLookupService; let mockClient: jest.Mocked; @@ -13,74 +13,78 @@ describe('PostcodeLookupService', () => { service = new PostcodeLookupService(mockClient); }); - describe('performLookup', () => { + describe("performLookup", () => { const mockResponse: PostcodeLookupResponse = { - postcode: 'SW1A 1AA', + postcode: "SW1A 1AA", addresses: [ { - id: '100062619632', - line1: '10 Downing Street', - line2: '', - line3: '', - fullAddress: '10 Downing Street, LONDON, SW1A 1AA', - town: 'LONDON', - postcode: 'SW1A 1AA', + id: "100062619632", + line1: "10 Downing Street", + line2: "", + line3: "", + fullAddress: "10 Downing Street, LONDON, SW1A 1AA", + town: "LONDON", + postcode: "SW1A 1AA", }, ], - status: 'found', + status: "found", }; - it('should successfully lookup a valid postcode', async () => { + it("should successfully lookup a valid postcode", async () => { mockClient.lookupPostcode.mockResolvedValue(mockResponse); - const result = await service.performLookup('SW1A 1AA'); + const result = await service.performLookup("SW1A 1AA"); expect(result).toEqual(mockResponse); - expect(mockClient.lookupPostcode).toHaveBeenCalledWith('SW1A 1AA'); + expect(mockClient.lookupPostcode).toHaveBeenCalledWith("SW1A 1AA"); }); - it('should normalize postcode before lookup', async () => { + it("should normalize postcode before lookup", async () => { mockClient.lookupPostcode.mockResolvedValue(mockResponse); - await service.performLookup('sw1a 1aa'); + await service.performLookup("sw1a 1aa"); - expect(mockClient.lookupPostcode).toHaveBeenCalledWith('SW1A 1AA'); + expect(mockClient.lookupPostcode).toHaveBeenCalledWith("SW1A 1AA"); }); - it('should throw error for invalid postcode format', async () => { - await expect(service.performLookup('INVALID')).rejects.toThrow('Invalid postcode format'); + it("should throw error for invalid postcode format", async () => { + await expect(service.performLookup("INVALID")).rejects.toThrow("Invalid postcode format"); expect(mockClient.lookupPostcode).not.toHaveBeenCalled(); }); - it('should throw error for empty postcode', async () => { - await expect(service.performLookup('')).rejects.toThrow('Invalid postcode format'); + it("should throw error for empty postcode", async () => { + await expect(service.performLookup("")).rejects.toThrow("Invalid postcode format"); expect(mockClient.lookupPostcode).not.toHaveBeenCalled(); }); - it('should throw error when client lookup fails', async () => { - mockClient.lookupPostcode.mockRejectedValue(new Error('API error')); + it("should throw error when client lookup fails", async () => { + mockClient.lookupPostcode.mockRejectedValue(new Error("API error")); - await expect(service.performLookup('SW1A 1AA')).rejects.toThrow('Failed to lookup postcode: API error'); + await expect(service.performLookup("SW1A 1AA")).rejects.toThrow( + "Failed to lookup postcode: API error", + ); }); - it('should handle non-Error exceptions from client', async () => { - mockClient.lookupPostcode.mockRejectedValue('String error'); + it("should handle non-Error exceptions from client", async () => { + mockClient.lookupPostcode.mockRejectedValue("String error"); - await expect(service.performLookup('SW1A 1AA')).rejects.toThrow('Failed to lookup postcode: Unknown error'); + await expect(service.performLookup("SW1A 1AA")).rejects.toThrow( + "Failed to lookup postcode: Unknown error", + ); }); - it('should accept postcode without space', async () => { + it("should accept postcode without space", async () => { mockClient.lookupPostcode.mockResolvedValue(mockResponse); - await service.performLookup('SW1A1AA'); + await service.performLookup("SW1A1AA"); - expect(mockClient.lookupPostcode).toHaveBeenCalledWith('SW1A1AA'); + expect(mockClient.lookupPostcode).toHaveBeenCalledWith("SW1A1AA"); }); - it('should accept various valid postcode formats', async () => { + it("should accept various valid postcode formats", async () => { mockClient.lookupPostcode.mockResolvedValue(mockResponse); - const validPostcodes = ['M1 1AA', 'B33 8TH', 'CR2 6XH', 'DN55 1PT', 'W1A 0AX', 'EC1A 1BB']; + const validPostcodes = ["M1 1AA", "B33 8TH", "CR2 6XH", "DN55 1PT", "W1A 0AX", "EC1A 1BB"]; for (const postcode of validPostcodes) { await service.performLookup(postcode); @@ -89,16 +93,18 @@ describe('PostcodeLookupService', () => { expect(mockClient.lookupPostcode).toHaveBeenCalledTimes(validPostcodes.length); }); - it('should reject postcode with invalid characters', async () => { - await expect(service.performLookup('SW1@ 1AA')).rejects.toThrow('Invalid postcode format'); + it("should reject postcode with invalid characters", async () => { + await expect(service.performLookup("SW1@ 1AA")).rejects.toThrow("Invalid postcode format"); }); - it('should reject postcode that is too short', async () => { - await expect(service.performLookup('M1')).rejects.toThrow('Invalid postcode format'); + it("should reject postcode that is too short", async () => { + await expect(service.performLookup("M1")).rejects.toThrow("Invalid postcode format"); }); - it('should reject postcode that is too long', async () => { - await expect(service.performLookup('SW1A 1AA 1BB')).rejects.toThrow('Invalid postcode format'); + it("should reject postcode that is too long", async () => { + await expect(service.performLookup("SW1A 1AA 1BB")).rejects.toThrow( + "Invalid postcode format", + ); }); }); }); diff --git a/lambdas/src/lib/postcode-lookup/postcode-lookup-service.ts b/lambdas/src/lib/postcode-lookup/postcode-lookup-service.ts index 4b5f59d9c..2fb4a7b73 100644 --- a/lambdas/src/lib/postcode-lookup/postcode-lookup-service.ts +++ b/lambdas/src/lib/postcode-lookup/postcode-lookup-service.ts @@ -1,5 +1,5 @@ -import { PostcodeLookupClient } from "./postcode-lookup-client-interface"; import { PostcodeLookupResponse } from "./models/postcode-lookup-response"; +import { PostcodeLookupClient } from "./postcode-lookup-client-interface"; export class PostcodeLookupService { private readonly client: PostcodeLookupClient; diff --git a/lambdas/src/lib/postcode-lookup/stub/stub-client.ts b/lambdas/src/lib/postcode-lookup/stub/stub-client.ts index b7ed4957d..8450cfec1 100644 --- a/lambdas/src/lib/postcode-lookup/stub/stub-client.ts +++ b/lambdas/src/lib/postcode-lookup/stub/stub-client.ts @@ -1,6 +1,7 @@ -import { PostcodeLookupClient } from "../postcode-lookup-client-interface"; import { PostcodeLookupClientConfig } from "src/lib/models/postcode-lookup-client-config"; + import { PostcodeLookupResponse } from "../models/postcode-lookup-response"; +import { PostcodeLookupClient } from "../postcode-lookup-client-interface"; /** * Stub implementation of PostcodeLookupClient for testing and development diff --git a/lambdas/src/lib/secrets/secrets-manager-client.test.ts b/lambdas/src/lib/secrets/secrets-manager-client.test.ts index 6c8f9d66f..27dbce176 100644 --- a/lambdas/src/lib/secrets/secrets-manager-client.test.ts +++ b/lambdas/src/lib/secrets/secrets-manager-client.test.ts @@ -1,3 +1,5 @@ +import { AwsSecretsClient } from "./secrets-manager-client"; + const mockSend = jest.fn(); jest.mock("@aws-sdk/client-secrets-manager", () => { @@ -9,8 +11,6 @@ jest.mock("@aws-sdk/client-secrets-manager", () => { }; }); -import { AwsSecretsClient } from "./secrets-manager-client"; - describe("AwsSecretsClient", () => { let client: AwsSecretsClient; @@ -30,17 +30,13 @@ describe("AwsSecretsClient", () => { const result = await client.getSecretString("my-secret"); expect(result).toBe("plain-secret"); - expect(mockSend).toHaveBeenCalledWith( - expect.objectContaining({ SecretId: "my-secret" }), - ); + expect(mockSend).toHaveBeenCalledWith(expect.objectContaining({ SecretId: "my-secret" })); }); it("should throw when secret string is empty", async () => { mockSend.mockResolvedValue({ SecretString: "" }); - await expect(client.getSecretString("my-secret")).rejects.toThrow( - "Secret string is empty", - ); + await expect(client.getSecretString("my-secret")).rejects.toThrow("Secret string is empty"); }); }); diff --git a/lambdas/src/lib/test-utils/component-integration-helpers.ts b/lambdas/src/lib/test-utils/component-integration-helpers.ts index 98cdba986..d56b67a28 100644 --- a/lambdas/src/lib/test-utils/component-integration-helpers.ts +++ b/lambdas/src/lib/test-utils/component-integration-helpers.ts @@ -23,9 +23,7 @@ export interface ComponentOrderTestConfig { * Test that components are created in the correct order * @param config - Configuration for the component order test */ -export function testComponentCreationOrder( - config: ComponentOrderTestConfig, -): void { +export function testComponentCreationOrder(config: ComponentOrderTestConfig): void { config.initFn(); config.components.forEach(({ mock, times = 1, calledWith }) => { diff --git a/lambdas/src/lib/test-utils/environment-test-helpers.ts b/lambdas/src/lib/test-utils/environment-test-helpers.ts index 3d3d83160..4ab3e43c1 100644 --- a/lambdas/src/lib/test-utils/environment-test-helpers.ts +++ b/lambdas/src/lib/test-utils/environment-test-helpers.ts @@ -7,9 +7,7 @@ * @param mockEnvVariables - Object containing environment variables to set * @returns The original environment for restoration */ -export function setupEnvironment( - mockEnvVariables: Record, -): NodeJS.ProcessEnv { +export function setupEnvironment(mockEnvVariables: Record): NodeJS.ProcessEnv { const originalEnv = process.env; process.env = { ...originalEnv }; Object.assign(process.env, mockEnvVariables); diff --git a/lambdas/src/lib/utils/validation-utils.test.ts b/lambdas/src/lib/utils/validation-utils.test.ts index de8cd996b..ab32a57d7 100644 --- a/lambdas/src/lib/utils/validation-utils.test.ts +++ b/lambdas/src/lib/utils/validation-utils.test.ts @@ -1,66 +1,67 @@ import { z } from "zod"; + import { generateReadableError } from "./validation-utils"; describe("generateReadableError", () => { - it("should return a readable error string for a ZodError with multiple issues", () => { - const schema = z.object({ - orderUID: z.string(), - code: z.string(), - arrayStuff: z.array(z.string()).min(1), - }); - - const result = schema.safeParse({ orderUID: 123, code: 456, arrayStuff: [2] }); - - expect(result.success).toBe(false); - if (result.error) { - // optional expects inside if statement because Sonarqube - // doesn't like us telling typescript that we know result.error is defined - const readable = generateReadableError(result.error); - expect(typeof readable).toBe("string"); - expect(readable.length).toBeGreaterThan(0); - expect(readable).toContain("orderUID:"); - expect(readable).toContain("code:"); - expect(readable).toContain("arrayStuff.0:"); - } + it("should return a readable error string for a ZodError with multiple issues", () => { + const schema = z.object({ + orderUID: z.string(), + code: z.string(), + arrayStuff: z.array(z.string()).min(1), }); - it("should handle empty ZodError gracefully", () => { - const emptyError = new z.ZodError([]); - const readable = generateReadableError(emptyError); - expect(typeof readable).toBe("string"); - expect(readable).toBe(""); - }); + const result = schema.safeParse({ orderUID: 123, code: 456, arrayStuff: [2] }); - it("should handle nested object errors", () => { - const schema = z.object({ - user: z.object({ - name: z.string(), - age: z.number(), - }), - }); + expect(result.success).toBe(false); + if (result.error) { + // optional expects inside if statement because Sonarqube + // doesn't like us telling typescript that we know result.error is defined + const readable = generateReadableError(result.error); + expect(typeof readable).toBe("string"); + expect(readable.length).toBeGreaterThan(0); + expect(readable).toContain("orderUID:"); + expect(readable).toContain("code:"); + expect(readable).toContain("arrayStuff.0:"); + } + }); - const result = schema.safeParse({ user: { name: 123, age: "not-a-number" } }); + it("should handle empty ZodError gracefully", () => { + const emptyError = new z.ZodError([]); + const readable = generateReadableError(emptyError); + expect(typeof readable).toBe("string"); + expect(readable).toBe(""); + }); - expect(result.success).toBe(false); - if (result.error) { - const readable = generateReadableError(result.error); - expect(readable).toContain("user.name:"); - expect(readable).toContain("user.age:"); - } + it("should handle nested object errors", () => { + const schema = z.object({ + user: z.object({ + name: z.string(), + age: z.number(), + }), }); - it("should handle array of objects errors", () => { - const schema = z.object({ - items: z.array(z.object({ id: z.string() })), - }); + const result = schema.safeParse({ user: { name: 123, age: "not-a-number" } }); - const result = schema.safeParse({ items: [{ id: 1 }, { id: 2 }] }); + expect(result.success).toBe(false); + if (result.error) { + const readable = generateReadableError(result.error); + expect(readable).toContain("user.name:"); + expect(readable).toContain("user.age:"); + } + }); - expect(result.success).toBe(false); - if (result.error) { - const readable = generateReadableError(result.error); - expect(readable).toContain("items.0.id:"); - expect(readable).toContain("items.1.id:"); - } + it("should handle array of objects errors", () => { + const schema = z.object({ + items: z.array(z.object({ id: z.string() })), }); + + const result = schema.safeParse({ items: [{ id: 1 }, { id: 2 }] }); + + expect(result.success).toBe(false); + if (result.error) { + const readable = generateReadableError(result.error); + expect(readable).toContain("items.0.id:"); + expect(readable).toContain("items.1.id:"); + } + }); }); diff --git a/lambdas/src/lib/utils/validation-utils.ts b/lambdas/src/lib/utils/validation-utils.ts index 45e31dc9e..ca1efb6d1 100644 --- a/lambdas/src/lib/utils/validation-utils.ts +++ b/lambdas/src/lib/utils/validation-utils.ts @@ -1,9 +1,9 @@ import { z } from "zod"; export const generateReadableError = (error: z.ZodError) => { - let errorMessage = ''; + let errorMessage = ""; error.issues.forEach((issue) => { - const path = issue.path.join('.'); + const path = issue.path.join("."); errorMessage += `${path}: ${issue.message}\n`; }); return errorMessage.trim(); diff --git a/lambdas/src/lib/validators/observation-validation.test.ts b/lambdas/src/lib/validators/observation-validation.test.ts index 4192750a3..cc2a36cc5 100644 --- a/lambdas/src/lib/validators/observation-validation.test.ts +++ b/lambdas/src/lib/validators/observation-validation.test.ts @@ -1,4 +1,5 @@ import { Observation } from "fhir/r4"; + import { ObservationValidation } from "./observation-validation"; describe("ObservationValidation", () => { @@ -12,8 +13,7 @@ describe("ObservationValidation", () => { { coding: [ { - system: - "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", + system: "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", code: "N", display: "Normal", }, @@ -34,8 +34,7 @@ describe("ObservationValidation", () => { { coding: [ { - system: - "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", + system: "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", code: "A", display: "Abnormal", }, @@ -90,8 +89,7 @@ describe("ObservationValidation", () => { display: "Other", }, { - system: - "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", + system: "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", code: "N", display: "Normal", }, @@ -121,8 +119,7 @@ describe("ObservationValidation", () => { { coding: [ { - system: - "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", + system: "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", code: "N", display: "Normal", }, @@ -143,8 +140,7 @@ describe("ObservationValidation", () => { { coding: [ { - system: - "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", + system: "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", code: "H", display: "High", }, diff --git a/lambdas/src/login-lambda/login-service.test.ts b/lambdas/src/login-lambda/login-service.test.ts index f12224009..539968d25 100644 --- a/lambdas/src/login-lambda/login-service.test.ts +++ b/lambdas/src/login-lambda/login-service.test.ts @@ -1,7 +1,7 @@ -import { LoginService } from "./login-service"; -import { type ITokenService } from "../lib/login/token-service"; import { type INhsLoginClient } from "../lib/login/nhs-login-client"; +import { type ITokenService } from "../lib/login/token-service"; import { type INhsUserInfoResponseModel } from "../lib/models/nhs-login/nhs-login-user-info-response-model"; +import { LoginService } from "./login-service"; function createUserInfo( overrides: Partial = {}, diff --git a/lambdas/src/login-lambda/login-service.ts b/lambdas/src/login-lambda/login-service.ts index 54fddac72..e420026d4 100644 --- a/lambdas/src/login-lambda/login-service.ts +++ b/lambdas/src/login-lambda/login-service.ts @@ -1,9 +1,10 @@ import { type JwtPayload } from "jsonwebtoken"; + +import { type LoginBody } from "."; import { type INhsLoginClient } from "../lib/login/nhs-login-client"; import { type ITokenService } from "../lib/login/token-service"; import { type INhsTokenResponseModel } from "../lib/models/nhs-login/nhs-login-token-response-model"; import { type INhsUserInfoResponseModel } from "../lib/models/nhs-login/nhs-login-user-info-response-model"; -import { type LoginBody } from "."; // ALPHA: This file will need revisiting. export interface ILoginService { diff --git a/lambdas/src/order-result-lambda/models.ts b/lambdas/src/order-result-lambda/models.ts index fef7bd3cc..499599bf0 100644 --- a/lambdas/src/order-result-lambda/models.ts +++ b/lambdas/src/order-result-lambda/models.ts @@ -1,10 +1,15 @@ -import { ResultStatus } from '../lib/types/status'; -import { FHIRCodeableConceptSchema, FHIRObservationSchema, FHIRReferenceSchema } from '../lib/models/fhir/fhir-schemas'; -import { z } from 'zod'; +import { z } from "zod"; + +import { + FHIRCodeableConceptSchema, + FHIRObservationSchema, + FHIRReferenceSchema, +} from "../lib/models/fhir/fhir-schemas"; +import { ResultStatus } from "../lib/types/status"; export enum InterpretationCode { - Normal = 'N', - Abnormal = 'A' + Normal = "N", + Abnormal = "A", } export const resultCodeMapping: { @@ -24,16 +29,18 @@ export interface Identifiers { // Apply business logic specific to order results on top of schema: // remove optionality for fields we require and only accept status of 'final' const orderResultInterpretationCodingSchema = FHIRCodeableConceptSchema.extend({ - coding: z.array( - z.object({ - code: z.enum(['N', 'A']), - }) - ).min(1), + coding: z + .array( + z.object({ + code: z.enum(["N", "A"]), + }), + ) + .min(1), }); export const orderResultFHIRObservationSchema = FHIRObservationSchema.extend({ basedOn: z.array(FHIRReferenceSchema), - status: z.literal('final'), + status: z.literal("final"), subject: FHIRReferenceSchema, performer: z.array(FHIRReferenceSchema), valueCodeableConcept: FHIRCodeableConceptSchema, diff --git a/lambdas/src/order-result-lambda/validation-service.test.ts b/lambdas/src/order-result-lambda/validation-service.test.ts index 2b31c41eb..30f46cfc7 100644 --- a/lambdas/src/order-result-lambda/validation-service.test.ts +++ b/lambdas/src/order-result-lambda/validation-service.test.ts @@ -1,13 +1,12 @@ -import * as utils from "../lib/utils/utils"; -import * as validation from "./validation-service"; -import * as validationUtils from "../lib/utils/validation-utils"; - -import { InterpretationCode, orderResultFHIRObservationSchema } from "./models"; - import { APIGatewayProxyEvent } from "aws-lambda"; -import { ConsoleCommons } from "../lib/commons"; import { Observation } from "fhir/r4"; + +import { ConsoleCommons } from "../lib/commons"; import { ResultStatus } from "../lib/types/status"; +import * as utils from "../lib/utils/utils"; +import * as validationUtils from "../lib/utils/validation-utils"; +import { InterpretationCode, orderResultFHIRObservationSchema } from "./models"; +import * as validation from "./validation-service"; describe("validation-service", () => { let commons: jest.Mocked; diff --git a/lambdas/src/order-result-lambda/validation-service.ts b/lambdas/src/order-result-lambda/validation-service.ts index e0b595978..a783cc56f 100644 --- a/lambdas/src/order-result-lambda/validation-service.ts +++ b/lambdas/src/order-result-lambda/validation-service.ts @@ -1,3 +1,10 @@ +import { APIGatewayProxyEvent } from "aws-lambda"; +import { Observation } from "fhir/r4"; + +import { ConsoleCommons } from "../lib/commons"; +import { OrderResultSummary } from "../lib/db/order-db"; +import { getCorrelationIdFromEventHeaders, isUUID } from "../lib/utils/utils"; +import { generateReadableError } from "../lib/utils/validation-utils"; import { Identifiers, InterpretationCode, @@ -5,13 +12,6 @@ import { resultCodeMapping, } from "./models"; import { ValidationResult, ValidationResultError, errorResult, successResult } from "./validation"; -import { getCorrelationIdFromEventHeaders, isUUID } from "../lib/utils/utils"; - -import { APIGatewayProxyEvent } from "aws-lambda"; -import { ConsoleCommons } from "../lib/commons"; -import { Observation } from "fhir/r4"; -import { OrderResultSummary } from "../lib/db/order-db"; -import { generateReadableError } from "../lib/utils/validation-utils"; function invalidErrorResult(errorMessage: string): ValidationResultError { return errorResult({ diff --git a/lambdas/src/order-service-lambda/fhir-mapper.test.ts b/lambdas/src/order-service-lambda/fhir-mapper.test.ts index 3b5170aa0..5ff5893b1 100644 --- a/lambdas/src/order-service-lambda/fhir-mapper.test.ts +++ b/lambdas/src/order-service-lambda/fhir-mapper.test.ts @@ -1,13 +1,10 @@ -import { mapTelecomToFhirContactPoints, buildFhirServiceRequest } from "./fhir-mapper"; +import { buildFhirServiceRequest, mapTelecomToFhirContactPoints } from "./fhir-mapper"; import type { OrderServiceRequest } from "./order-service-request-type"; describe("fhir-mapper", () => { describe("mapTelecomToFhirContactPoints", () => { it("should map phone and email to FHIR contact points", () => { - const telecom = [ - { phone: "01234567890" }, - { email: "test@example.com" }, - ]; + const telecom = [{ phone: "01234567890" }, { email: "test@example.com" }]; const result = mapTelecomToFhirContactPoints(telecom); @@ -18,10 +15,7 @@ describe("fhir-mapper", () => { }); it("should handle multiple contact types in one entry", () => { - const telecom = [ - { phone: "01234567890", fax: "09876543210" }, - { email: "test@example.com" }, - ]; + const telecom = [{ phone: "01234567890", fax: "09876543210" }, { email: "test@example.com" }]; const result = mapTelecomToFhirContactPoints(telecom); @@ -92,11 +86,7 @@ describe("fhir-mapper", () => { const patientUid = "patient-123"; const orderUid = "order-456"; - const result = buildFhirServiceRequest( - mockOrderRequest, - patientUid, - orderUid, - ); + const result = buildFhirServiceRequest(mockOrderRequest, patientUid, orderUid); expect(result).toEqual({ resourceType: "ServiceRequest", @@ -168,11 +158,7 @@ describe("fhir-mapper", () => { }, }; - const result = buildFhirServiceRequest( - minimalRequest, - "patient-123", - "order-456", - ); + const result = buildFhirServiceRequest(minimalRequest, "patient-123", "order-456"); expect(result.contained[0].address[0]).toEqual({ use: undefined, @@ -186,11 +172,7 @@ describe("fhir-mapper", () => { it("should correctly map patient reference", () => { const patientUid = "unique-patient-id"; - const result = buildFhirServiceRequest( - mockOrderRequest, - patientUid, - "order-456", - ); + const result = buildFhirServiceRequest(mockOrderRequest, patientUid, "order-456"); expect(result.subject.reference).toBe(`#${patientUid}`); expect(result.contained[0].id).toBe(patientUid); @@ -203,11 +185,7 @@ describe("fhir-mapper", () => { supplierId, }; - const result = buildFhirServiceRequest( - requestWithCustomSupplier, - "patient-123", - "order-456", - ); + const result = buildFhirServiceRequest(requestWithCustomSupplier, "patient-123", "order-456"); expect(result.performer?.[0].reference).toBe(supplierId); }); diff --git a/lambdas/src/order-service-lambda/fhir-mapper.ts b/lambdas/src/order-service-lambda/fhir-mapper.ts index 44cfcf74e..60bd1579a 100644 --- a/lambdas/src/order-service-lambda/fhir-mapper.ts +++ b/lambdas/src/order-service-lambda/fhir-mapper.ts @@ -2,18 +2,13 @@ import type { FHIRContactPoint, FHIRServiceRequest, } from "../lib/models/fhir/fhir-service-request-type"; -import type { - OrderServiceRequest, - OrderServiceTelecom, -} from "./order-service-request-type"; +import type { OrderServiceRequest, OrderServiceTelecom } from "./order-service-request-type"; export const mapTelecomToFhirContactPoints = ( telecom: OrderServiceTelecom[], ): FHIRContactPoint[] => { const result: FHIRContactPoint[] = []; - const mappings: Array< - [keyof OrderServiceTelecom, FHIRContactPoint["system"]] - > = [ + const mappings: Array<[keyof OrderServiceTelecom, FHIRContactPoint["system"]]> = [ ["phone", "phone"], ["fax", "fax"], ["email", "email"], diff --git a/lambdas/src/order-service-lambda/index.test.ts b/lambdas/src/order-service-lambda/index.test.ts index e916c7566..488130e14 100644 --- a/lambdas/src/order-service-lambda/index.test.ts +++ b/lambdas/src/order-service-lambda/index.test.ts @@ -1,6 +1,7 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; -import { OrderServiceRequestSchema } from "./order-service-request-schema"; + import { OrderStatusCodes } from "../lib/db/order-status-db"; +import { OrderServiceRequestSchema } from "./order-service-request-schema"; const mockInit = jest.fn(); const mockCreatePatientOrderAndConsent = jest.fn(); diff --git a/lambdas/src/order-service-lambda/order-service-request-schema.ts b/lambdas/src/order-service-lambda/order-service-request-schema.ts index 5a4cb29e6..aa474d3db 100644 --- a/lambdas/src/order-service-lambda/order-service-request-schema.ts +++ b/lambdas/src/order-service-lambda/order-service-request-schema.ts @@ -1,6 +1,7 @@ import { z } from "zod"; -import { isUUID } from "../lib/utils/utils"; + import { FHIRAddressSchema, FHIRHumanNameSchema } from "../lib/models/fhir/fhir-schemas"; +import { isUUID } from "../lib/utils/utils"; const TelecomItemSchema = z .object({ diff --git a/lambdas/src/postcode-lookup-lambda/index.test.ts b/lambdas/src/postcode-lookup-lambda/index.test.ts index b021b1794..5316915cd 100644 --- a/lambdas/src/postcode-lookup-lambda/index.test.ts +++ b/lambdas/src/postcode-lookup-lambda/index.test.ts @@ -1,19 +1,26 @@ -jest.mock('../lib/utils/utils'); -jest.mock('../lib/postcode-lookup/postcode-lookup-service'); -jest.mock('./init'); - -import { APIGatewayProxyEvent } from 'aws-lambda'; -import { lambdaHandler } from './index'; -import * as utils from '../lib/utils/utils'; -import { PostcodeLookupService } from '../lib/postcode-lookup/postcode-lookup-service'; -import { init } from './init'; - -const mockRetrieveMandatoryEnvVariable = utils.retrieveMandatoryEnvVariable as jest.MockedFunction; -const mockRetrieveOptionalEnvVariable = utils.retrieveOptionalEnvVariable as jest.MockedFunction; -const MockPostcodeLookupService = PostcodeLookupService as jest.MockedClass; +import { APIGatewayProxyEvent } from "aws-lambda"; + +import { PostcodeLookupService } from "../lib/postcode-lookup/postcode-lookup-service"; +import * as utils from "../lib/utils/utils"; +import { lambdaHandler } from "./index"; +import { init } from "./init"; + +jest.mock("../lib/utils/utils"); +jest.mock("../lib/postcode-lookup/postcode-lookup-service"); +jest.mock("./init"); + +const mockRetrieveMandatoryEnvVariable = utils.retrieveMandatoryEnvVariable as jest.MockedFunction< + typeof utils.retrieveMandatoryEnvVariable +>; +const mockRetrieveOptionalEnvVariable = utils.retrieveOptionalEnvVariable as jest.MockedFunction< + typeof utils.retrieveOptionalEnvVariable +>; +const MockPostcodeLookupService = PostcodeLookupService as jest.MockedClass< + typeof PostcodeLookupService +>; const mockInit = init as jest.MockedFunction; -describe('postcode-lookup-lambda handler', () => { +describe("postcode-lookup-lambda handler", () => { const mockPerformLookup = jest.fn(); const mockServiceInstance = { performLookup: mockPerformLookup, @@ -24,15 +31,15 @@ describe('postcode-lookup-lambda handler', () => { mockRetrieveMandatoryEnvVariable.mockImplementation((key: string) => { const mockEnvVars: Record = { - 'POSTCODE_LOOKUP_CREDENTIALS_SECRET_NAME': 'test-secret', - 'POSTCODE_LOOKUP_BASE_URL': 'https://test.example.com', + POSTCODE_LOOKUP_CREDENTIALS_SECRET_NAME: "test-secret", + POSTCODE_LOOKUP_BASE_URL: "https://test.example.com", }; - return mockEnvVars[key] || 'mock-value'; + return mockEnvVars[key] || "mock-value"; }); mockRetrieveOptionalEnvVariable.mockImplementation((key: string) => { const mockEnvVars: Record = { - 'USE_STUB_POSTCODE_CLIENT': 'true', + USE_STUB_POSTCODE_CLIENT: "true", }; return mockEnvVars[key]; }); @@ -42,7 +49,7 @@ describe('postcode-lookup-lambda handler', () => { }); }); - it('should return 400 when postcode query parameter is missing', async () => { + it("should return 400 when postcode query parameter is missing", async () => { const event = { queryStringParameters: {}, } as Partial as APIGatewayProxyEvent; @@ -50,11 +57,11 @@ describe('postcode-lookup-lambda handler', () => { const result = await lambdaHandler(event); expect(result.statusCode).toBe(400); - expect(result.body).toBe('Invalid request, missing parameters'); + expect(result.body).toBe("Invalid request, missing parameters"); expect(mockPerformLookup).not.toHaveBeenCalled(); }); - it('should return 400 when queryStringParameters is null', async () => { + it("should return 400 when queryStringParameters is null", async () => { const event = { queryStringParameters: null, } as Partial as APIGatewayProxyEvent; @@ -62,59 +69,59 @@ describe('postcode-lookup-lambda handler', () => { const result = await lambdaHandler(event); expect(result.statusCode).toBe(400); - expect(result.body).toBe('Invalid request, missing parameters'); + expect(result.body).toBe("Invalid request, missing parameters"); expect(mockPerformLookup).not.toHaveBeenCalled(); }); - it('should return 200 with lookup results when postcode is valid', async () => { + it("should return 200 with lookup results when postcode is valid", async () => { const mockResponse = { - postcode: 'SW1A 1AA', + postcode: "SW1A 1AA", addresses: [ { - line1: 'Prime Minister & First Lord Of The Treasury', - line2: '10 Downing Street', - town: 'London', - postcode: 'SW1A 1AA', + line1: "Prime Minister & First Lord Of The Treasury", + line2: "10 Downing Street", + town: "London", + postcode: "SW1A 1AA", }, ], - status: 'found', + status: "found", }; mockPerformLookup.mockResolvedValue(mockResponse); const event = { - queryStringParameters: { postcode: 'SW1A 1AA' }, + queryStringParameters: { postcode: "SW1A 1AA" }, } as Partial as APIGatewayProxyEvent; const result = await lambdaHandler(event); expect(result.statusCode).toBe(200); expect(JSON.parse(result.body)).toEqual(mockResponse); - expect(mockPerformLookup).toHaveBeenCalledWith('SW1A 1AA'); + expect(mockPerformLookup).toHaveBeenCalledWith("SW1A 1AA"); }); - it('should rethrow when service throws error', async () => { + it("should rethrow when service throws error", async () => { const mockError = { cause: { details: { - responseData: { error: 'Service unavailable' }, + responseData: { error: "Service unavailable" }, }, }, }; mockPerformLookup.mockRejectedValue(mockError); const event = { - queryStringParameters: { postcode: 'INVALID' }, + queryStringParameters: { postcode: "INVALID" }, } as Partial as APIGatewayProxyEvent; await expect(lambdaHandler(event)).rejects.toEqual(mockError); }); - it('should handle errors without cause details', async () => { - const mockError = new Error('Generic error'); + it("should handle errors without cause details", async () => { + const mockError = new Error("Generic error"); mockPerformLookup.mockRejectedValue(mockError); const event = { - queryStringParameters: { postcode: 'SW1A 1AA' }, + queryStringParameters: { postcode: "SW1A 1AA" }, } as Partial as APIGatewayProxyEvent; await expect(lambdaHandler(event)).rejects.toEqual(mockError); diff --git a/project.code-workspace b/project.code-workspace index fbcf948a5..302f2118e 100644 --- a/project.code-workspace +++ b/project.code-workspace @@ -2,7 +2,7 @@ "folders": [ { "name": "Repository Template", - "path": "." - } - ] + "path": ".", + }, + ], } diff --git a/scripts/config/markdownlint.yaml b/scripts/config/markdownlint.yaml index 0a15e0021..d6f5b7827 100644 --- a/scripts/config/markdownlint.yaml +++ b/scripts/config/markdownlint.yaml @@ -1,7 +1,7 @@ # SEE: https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.yaml --- # https://github.com/DavidAnson/markdownlint/blob/main/doc/md010.md -MD010: # no-hard-tabs +MD010: # no-hard-tabs ignore_code_languages: - make - console diff --git a/scripts/nhslogin/sign_jwt.js b/scripts/nhslogin/sign_jwt.js index 9644f5fc2..ef40c2df4 100644 --- a/scripts/nhslogin/sign_jwt.js +++ b/scripts/nhslogin/sign_jwt.js @@ -1,42 +1,41 @@ // This script will sign a JWT for use with NHS Login (Sandpit) // The script needs to placed in the same directory as the registered private_key and public_key // Run the script in terminal using the command node sign_jwt.js +import fs from "fs"; - -import fs from 'fs'; -import jwt from 'jsonwebtoken'; -import { v4 as uuidv4 } from 'uuid'; +import jwt from "jsonwebtoken"; +import { v4 as uuidv4 } from "uuid"; // Load private key -const privateKey = fs.readFileSync('./private_key.pem', 'utf-8'); +const privateKey = fs.readFileSync("./private_key.pem", "utf-8"); // Config -const clientId = 'hometest'; -const baseUri = 'https://auth.sandpit.signin.nhs.uk'; +const clientId = "hometest"; +const baseUri = "https://auth.sandpit.signin.nhs.uk"; const expiresIn = 300; // seconds (5 minutes) // JWT sign options const signOptions = { - algorithm: 'RS512', + algorithm: "RS512", subject: clientId, issuer: clientId, audience: `${baseUri}/token`, jwtid: uuidv4(), - expiresIn + expiresIn, }; try { - console.log('about to sign jwt token'); + console.log("about to sign jwt token"); const clientAssertion = jwt.sign( - {}, // empty payload + {}, // empty payload privateKey, - signOptions + signOptions, ); - console.log('jwt token successfully signed'); + console.log("jwt token successfully signed"); console.log(clientAssertion); } catch (error) { - console.error('could not sign jwt token', error); + console.error("could not sign jwt token", error); process.exit(1); } diff --git a/tests/api/clients/OrderApiResource.ts b/tests/api/clients/OrderApiResource.ts index 10a359afe..7646261db 100644 --- a/tests/api/clients/OrderApiResource.ts +++ b/tests/api/clients/OrderApiResource.ts @@ -1,26 +1,20 @@ -import { APIResponse, expect } from '@playwright/test'; -import { BaseApiClient } from './BaseApiClient'; -import { API_ENDPOINTS } from '../Endpoints'; -import { OrderPayload } from '../../test-data/OrderTestData'; -import type { ApiHeaders } from '../../utils'; -import { OrderStatusCode } from '../../models/TestOrder'; +import { APIResponse, expect } from "@playwright/test"; + +import { OrderStatusCode } from "../../models/TestOrder"; +import { OrderPayload } from "../../test-data/OrderTestData"; +import type { ApiHeaders } from "../../utils"; +import { API_ENDPOINTS } from "../Endpoints"; +import { BaseApiClient } from "./BaseApiClient"; export class OrderApiResource extends BaseApiClient { - async createOrder( - payload: OrderPayload, - headers: ApiHeaders, - ): Promise { + async createOrder(payload: OrderPayload, headers: ApiHeaders): Promise { return this.post(API_ENDPOINTS.order.create, { headers, data: payload, }); } - async getOrder( - nhsNumber: string, - dateOfBirth: string, - orderId: string, - ): Promise { + async getOrder(nhsNumber: string, dateOfBirth: string, orderId: string): Promise { return this.get(API_ENDPOINTS.order.get, { params: { nhs_number: nhsNumber, @@ -32,8 +26,7 @@ export class OrderApiResource extends BaseApiClient { extractOrderStatus(responseBody: Record): string { const body = responseBody as any; - return body.entry[0].resource.extension[0].valueCodeableConcept.coding[0] - .code; + return body.entry[0].resource.extension[0].valueCodeableConcept.coding[0].code; } async assertOrderHasStatus( diff --git a/tests/api/clients/OrderStatusApiResource.ts b/tests/api/clients/OrderStatusApiResource.ts index e640188d2..167b455b9 100644 --- a/tests/api/clients/OrderStatusApiResource.ts +++ b/tests/api/clients/OrderStatusApiResource.ts @@ -1,7 +1,8 @@ import { APIRequestContext, APIResponse } from "@playwright/test"; -import { BaseApiClient } from "./BaseApiClient"; -import { API_ENDPOINTS } from "../Endpoints"; + import { OrderStatusTaskPayload } from "../../test-data/OrderStatusTypes"; +import { API_ENDPOINTS } from "../Endpoints"; +import { BaseApiClient } from "./BaseApiClient"; export class OrderStatusApiResource extends BaseApiClient { constructor(request: APIRequestContext) { diff --git a/tests/db/TestResultDbClient.ts b/tests/db/TestResultDbClient.ts index c9a511fdb..d5c3e9425 100644 --- a/tests/db/TestResultDbClient.ts +++ b/tests/db/TestResultDbClient.ts @@ -1,6 +1,6 @@ -import { BaseDbClient } from "./BaseDbClient"; import { UUID } from "../models/TestOrder"; import { ResultStatus, TestResult } from "../models/TestResult"; +import { BaseDbClient } from "./BaseDbClient"; export class TestResultDbClient extends BaseDbClient { async insertStatusResult( diff --git a/tests/docs/guides/PullRequestGuidelines.md b/tests/docs/guides/PullRequestGuidelines.md index 759172d6c..42e90e1c6 100644 --- a/tests/docs/guides/PullRequestGuidelines.md +++ b/tests/docs/guides/PullRequestGuidelines.md @@ -368,11 +368,15 @@ Always remove unused code before submitting a PR: ❌ **Bad** - Unused imports and variables: ```typescript -import { Page, Locator, BrowserContext, expect } from '@playwright/test'; // BrowserContext unused -import { config } from '../configuration'; -import { OldPage } from './OldPage'; // Not used +import { BrowserContext, Locator, Page, expect } from "@playwright/test"; -const UNUSED_CONSTANT = 'test'; // Never used +// BrowserContext unused +import { config } from "../configuration"; +import { OldPage } from "./OldPage"; + +// Not used + +const UNUSED_CONSTANT = "test"; // Never used export class MyPage { readonly page: Page; @@ -385,8 +389,9 @@ export class MyPage { ✅ **Good** - Only what's needed: ```typescript -import { Page, Locator } from '@playwright/test'; -import { config } from '../configuration'; +import { Locator, Page } from "@playwright/test"; + +import { config } from "../configuration"; export class MyPage { readonly page: Page; @@ -404,8 +409,9 @@ Always declare locators as `readonly` properties and initialize them in the cons ✅ **Correct Pattern**: ```typescript -import { Page, Locator } from '@playwright/test'; -import { config, EnvironmentVariables } from '../configuration'; +import { Locator, Page } from "@playwright/test"; + +import { EnvironmentVariables, config } from "../configuration"; export class LoginPage { readonly page: Page; @@ -418,9 +424,9 @@ export class LoginPage { constructor(page: Page) { this.page = page; - this.usernameInput = page.getByRole('textbox', { name: 'Username' }); - this.passwordInput = page.getByRole('textbox', { name: 'Password' }); - this.loginButton = page.getByRole('button', { name: 'Log in' }); + this.usernameInput = page.getByRole("textbox", { name: "Username" }); + this.passwordInput = page.getByRole("textbox", { name: "Password" }); + this.loginButton = page.getByRole("button", { name: "Log in" }); this.errorMessage = page.locator('[data-testid="error-message"]'); } @@ -446,9 +452,9 @@ export class LoginPage { export class LoginPage { async login(username: string, password: string): Promise { // BAD: Inline locators are harder to maintain - await this.page.getByRole('textbox', { name: 'Username' }).fill(username); - await this.page.getByRole('textbox', { name: 'Password' }).fill(password); - await this.page.getByRole('button', { name: 'Log in' }).click(); + await this.page.getByRole("textbox", { name: "Username" }).fill(username); + await this.page.getByRole("textbox", { name: "Password" }).fill(password); + await this.page.getByRole("button", { name: "Log in" }).click(); } } ``` @@ -466,7 +472,7 @@ export class DashboardPage { constructor(page: Page) { this.page = page; - this.welcomeMessage = page.locator('h1'); + this.welcomeMessage = page.locator("h1"); } async verifyWelcomeMessage(expectedText: string): Promise { @@ -485,11 +491,11 @@ export class DashboardPage { constructor(page: Page) { this.page = page; - this.welcomeMessage = page.locator('h1'); + this.welcomeMessage = page.locator("h1"); } async getWelcomeMessage(): Promise { - return await this.welcomeMessage.textContent() || ''; + return (await this.welcomeMessage.textContent()) || ""; } // Or simply expose the locator for the test to assert @@ -506,28 +512,32 @@ All assertions should be in the test specification files, not in page objects. ✅ **Good Pattern**: ```typescript -import { test, expect } from '../fixtures'; - -test.describe('Login Flow', () => { - test('should successfully log in with valid credentials', async ({ page, loginPage, dashboardPage }) => { +import { expect, test } from "../fixtures"; + +test.describe("Login Flow", () => { + test("should successfully log in with valid credentials", async ({ + page, + loginPage, + dashboardPage, + }) => { // Navigate to login page await loginPage.navigate(); // Perform login - await loginPage.login('testuser', 'password123'); + await loginPage.login("testuser", "password123"); // ASSERTIONS IN TEST, NOT PAGE OBJECT - await expect(dashboardPage.welcomeMessage).toHaveText('Welcome, testuser'); + await expect(dashboardPage.welcomeMessage).toHaveText("Welcome, testuser"); await expect(page).toHaveURL(/.*dashboard/); }); - test('should show error with invalid credentials', async ({ loginPage }) => { + test("should show error with invalid credentials", async ({ loginPage }) => { await loginPage.navigate(); - await loginPage.login('invalid', 'wrong'); + await loginPage.login("invalid", "wrong"); // ASSERTION IN TEST await expect(loginPage.errorMessage).toBeVisible(); - await expect(loginPage.errorMessage).toHaveText('Invalid username or password'); + await expect(loginPage.errorMessage).toHaveText("Invalid username or password"); }); }); ``` @@ -541,13 +551,13 @@ Always import page objects, accessibility module, and configuration through fixt ✅ **Correct** - Using fixtures: ```typescript -import { test, expect } from '../fixtures'; +import { expect, test } from "../fixtures"; -test('homepage accessibility', async ({ page, homePage, accessibility }) => { +test("homepage accessibility", async ({ page, homePage, accessibility }) => { // homePage, accessibility are provided by fixtures await homePage.navigate(); - const hasViolations = await accessibility.runAccessibilityCheck(page, 'homepage'); + const hasViolations = await accessibility.runAccessibilityCheck(page, "homepage"); expect(hasViolations).toBe(false); }); ``` @@ -555,11 +565,15 @@ test('homepage accessibility', async ({ page, homePage, accessibility }) => { ❌ **Incorrect** - Direct imports: ```typescript -import { test, expect } from '@playwright/test'; -import { HomePage } from '../page-objects/HomePage'; // Don't do this -import { AccessibilityModule } from '../utils'; // Don't do this +import { expect, test } from "@playwright/test"; + +import { HomePage } from "../page-objects/HomePage"; +// Don't do this +import { AccessibilityModule } from "../utils"; -test('homepage test', async ({ page }) => { +// Don't do this + +test("homepage test", async ({ page }) => { const homePage = new HomePage(page); // Don't instantiate manually // ... }); @@ -571,9 +585,10 @@ Reference the fixtures file to understand what's available: ```typescript // tests/fixtures/index.ts -import { test as base } from '@playwright/test'; -import { PlaywrightDevPage, WPHomePage, HomeStartTestPage } from '../page-objects'; -import { AccessibilityModule } from '../utils'; +import { test as base } from "@playwright/test"; + +import { HomeStartTestPage, PlaywrightDevPage, WPHomePage } from "../page-objects"; +import { AccessibilityModule } from "../utils"; type MyFixtures = { playwrightDevPage: PlaywrightDevPage; @@ -597,7 +612,7 @@ export const test = base.extend({ }, }); -export { expect } from '@playwright/test'; +export { expect } from "@playwright/test"; ``` ## Summary Checklist diff --git a/tests/eslint.config.mjs b/tests/eslint.config.mjs index 357b41946..58342155b 100644 --- a/tests/eslint.config.mjs +++ b/tests/eslint.config.mjs @@ -1,5 +1,5 @@ -import { defineConfig } from "eslint/config"; import eslint from "@eslint/js"; +import { defineConfig } from "eslint/config"; import globals from "globals"; import tseslint from "typescript-eslint"; diff --git a/tests/fixtures/accessibilityFixture.ts b/tests/fixtures/accessibilityFixture.ts index eb7d51ca8..17d12bce4 100644 --- a/tests/fixtures/accessibilityFixture.ts +++ b/tests/fixtures/accessibilityFixture.ts @@ -1,5 +1,6 @@ -import { test as base } from '@playwright/test'; -import { AccessibilityModule, accessibilityModule } from '../utils'; +import { test as base } from "@playwright/test"; + +import { AccessibilityModule, accessibilityModule } from "../utils"; type AccessibilityFixtures = { accessibility: AccessibilityModule; diff --git a/tests/fixtures/apiFixture.ts b/tests/fixtures/apiFixture.ts index 32ef73349..62d3bf8eb 100644 --- a/tests/fixtures/apiFixture.ts +++ b/tests/fixtures/apiFixture.ts @@ -1,4 +1,5 @@ import { test as base } from "@playwright/test"; + import { HIVResultsApiResource } from "../api/clients/HIVResultsApiResource"; import { OrderApiResource } from "../api/clients/OrderApiResource"; import { OrderStatusApiResource } from "../api/clients/OrderStatusApiResource"; diff --git a/tests/fixtures/configurationFixture.ts b/tests/fixtures/configurationFixture.ts index 8ae40a99d..25e992bc9 100644 --- a/tests/fixtures/configurationFixture.ts +++ b/tests/fixtures/configurationFixture.ts @@ -1,5 +1,6 @@ -import { test as base } from '@playwright/test'; -import { ConfigFactory, type ConfigInterface } from '../configuration/EnvironmentConfiguration'; +import { test as base } from "@playwright/test"; + +import { ConfigFactory, type ConfigInterface } from "../configuration/EnvironmentConfiguration"; type ConfigurationFixtures = { config: ConfigInterface; @@ -8,5 +9,5 @@ type ConfigurationFixtures = { export const configurationFixture = base.extend({ config: async ({}, use) => { await use(ConfigFactory.getConfig()); - } + }, }); diff --git a/tests/fixtures/pageObjectsFixture.ts b/tests/fixtures/pageObjectsFixture.ts index 553610f1a..17f13452d 100644 --- a/tests/fixtures/pageObjectsFixture.ts +++ b/tests/fixtures/pageObjectsFixture.ts @@ -1,27 +1,27 @@ import { test as base } from "@playwright/test"; -// Page Objects -import { HomeTestStartPage } from "../page-objects/HomeTestStartPage"; -import { EnterDeliveryAddressPage } from "../page-objects/EnterDeliveryAddressPage"; import { BloodSampleGuidePage } from "../page-objects/BloodSampleGuidePage"; +import { CannotUseServiceUnder18Page } from "../page-objects/CannotUseServiceUnder18Page"; import { CheckYourAnswersPage } from "../page-objects/CheckYourAnswersPage"; -import { EnterAddressManuallyPage } from "../page-objects/EnterAddressManuallyPage"; -import { SelectDeliveryAddressPage } from "../page-objects/SelectDeliveryAddressPage"; -import { OrderStatusPage } from "../page-objects/OrderStatusPage"; -import { HowComfortablePrickingFingerPage } from "../page-objects/HowComfortablePrickingFingerPage"; -import { PrivacyPolicyPage } from "../page-objects/PrivacyPolicyPage"; import { ConfirmMobileNumberPage } from "../page-objects/ConfirmMobileNumberPage"; -import { NegativeResultPage } from "../page-objects/NegativeResultPage"; -import { NHSEmailAndPasswordPage } from "../page-objects/NHSLogin/NHSEmailAndPasswordPage"; +import { EnterAddressManuallyPage } from "../page-objects/EnterAddressManuallyPage"; +import { EnterDeliveryAddressPage } from "../page-objects/EnterDeliveryAddressPage"; import { ErrorPage } from "../page-objects/ErrorPage"; +import { GoToClinicPage } from "../page-objects/GoToClinicPage"; +// Page Objects +import { HomeTestStartPage } from "../page-objects/HomeTestStartPage"; +import { HowComfortablePrickingFingerPage } from "../page-objects/HowComfortablePrickingFingerPage"; import { KitNotAvailableInYourAreaPage } from "../page-objects/KitNotAvailableInYourAreaPage"; -import { OrderSubmittedPage } from "../page-objects/OrderSubmittedPage"; -import { TermsOfUsePage } from "../page-objects/TermsOfUsePage"; import { CodeSecurityPage } from "../page-objects/NHSLogin/CodeSecurityPage"; -import { SuppliersTermsOfUsePage } from "../page-objects/SuppliersTermsOfUsePage"; -import { CannotUseServiceUnder18Page } from "../page-objects/CannotUseServiceUnder18Page"; -import { GoToClinicPage } from "../page-objects/GoToClinicPage"; +import { NHSEmailAndPasswordPage } from "../page-objects/NHSLogin/NHSEmailAndPasswordPage"; +import { NegativeResultPage } from "../page-objects/NegativeResultPage"; +import { OrderStatusPage } from "../page-objects/OrderStatusPage"; +import { OrderSubmittedPage } from "../page-objects/OrderSubmittedPage"; +import { PrivacyPolicyPage } from "../page-objects/PrivacyPolicyPage"; +import { SelectDeliveryAddressPage } from "../page-objects/SelectDeliveryAddressPage"; import { SuppliersPrivacyPolicyPage } from "../page-objects/SuppliersPrivacyPolicyPage"; +import { SuppliersTermsOfUsePage } from "../page-objects/SuppliersTermsOfUsePage"; +import { TermsOfUsePage } from "../page-objects/TermsOfUsePage"; export interface MyFixtures { homeTestStartPage: HomeTestStartPage; diff --git a/tests/models/CreateOrderResponse.ts b/tests/models/CreateOrderResponse.ts index 4a6fd073a..683ea6eee 100644 --- a/tests/models/CreateOrderResponse.ts +++ b/tests/models/CreateOrderResponse.ts @@ -18,10 +18,6 @@ export class CreateOrderResponseModel { } isValidResponse(): boolean { - return ( - !!this.orderUid && - !!this.orderReference && - !!this.message - ); + return !!this.orderUid && !!this.orderReference && !!this.message; } } diff --git a/tests/models/TestResult.ts b/tests/models/TestResult.ts index 62e019d3c..d61701e61 100644 --- a/tests/models/TestResult.ts +++ b/tests/models/TestResult.ts @@ -1,4 +1,4 @@ -export type ResultStatus = 'RESULT_AVAILABLE' | 'RESULT_WITHHELD'; +export type ResultStatus = "RESULT_AVAILABLE" | "RESULT_WITHHELD"; export interface TestResult { order_uid: string; diff --git a/tests/page-objects/BasePage.ts b/tests/page-objects/BasePage.ts index a29066b4b..18ca92e25 100644 --- a/tests/page-objects/BasePage.ts +++ b/tests/page-objects/BasePage.ts @@ -1,4 +1,5 @@ import { Locator, Page } from "@playwright/test"; + export abstract class BasePage { readonly page: Page; readonly headerText: Locator; @@ -6,7 +7,7 @@ export abstract class BasePage { constructor(page: Page) { this.headerText = page.locator("h1"); - this.backLink = page.getByText('Back', { exact: true }); + this.backLink = page.getByText("Back", { exact: true }); this.page = page; } diff --git a/tests/page-objects/BloodSampleGuidePage.ts b/tests/page-objects/BloodSampleGuidePage.ts index 869108890..19f574621 100644 --- a/tests/page-objects/BloodSampleGuidePage.ts +++ b/tests/page-objects/BloodSampleGuidePage.ts @@ -1,4 +1,5 @@ import { Locator, Page } from "@playwright/test"; + import { BasePage } from "./BasePage"; export class BloodSampleGuidePage extends BasePage { diff --git a/tests/page-objects/ConfirmMobileNumberPage.ts b/tests/page-objects/ConfirmMobileNumberPage.ts index 7f1f27227..62379b2cd 100644 --- a/tests/page-objects/ConfirmMobileNumberPage.ts +++ b/tests/page-objects/ConfirmMobileNumberPage.ts @@ -1,6 +1,7 @@ import { Locator, Page } from "@playwright/test"; -import { BasePage } from "./BasePage"; + import { PersonalDetailsModel } from "../models/PersonalDetails"; +import { BasePage } from "./BasePage"; export class ConfirmMobileNumberPage extends BasePage { readonly mobileNumberInput: Locator; diff --git a/tests/page-objects/EnterDeliveryAddressPage.ts b/tests/page-objects/EnterDeliveryAddressPage.ts index 5a4480b58..c955b6479 100644 --- a/tests/page-objects/EnterDeliveryAddressPage.ts +++ b/tests/page-objects/EnterDeliveryAddressPage.ts @@ -1,6 +1,7 @@ import { Locator, Page } from "@playwright/test"; -import { BasePage } from "./BasePage"; + import { Address } from "../models/Address"; +import { BasePage } from "./BasePage"; export class EnterDeliveryAddressPage extends BasePage { readonly postCodeInput: Locator; diff --git a/tests/page-objects/GoToClinicPage.ts b/tests/page-objects/GoToClinicPage.ts index 936a59b6d..b416f568b 100644 --- a/tests/page-objects/GoToClinicPage.ts +++ b/tests/page-objects/GoToClinicPage.ts @@ -1,4 +1,5 @@ import { Locator, Page } from "@playwright/test"; + import { BasePage } from "./BasePage"; export class GoToClinicPage extends BasePage { diff --git a/tests/page-objects/KitNotAvailableInYourAreaPage.ts b/tests/page-objects/KitNotAvailableInYourAreaPage.ts index db81fc2df..c8cdf92bf 100644 --- a/tests/page-objects/KitNotAvailableInYourAreaPage.ts +++ b/tests/page-objects/KitNotAvailableInYourAreaPage.ts @@ -11,7 +11,9 @@ export class KitNotAvailableInYourAreaPage extends BasePage { this.findAnotherSexualHealthClinicLink = page.getByRole("link", { name: "Find another sexual health clinic", }); - this.pageHeader = page.locator("h1", { hasText: "Free HIV self-test kits are not available in your area using this service" }); + this.pageHeader = page.locator("h1", { + hasText: "Free HIV self-test kits are not available in your area using this service", + }); } async waitUntilPageLoaded(): Promise { diff --git a/tests/page-objects/NHSLogin/CodeSecurityPage.ts b/tests/page-objects/NHSLogin/CodeSecurityPage.ts index 35ebd36b3..b551972c1 100644 --- a/tests/page-objects/NHSLogin/CodeSecurityPage.ts +++ b/tests/page-objects/NHSLogin/CodeSecurityPage.ts @@ -1,4 +1,4 @@ -import { type Locator, type Page } from '@playwright/test'; +import { type Locator, type Page } from "@playwright/test"; export class CodeSecurityPage { readonly page: Page; @@ -10,11 +10,11 @@ export class CodeSecurityPage { constructor(page: Page) { this.page = page; - this.securityCodeField = page.locator('#otp-input'); - this.rememberDeviceCheckbox = page.locator('#rmd'); + this.securityCodeField = page.locator("#otp-input"); + this.rememberDeviceCheckbox = page.locator("#rmd"); this.continueBtn = page.locator('button:has-text("Continue")'); - this.acceptAllCookies = page.locator('#nhsuk-cookie-banner__link_accept'); - this.chooseYourCookies = page.locator('#nhsuk-cookie-banner__link'); + this.acceptAllCookies = page.locator("#nhsuk-cookie-banner__link_accept"); + this.chooseYourCookies = page.locator("#nhsuk-cookie-banner__link"); } async fillAuthOneTimePassword(oneTimePassword: string): Promise { @@ -22,9 +22,7 @@ export class CodeSecurityPage { await this.securityCodeField.fill(oneTimePassword); } - async fillAuthOneTimePasswordAndClickContinue( - oneTimePassword: string - ): Promise { + async fillAuthOneTimePasswordAndClickContinue(oneTimePassword: string): Promise { await this.waitForOtpTrigger(); await this.fillAuthOneTimePassword(oneTimePassword); await this.continueBtn.click(); @@ -37,9 +35,9 @@ export class CodeSecurityPage { async waitForOtpTrigger(): Promise { await this.page.waitForResponse( (response) => - response.url().includes('trigger-otp') && - response.request().method() === 'POST' && - response.status() === 200 + response.url().includes("trigger-otp") && + response.request().method() === "POST" && + response.status() === 200, ); } } diff --git a/tests/page-objects/NHSLogin/NHSEmailAndPasswordPage.ts b/tests/page-objects/NHSLogin/NHSEmailAndPasswordPage.ts index 3e7c6a20e..f3a5d56f1 100644 --- a/tests/page-objects/NHSLogin/NHSEmailAndPasswordPage.ts +++ b/tests/page-objects/NHSLogin/NHSEmailAndPasswordPage.ts @@ -1,4 +1,5 @@ import { type Locator, type Page } from "@playwright/test"; + import type { NHSLoginUser } from "../../utils/users"; import { BasePage } from "../BasePage"; diff --git a/tests/page-objects/NhsLoginHelper.ts b/tests/page-objects/NhsLoginHelper.ts index a638bc544..52ffdc919 100644 --- a/tests/page-objects/NhsLoginHelper.ts +++ b/tests/page-objects/NhsLoginHelper.ts @@ -1,11 +1,9 @@ -import { type Page } from '@playwright/test'; -import { - ConfigFactory, - type ConfigInterface -} from '../configuration/EnvironmentConfiguration'; -import type { NHSLoginUser } from '../utils/users'; -import { NHSEmailAndPasswordPage } from './NHSLogin/NHSEmailAndPasswordPage'; -import { CodeSecurityPage } from './NHSLogin/CodeSecurityPage'; +import { type Page } from "@playwright/test"; + +import { ConfigFactory, type ConfigInterface } from "../configuration/EnvironmentConfiguration"; +import type { NHSLoginUser } from "../utils/users"; +import { CodeSecurityPage } from "./NHSLogin/CodeSecurityPage"; +import { NHSEmailAndPasswordPage } from "./NHSLogin/NHSEmailAndPasswordPage"; export default class NhsLoginHelper { readonly config: ConfigInterface; @@ -15,7 +13,7 @@ export default class NhsLoginHelper { public async fillNhsLoginFormsAndWaitForStartPage( nhsLoginUser: NHSLoginUser, - page: Page + page: Page, ): Promise { const loginPage = new NHSEmailAndPasswordPage(page); const codeSecurityPage = new CodeSecurityPage(page); @@ -25,10 +23,8 @@ export default class NhsLoginHelper { console.log(`Redirected to NHS Login: ${page.url()}`); await loginPage.fillAuthFormWithCredentialsAndClickContinue(nhsLoginUser); - await codeSecurityPage.fillAuthOneTimePasswordAndClickContinue( - nhsLoginUser.otp - ); - await page.waitForURL('**/get-self-test-kit-for-HIV'); + await codeSecurityPage.fillAuthOneTimePasswordAndClickContinue(nhsLoginUser.otp); + await page.waitForURL("**/get-self-test-kit-for-HIV"); } public async loginNhsUser(page: Page, user: NHSLoginUser): Promise { diff --git a/tests/page-objects/OrderSubmittedPage.ts b/tests/page-objects/OrderSubmittedPage.ts index 1441fe168..78edfcd38 100644 --- a/tests/page-objects/OrderSubmittedPage.ts +++ b/tests/page-objects/OrderSubmittedPage.ts @@ -1,4 +1,5 @@ import { Locator, Page } from "@playwright/test"; + import { BasePage } from "./BasePage"; export class OrderSubmittedPage extends BasePage { diff --git a/tests/page-objects/PrivacyPolicyPage.ts b/tests/page-objects/PrivacyPolicyPage.ts index 1a084e243..bf4edd88a 100644 --- a/tests/page-objects/PrivacyPolicyPage.ts +++ b/tests/page-objects/PrivacyPolicyPage.ts @@ -1,4 +1,5 @@ import { Locator, Page } from "@playwright/test"; + import { BasePage } from "./BasePage"; export class PrivacyPolicyPage extends BasePage { diff --git a/tests/page-objects/TermsOfUsePage.ts b/tests/page-objects/TermsOfUsePage.ts index 567d9e30a..d95a8d113 100644 --- a/tests/page-objects/TermsOfUsePage.ts +++ b/tests/page-objects/TermsOfUsePage.ts @@ -1,4 +1,5 @@ import { Locator, Page } from "@playwright/test"; + import { BasePage } from "./BasePage"; export class TermsOfUsePage extends BasePage { diff --git a/tests/test-data/GetOrderRequestParams.ts b/tests/test-data/GetOrderRequestParams.ts index 62871e312..4515c72f1 100644 --- a/tests/test-data/GetOrderRequestParams.ts +++ b/tests/test-data/GetOrderRequestParams.ts @@ -7,7 +7,7 @@ export interface GetOrderParams extends Record ({ nhs_number: nhsNumber, date_of_birth: dateOfBirth, diff --git a/tests/tests/ui/GoToClinicTest.spec.ts b/tests/tests/ui/GoToClinicTest.spec.ts index f117bbf2c..7c4adad0c 100644 --- a/tests/tests/ui/GoToClinicTest.spec.ts +++ b/tests/tests/ui/GoToClinicTest.spec.ts @@ -1,4 +1,5 @@ import { expect } from "@playwright/test"; + import { test } from "../../fixtures/CombinedTestFixture"; import { AddressModel } from "../../models/Address"; diff --git a/tests/tests/ui/KitUnavailableTest.spec.ts b/tests/tests/ui/KitUnavailableTest.spec.ts index 023e0e019..96176510b 100644 --- a/tests/tests/ui/KitUnavailableTest.spec.ts +++ b/tests/tests/ui/KitUnavailableTest.spec.ts @@ -1,4 +1,5 @@ import { expect } from "@playwright/test"; + import { test } from "../../fixtures/CombinedTestFixture"; test.describe( diff --git a/tests/tests/ui/ServiceNavigationTest.spec.ts b/tests/tests/ui/ServiceNavigationTest.spec.ts index e73a9fcdb..ead205cca 100644 --- a/tests/tests/ui/ServiceNavigationTest.spec.ts +++ b/tests/tests/ui/ServiceNavigationTest.spec.ts @@ -1,7 +1,8 @@ -import { test } from "../../fixtures/CombinedTestFixture"; import { expect } from "@playwright/test"; -import { PersonalDetailsModel } from "../../models/PersonalDetails"; + +import { test } from "../../fixtures/CombinedTestFixture"; import { AddressModel } from "../../models/Address"; +import { PersonalDetailsModel } from "../../models/PersonalDetails"; const randomAddress = AddressModel.getRandomAddress(); const personalDetails = PersonalDetailsModel.getRandomPersonalDetails(); diff --git a/tests/utils/AccessibilityModule.ts b/tests/utils/AccessibilityModule.ts index 539b51ad7..3c1d213db 100644 --- a/tests/utils/AccessibilityModule.ts +++ b/tests/utils/AccessibilityModule.ts @@ -1,19 +1,15 @@ -import { Page } from '@playwright/test'; -import AxeBuilder from '@axe-core/playwright'; -import { createHtmlReport } from 'axe-html-reporter'; -import { AxeResults, Result } from 'axe-core'; -import { existsSync, mkdirSync } from 'fs'; -import * as path from 'path'; -import { ConfigFactory } from '../configuration/EnvironmentConfiguration'; +import { existsSync, mkdirSync } from "fs"; +import * as path from "path"; + +import AxeBuilder from "@axe-core/playwright"; +import { Page } from "@playwright/test"; +import { AxeResults, Result } from "axe-core"; +import { createHtmlReport } from "axe-html-reporter"; + +import { ConfigFactory } from "../configuration/EnvironmentConfiguration"; // Accessibility standards to test against -const ACCESSIBILITY_STANDARDS = [ - 'wcag2a', - 'wcag2aa', - 'wcag21a', - 'wcag21aa', - 'wcag22aa' -] as const; +const ACCESSIBILITY_STANDARDS = ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"] as const; export class AccessibilityModule { private readonly standards: string[]; @@ -23,17 +19,12 @@ export class AccessibilityModule { // Get standards from configuration or use default const standardsConfig = ConfigFactory.getConfig().accessibilityStandards; this.standards = standardsConfig - ? standardsConfig.split(',').map((s: string) => s.trim()) + ? standardsConfig.split(",").map((s: string) => s.trim()) : [...ACCESSIBILITY_STANDARDS]; // Get absolute path to tests/testResults/accessibility // __dirname is tests/utils, so go up one level to tests, then into testResults/accessibility - const absoluteDir = path.resolve( - __dirname, - '..', - 'testResults', - 'accessibility' - ); + const absoluteDir = path.resolve(__dirname, "..", "testResults", "accessibility"); // axe-html-reporter always treats outputDir as relative and prepends cwd, // so we need to provide a relative path from cwd to the target directory @@ -53,14 +44,13 @@ export class AccessibilityModule { async runAccessibilityCheck( pageOrPageObject: Page | { page: Page }, pageName: string, - prefix: string = '' + prefix: string = "", ): Promise { - const page = - 'page' in pageOrPageObject ? pageOrPageObject.page : pageOrPageObject; + const page = "page" in pageOrPageObject ? pageOrPageObject.page : pageOrPageObject; const accessErrors: Result[] = []; console.log(`🔍 Running accessibility check on: ${pageName}`); - console.log(`📋 Testing against standards: ${this.standards.join(', ')}`); + console.log(`📋 Testing against standards: ${this.standards.join(", ")}`); // Verify page title await this.verifyPageTitle(page); @@ -79,9 +69,7 @@ export class AccessibilityModule { }); if (accessErrors.length > 0) { - console.log( - `❌ ${accessErrors.length} accessibility errors found on ${pageName}` - ); + console.log(`❌ ${accessErrors.length} accessibility errors found on ${pageName}`); this.logViolations(accessErrors, pageName); } else { console.log(`✅ No accessibility violations found on ${pageName}`); @@ -106,21 +94,16 @@ export class AccessibilityModule { private async createHtmlAccessibilityReport( scanResult: AxeResults, pageName: string, - prefix: string = '' + prefix: string = "", ): Promise { const accessibilityReportPath = this.reportDirectory; const accessibilityReportHtml = - prefix === '' + prefix === "" ? `${pageName}-accessibility-report.html` : `${prefix}-${pageName}-accessibility-report.html`; // Ensure directory exists - const absolutePath = path.resolve( - __dirname, - '..', - 'testResults', - 'accessibility' - ); + const absolutePath = path.resolve(__dirname, "..", "testResults", "accessibility"); if (!existsSync(absolutePath)) { mkdirSync(absolutePath, { recursive: true }); } @@ -130,13 +113,11 @@ export class AccessibilityModule { options: { projectKey: `${pageName}`, outputDir: accessibilityReportPath, - reportFileName: accessibilityReportHtml - } + reportFileName: accessibilityReportHtml, + }, }); - console.log( - `📄 Report generated: ${accessibilityReportPath}/${accessibilityReportHtml}` - ); + console.log(`📄 Report generated: ${accessibilityReportPath}/${accessibilityReportHtml}`); } /** diff --git a/tests/utils/users/SandBoxUserManager.ts b/tests/utils/users/SandBoxUserManager.ts index b4fa505f7..479abb4b5 100644 --- a/tests/utils/users/SandBoxUserManager.ts +++ b/tests/utils/users/SandBoxUserManager.ts @@ -1,8 +1,9 @@ -import { BaseUserManager } from "./BaseUserManager"; +import type { Page } from "@playwright/test"; + import { ConfigFactory } from "../../configuration/EnvironmentConfiguration"; -import type { NHSLoginUser } from "./BaseUser"; import NhsLoginHelper from "../../page-objects/NhsLoginHelper"; -import type { Page } from "@playwright/test"; +import type { NHSLoginUser } from "./BaseUser"; +import { BaseUserManager } from "./BaseUserManager"; import { SpecialUserKey } from "./SpecialUserKey"; export class SandBoxUserManager extends BaseUserManager { diff --git a/tests/utils/users/index.ts b/tests/utils/users/index.ts index ee3b5221f..06c6c99c9 100644 --- a/tests/utils/users/index.ts +++ b/tests/utils/users/index.ts @@ -1,5 +1,5 @@ -export * from './BaseUser'; -export * from './BaseUserManager'; -export * from './SandBoxUserManager'; -export { SpecialUserKey } from './SpecialUserKey'; -export { UserManagerFactory } from './UserManagerFactory'; +export * from "./BaseUser"; +export * from "./BaseUserManager"; +export * from "./SandBoxUserManager"; +export { SpecialUserKey } from "./SpecialUserKey"; +export { UserManagerFactory } from "./UserManagerFactory"; diff --git a/ui/README.md b/ui/README.md index 9289a60af..e191fcb78 100644 --- a/ui/README.md +++ b/ui/README.md @@ -52,7 +52,7 @@ This builds the application and serves it at [http://localhost:8085](http://loca The application requires the following environment variables: -- `NEXT_PUBLIC_BACKEND_URL` - The backend API URL +- `NEXT_PUBLIC_BACKEND_URL` - The backend API URL ### Local Development @@ -72,10 +72,10 @@ the technologies used in this project: In development, all React context state is accessible via `globalThis.__appDebug` in the browser console. Values are evaluated lazily via property getters, so you always see the current state. This is a no-op in production. ```js -__appDebug.order // OrderContext — order answers collected through the journey -__appDebug.auth // AuthContext — authenticated user -__appDebug.navigation // NavigationContext — current step and step history -__appDebug.postcode // PostcodeLookupContext — address lookup results +__appDebug.order; // OrderContext — order answers collected through the journey +__appDebug.auth; // AuthContext — authenticated user +__appDebug.navigation; // NavigationContext — current step and step history +__appDebug.postcode; // PostcodeLookupContext — address lookup results ``` Debug registration follows a **boundary-style devtools pattern** — state providers contain no debug code. Instead, two dedicated devtools components handle all registration: diff --git a/ui/content/ContentService.ts b/ui/content/ContentService.ts index 2cbcb5644..3ce9bddb0 100644 --- a/ui/content/ContentService.ts +++ b/ui/content/ContentService.ts @@ -6,12 +6,19 @@ * * The content is validated on import to ensure structural integrity. */ - -import type { ContentFile, HomeTestPrivacyPolicyContent, HomeTestTermsOfUseContent } from "./schema"; -import { assertValidContent, assertValidPrivacyPolicyContent, assertValidTermsOfUseContent } from "./ContentValidator"; +import { + assertValidContent, + assertValidPrivacyPolicyContent, + assertValidTermsOfUseContent, +} from "./ContentValidator"; import contentData from "./content.json"; import privacyPolicyData from "./hometest-privacy-policy.json"; import termsOfUseData from "./hometest-terms-of-use.json"; +import type { + ContentFile, + HomeTestPrivacyPolicyContent, + HomeTestTermsOfUseContent, +} from "./schema"; assertValidContent(contentData); assertValidPrivacyPolicyContent(privacyPolicyData); diff --git a/ui/content/README.md b/ui/content/README.md index 0e98f612c..50f2f26b0 100644 --- a/ui/content/README.md +++ b/ui/content/README.md @@ -240,7 +240,7 @@ Content is validated at build time. The validator checks: ### Running Validation Manually ```ts -import { validateContent, assertValidContent } from "@/content"; +import { assertValidContent, validateContent } from "@/content"; import contentData from "@/content/content.json"; // Returns { valid: boolean, errors: string[] } diff --git a/ui/content/content.json b/ui/content/content.json index 65b05a7a4..e3891df54 100644 --- a/ui/content/content.json +++ b/ui/content/content.json @@ -396,22 +396,16 @@ "suppliers": { "preventx": { "title": "Preventx terms of use", - "introduction": [ - "This is introduction paragraph for Preventx. https://www.sh.uk/" - ], + "introduction": ["This is introduction paragraph for Preventx. https://www.sh.uk/"], "sections": [ { "id": "preventx-first-section", "heading": "It is first section for Preventx", - "paragraphs": [ - "It is example of content for Preventx." - ], + "paragraphs": ["It is example of content for Preventx."], "subsections": [ { "heading": "It is the first subsection for Preventx", - "paragraphs": [ - "It is example of content for Preventx." - ] + "paragraphs": ["It is example of content for Preventx."] } ] } @@ -419,22 +413,16 @@ }, "sh:24": { "title": "SH:24 terms of use", - "introduction": [ - "This is introduction paragraph for SH:24. https://sh24.org.uk/" - ], + "introduction": ["This is introduction paragraph for SH:24. https://sh24.org.uk/"], "sections": [ { "id": "sh24-first-section", "heading": "It is first section for SH:24 ", - "paragraphs": [ - "It is example of content for SH:24." - ], + "paragraphs": ["It is example of content for SH:24."], "subsections": [ { "heading": "It is the first subsection for SH:24", - "paragraphs": [ - "It is example of content for SH:24." - ] + "paragraphs": ["It is example of content for SH:24."] } ] } @@ -454,15 +442,11 @@ { "id": "preventx-first-section", "heading": "It is first privacy section for Preventx", - "paragraphs": [ - "It is example of privacy policy content for Preventx." - ], + "paragraphs": ["It is example of privacy policy content for Preventx."], "subsections": [ { "heading": "It is the first privacy subsection for Preventx", - "paragraphs": [ - "It is example of privacy policy content for Preventx." - ] + "paragraphs": ["It is example of privacy policy content for Preventx."] } ] } @@ -477,15 +461,11 @@ { "id": "sh24-first-section", "heading": "It is first privacy section for SH:24", - "paragraphs": [ - "It is example of privacy policy content for SH:24." - ], + "paragraphs": ["It is example of privacy policy content for SH:24."], "subsections": [ { "heading": "It is the first privacy subsection for SH:24", - "paragraphs": [ - "It is example of privacy policy content for SH:24." - ] + "paragraphs": ["It is example of privacy policy content for SH:24."] } ] } diff --git a/ui/content/hometest-privacy-policy.json b/ui/content/hometest-privacy-policy.json index e1f879d2a..1df7c6e36 100644 --- a/ui/content/hometest-privacy-policy.json +++ b/ui/content/hometest-privacy-policy.json @@ -25,9 +25,7 @@ ] }, { - "paragraphs": [ - "Further details are set out in the Hometest Terms of Use [LINK]." - ] + "paragraphs": ["Further details are set out in the Hometest Terms of Use [LINK]."] } ] }, @@ -115,9 +113,7 @@ { "heading": "Health Data", "inlineHeading": true, - "paragraphs": [ - "Special category data may also be collected, including test results." - ] + "paragraphs": ["Special category data may also be collected, including test results."] }, { "heading": "Technical Data", @@ -129,9 +125,7 @@ { "heading": "Preference Data", "inlineHeading": true, - "paragraphs": [ - "This includes your preferences on receiving communications from us." - ] + "paragraphs": ["This includes your preferences on receiving communications from us."] }, { "heading": "Usage Data", diff --git a/ui/content/hometest-terms-of-use.json b/ui/content/hometest-terms-of-use.json index b0af9a370..3b4e85fc8 100644 --- a/ui/content/hometest-terms-of-use.json +++ b/ui/content/hometest-terms-of-use.json @@ -136,11 +136,7 @@ { "table": { "caption": "Name of home testing services with functionality and further information", - "headers": [ - "Service", - "Functionality", - "For further information, see:" - ], + "headers": ["Service", "Functionality", "For further information, see:"], "rows": [ [ "Testing Services\n(Hometest App allows you to access the service, but the service is provided outside the Hometest App)", @@ -245,9 +241,7 @@ { "id": "prohibited-uses", "heading": "10. Prohibited uses", - "paragraphs": [ - "10.1. You may not use the Hometest App:" - ], + "paragraphs": ["10.1. You may not use the Hometest App:"], "subsections": [ { "indented": true, diff --git a/ui/content/index.ts b/ui/content/index.ts index 15fa01ba9..eaf9647db 100644 --- a/ui/content/index.ts +++ b/ui/content/index.ts @@ -4,10 +4,10 @@ export { content, getCommonContent, getPageContent } from "./ContentService"; export { - validateContent, - isValidContentFile, - assertValidContent, - assertValidPrivacyPolicyContent, - assertValidTermsOfUseContent, + validateContent, + isValidContentFile, + assertValidContent, + assertValidPrivacyPolicyContent, + assertValidTermsOfUseContent, } from "./ContentValidator"; export * from "./schema"; diff --git a/ui/content/schema.ts b/ui/content/schema.ts index eaebc43b1..d60d483ca 100644 --- a/ui/content/schema.ts +++ b/ui/content/schema.ts @@ -521,8 +521,7 @@ export interface ContentFile { export type MainPagesContent = Omit< PagesContent, - | "home-test-privacy-policy" - | "home-test-terms-of-use" + "home-test-privacy-policy" | "home-test-terms-of-use" >; export interface MainContentFile { diff --git a/ui/eslint.config.mjs b/ui/eslint.config.mjs index f3b6384d9..938e25328 100644 --- a/ui/eslint.config.mjs +++ b/ui/eslint.config.mjs @@ -1,7 +1,6 @@ -import { defineConfig, globalIgnores } from "eslint/config"; - -import nextTs from "eslint-config-next/typescript"; import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; +import { defineConfig, globalIgnores } from "eslint/config"; const eslintConfig = defineConfig([ ...nextVitals, diff --git a/ui/hooks/useContent.ts b/ui/hooks/useContent.ts index c80c0b2cc..1a398f685 100644 --- a/ui/hooks/useContent.ts +++ b/ui/hooks/useContent.ts @@ -7,7 +7,6 @@ import type { EnterAddressManuallyContent, EnterDeliveryAddressContent, EnterMobilePhoneNumberContent, - ServiceErrorContent, GoToClinicContent, HomeTestPrivacyPolicyContent, HomeTestTermsOfUseContent, @@ -17,11 +16,11 @@ import type { OrderSubmittedContent, OrderTrackingContent, SelectDeliveryAddressContent, + ServiceErrorContent, StartPageContent, SuppliersLegalDocumentsContent, TestResultsContent, } from "@/content"; - import { content } from "@/content"; export const PageKeys = { diff --git a/ui/hooks/useThrowError.ts b/ui/hooks/useThrowError.ts index fccf2c28c..b0927b82a 100644 --- a/ui/hooks/useThrowError.ts +++ b/ui/hooks/useThrowError.ts @@ -1,4 +1,4 @@ -import { useState, useCallback } from "react"; +import { useCallback, useState } from "react"; export function useThrowError() { const [error, setError] = useState(null); diff --git a/ui/jest.config.ts b/ui/jest.config.ts index 5e240417c..2cde2d94f 100644 --- a/ui/jest.config.ts +++ b/ui/jest.config.ts @@ -2,42 +2,41 @@ * For a detailed explanation regarding each configuration property, visit: * https://jestjs.io/docs/configuration */ - -import type { Config } from 'jest'; -import nextJest from 'next/jest.js'; +import type { Config } from "jest"; +import nextJest from "next/jest.js"; const createJestConfig = nextJest({ - dir: './', + dir: "./", }); const config: Config = { clearMocks: true, collectCoverage: true, - coverageDirectory: 'coverage', - coverageProvider: 'v8', - coverageReporters: ['text', 'lcov', 'json', 'json-summary'], + coverageDirectory: "coverage", + coverageProvider: "v8", + coverageReporters: ["text", "lcov", "json", "json-summary"], reporters: [ - 'default', + "default", [ - 'jest-junit', + "jest-junit", { - outputDirectory: 'test-results', - outputName: 'junit.xml', + outputDirectory: "test-results", + outputName: "junit.xml", addFileAttribute: true, reportTestSuiteErrors: true, }, ], ], - testEnvironment: 'jsdom', - setupFilesAfterEnv: ['/jest.setup.ts'], + testEnvironment: "jsdom", + setupFilesAfterEnv: ["/jest.setup.ts"], moduleNameMapper: { - '^@/content/(.*)$': '/content/$1', - '^@/content$': '/content/index.ts', - '^@/hooks/(.*)$': '/hooks/$1', - '^@/hooks$': '/hooks/index.ts', - '^@/(.*)$': '/src/$1', + "^@/content/(.*)$": "/content/$1", + "^@/content$": "/content/index.ts", + "^@/hooks/(.*)$": "/hooks/$1", + "^@/hooks$": "/hooks/index.ts", + "^@/(.*)$": "/src/$1", }, - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'json', 'node'], + moduleFileExtensions: ["ts", "tsx", "js", "jsx", "mjs", "cjs", "json", "node"], }; export default createJestConfig(config); diff --git a/ui/src/__tests__/components/AboutService.test.tsx b/ui/src/__tests__/components/AboutService.test.tsx index d805d93ad..e814875fd 100644 --- a/ui/src/__tests__/components/AboutService.test.tsx +++ b/ui/src/__tests__/components/AboutService.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from "@testing-library/react"; +import { MemoryRouter } from "react-router-dom"; import { AboutService } from "@/components/AboutService"; -import { MemoryRouter } from "react-router-dom"; describe("AboutService", () => { it('renders the "About this service" heading', () => { diff --git a/ui/src/__tests__/components/ErrorRedirect.test.tsx b/ui/src/__tests__/components/ErrorRedirect.test.tsx index bcd18ba88..0cbdcce76 100644 --- a/ui/src/__tests__/components/ErrorRedirect.test.tsx +++ b/ui/src/__tests__/components/ErrorRedirect.test.tsx @@ -1,7 +1,7 @@ import "@testing-library/jest-dom"; - -import { act } from "react"; import { render } from "@testing-library/react"; +import { act } from "react"; + import ErrorRedirect from "@/components/ErrorRedirect"; const mockNavigate = jest.fn(); diff --git a/ui/src/__tests__/components/MedicalAbbreviationsHelp.test.tsx b/ui/src/__tests__/components/MedicalAbbreviationsHelp.test.tsx index 317a6b345..49477ef24 100644 --- a/ui/src/__tests__/components/MedicalAbbreviationsHelp.test.tsx +++ b/ui/src/__tests__/components/MedicalAbbreviationsHelp.test.tsx @@ -7,9 +7,7 @@ describe("MedicalAbbreviationsHelp", () => { render(); expect( - screen.getByText( - "You may see medical abbreviations you are not familiar with.", - ), + screen.getByText("You may see medical abbreviations you are not familiar with."), ).toBeInTheDocument(); const link = screen.getByRole("link", { diff --git a/ui/src/__tests__/components/MoreOptionsAndInformation.test.tsx b/ui/src/__tests__/components/MoreOptionsAndInformation.test.tsx index c49eebdbd..7f67e0b21 100644 --- a/ui/src/__tests__/components/MoreOptionsAndInformation.test.tsx +++ b/ui/src/__tests__/components/MoreOptionsAndInformation.test.tsx @@ -24,9 +24,6 @@ describe("MoreOptionsAndInformation", () => { }); expect(hivLink).toBeInTheDocument(); - expect(hivLink).toHaveAttribute( - "href", - "https://www.nhs.uk/conditions/hiv-and-aids/", - ); + expect(hivLink).toHaveAttribute("href", "https://www.nhs.uk/conditions/hiv-and-aids/"); }); }); diff --git a/ui/src/__tests__/components/SupplierLegalDocumentContent.test.tsx b/ui/src/__tests__/components/SupplierLegalDocumentContent.test.tsx index 3fb58e827..a91e94733 100644 --- a/ui/src/__tests__/components/SupplierLegalDocumentContent.test.tsx +++ b/ui/src/__tests__/components/SupplierLegalDocumentContent.test.tsx @@ -1,5 +1,4 @@ import "@testing-library/jest-dom"; - import { render } from "@testing-library/react"; import { SupplierLegalDocumentContent } from "@/components/SupplierLegalDocumentContent"; diff --git a/ui/src/__tests__/components/order-status/OrderStatus.test.tsx b/ui/src/__tests__/components/order-status/OrderStatus.test.tsx index e846d9b65..649c22345 100644 --- a/ui/src/__tests__/components/order-status/OrderStatus.test.tsx +++ b/ui/src/__tests__/components/order-status/OrderStatus.test.tsx @@ -1,8 +1,8 @@ -import { OrderDetails, OrderStatus as OrderStatusEnum } from "@/lib/models/order-details"; import { render, screen } from "@testing-library/react"; - import { MemoryRouter } from "react-router-dom"; + import { OrderStatus } from "@/components/order-status"; +import { OrderDetails, OrderStatus as OrderStatusEnum } from "@/lib/models/order-details"; const renderWithRouter = (component: React.ReactElement) => { return render({component}); diff --git a/ui/src/__tests__/components/order-status/OrderStatusHeader.test.tsx b/ui/src/__tests__/components/order-status/OrderStatusHeader.test.tsx index 81f66e9ac..7bf447b87 100644 --- a/ui/src/__tests__/components/order-status/OrderStatusHeader.test.tsx +++ b/ui/src/__tests__/components/order-status/OrderStatusHeader.test.tsx @@ -1,7 +1,7 @@ -import { OrderDetails, OrderStatus } from "@/lib/models/order-details"; import { render, screen } from "@testing-library/react"; import { OrderStatusHeader } from "@/components/order-status"; +import { OrderDetails, OrderStatus } from "@/lib/models/order-details"; describe("OrderStatusHeader", () => { const mockOrder: OrderDetails = { diff --git a/ui/src/__tests__/hooks/useThrowError.test.tsx b/ui/src/__tests__/hooks/useThrowError.test.tsx index 9fede4a2b..8f56d05f5 100644 --- a/ui/src/__tests__/hooks/useThrowError.test.tsx +++ b/ui/src/__tests__/hooks/useThrowError.test.tsx @@ -1,10 +1,9 @@ import "@testing-library/jest-dom"; - import { fireEvent, render, screen, waitFor } from "@testing-library/react"; - import React from "react"; -import { TestErrorBoundary } from "@/lib/test-utils/TestErrorBoundary"; + import { useThrowError } from "@/hooks"; +import { TestErrorBoundary } from "@/lib/test-utils/TestErrorBoundary"; function AsyncSubmitter({ error }: Readonly<{ error: Error }>) { const throwError = useThrowError(); diff --git a/ui/src/__tests__/layouts/PageLayout.test.tsx b/ui/src/__tests__/layouts/PageLayout.test.tsx index 88fc93b0d..a54ffbe33 100644 --- a/ui/src/__tests__/layouts/PageLayout.test.tsx +++ b/ui/src/__tests__/layouts/PageLayout.test.tsx @@ -1,5 +1,4 @@ import "@testing-library/jest-dom"; - import { fireEvent, render, screen } from "@testing-library/react"; import PageLayout from "@/layouts/PageLayout"; diff --git a/ui/src/__tests__/lib/services/order-details-service.test.ts b/ui/src/__tests__/lib/services/order-details-service.test.ts index d77f2849f..0ae713d7b 100644 --- a/ui/src/__tests__/lib/services/order-details-service.test.ts +++ b/ui/src/__tests__/lib/services/order-details-service.test.ts @@ -1,7 +1,7 @@ -jest.mock("@/settings", () => ({backendUrl: "http://mock-backend"})); - -import orderDetailsService from "@/lib/services/order-details-service"; import { OrderStatus } from "@/lib/models/order-details"; +import orderDetailsService from "@/lib/services/order-details-service"; + +jest.mock("@/settings", () => ({ backendUrl: "http://mock-backend" })); const mockFetch = jest.fn(); globalThis.fetch = mockFetch as typeof fetch; @@ -10,7 +10,7 @@ describe("OrderDetailsService", () => { const orderId = "550e8400-e29b-41d4-a716-446655440000"; const nhsNumber = "2657119018"; const dateOfBirth = "1990-08-11"; - const patient = {nhsNumber, dateOfBirth}; + const patient = { nhsNumber, dateOfBirth }; const apiUrl = `http://mock-backend/get-order`; @@ -20,58 +20,78 @@ describe("OrderDetailsService", () => { it("should return order details for a valid order id", async () => { const mockResponse = { - "resourceType": "Bundle", "type": "searchset", "total": 1, "entry": [{ - "fullUrl": `urn:uuid:${orderId}`, "resource": { - "resourceType": "ServiceRequest", - "id": orderId, - "identifier": [{"system": "https://fhir.hometest.nhs.uk/Id/order-id", "value": "100007"}], - "status": "active", - "intent": "order", - "code": { - "coding": [{ - "system": "http://snomed.info/sct", - "code": "31676001", - "display": "HIV antigen test" - }], "text": "HIV antigen test" + resourceType: "Bundle", + type: "searchset", + total: 1, + entry: [ + { + fullUrl: `urn:uuid:${orderId}`, + resource: { + resourceType: "ServiceRequest", + id: orderId, + identifier: [{ system: "https://fhir.hometest.nhs.uk/Id/order-id", value: "100007" }], + status: "active", + intent: "order", + code: { + coding: [ + { + system: "http://snomed.info/sct", + code: "31676001", + display: "HIV antigen test", + }, + ], + text: "HIV antigen test", + }, + subject: { reference: "#patient-1" }, + requester: { reference: "Organization/ORG001" }, + performer: [ + { + reference: "Organization/77777777-7777-4777-8777-777777777777", + type: "Organization", + display: "SH:24", + }, + ], + authoredOn: "2026-03-09T13:56:17.471Z", + extension: [ + { + url: "https://fhir.hometest.nhs.uk/StructureDefinition/business-status", + extension: [{ url: "timestamp", valueDate: "2026-03-09" }], + valueCodeableConcept: { + coding: [ + { + system: "https://fhir.hometest.nhs.uk/CodeSystem/order-business-status", + code: "DISPATCHED", + display: "Test has been dispatched to the patient", + }, + ], + text: "DISPATCHED", + }, + }, + ], + contained: [ + { + resourceType: "Patient", + id: "patient-1", + identifier: [ + { + system: "https://fhir.nhs.uk/Id/nhs-number", + value: nhsNumber, + use: "official", + }, + ], + birthDate: dateOfBirth, + }, + ], }, - "subject": {"reference": "#patient-1"}, - "requester": {"reference": "Organization/ORG001"}, - "performer": [{ - "reference": "Organization/77777777-7777-4777-8777-777777777777", - "type": "Organization", - "display": "SH:24" - }], - "authoredOn": "2026-03-09T13:56:17.471Z", - "extension": [{ - "url": "https://fhir.hometest.nhs.uk/StructureDefinition/business-status", - "extension": [{"url": "timestamp", "valueDate": "2026-03-09"}], - "valueCodeableConcept": { - "coding": [{ - "system": "https://fhir.hometest.nhs.uk/CodeSystem/order-business-status", - "code": "DISPATCHED", - "display": "Test has been dispatched to the patient" - }], "text": "DISPATCHED" - } - }], - "contained": [{ - "resourceType": "Patient", - "id": "patient-1", - "identifier": [{ - "system": "https://fhir.nhs.uk/Id/nhs-number", - "value": nhsNumber, - "use": "official" - }], - "birthDate": dateOfBirth - }] - } - }] - } + }, + ], + }; mockFetch.mockResolvedValueOnce({ ok: true, status: 200, json: async () => mockResponse, - }) + }); const response = await orderDetailsService.get(orderId, patient); @@ -82,14 +102,17 @@ describe("OrderDetailsService", () => { orderedDate: "2026-03-09T13:56:17.471Z", referenceNumber: "100007", status: OrderStatus.DISPATCHED, - supplier: "SH:24" + supplier: "SH:24", }); - expect(mockFetch).toHaveBeenCalledWith(`${apiUrl}?nhs_number=${nhsNumber}&date_of_birth=${dateOfBirth}&order_id=${orderId}`, { - method: "GET", - headers: { - "Accept": "application/fhir+json", + expect(mockFetch).toHaveBeenCalledWith( + `${apiUrl}?nhs_number=${nhsNumber}&date_of_birth=${dateOfBirth}&order_id=${orderId}`, + { + method: "GET", + headers: { + Accept: "application/fhir+json", + }, }, - }) - }) + ); + }); }); diff --git a/ui/src/__tests__/lib/services/order-service.test.ts b/ui/src/__tests__/lib/services/order-service.test.ts index 609ab5770..a8a74dad3 100644 --- a/ui/src/__tests__/lib/services/order-service.test.ts +++ b/ui/src/__tests__/lib/services/order-service.test.ts @@ -1,10 +1,10 @@ -jest.mock("@/settings", () => ({ backendUrl: "http://mock-backend" })); - import orderService, { OrderServiceRequest, OrderServiceResponse, } from "@/lib/services/order-service"; +jest.mock("@/settings", () => ({ backendUrl: "http://mock-backend" })); + const mockFetch = jest.fn(); globalThis.fetch = mockFetch as typeof fetch; @@ -45,9 +45,7 @@ describe("OrderService", () => { }); beforeEach(() => { - randomUUIDSpy = jest - .spyOn(globalThis.crypto, "randomUUID") - .mockReturnValue("test-uuid"); + randomUUIDSpy = jest.spyOn(globalThis.crypto, "randomUUID").mockReturnValue("test-uuid"); }); afterEach(() => { @@ -88,9 +86,7 @@ describe("OrderService", () => { json: async () => ({ message: "Bad request" }), }); - await expect(orderService.submitOrder(request)).rejects.toThrow( - "Bad request", - ); + await expect(orderService.submitOrder(request)).rejects.toThrow("Bad request"); }); it("should throw a fallback error when the response has no message", async () => { @@ -102,8 +98,6 @@ describe("OrderService", () => { }, }); - await expect(orderService.submitOrder(request)).rejects.toThrow( - "Failed to submit order: 500", - ); + await expect(orderService.submitOrder(request)).rejects.toThrow("Failed to submit order: 500"); }); }); diff --git a/ui/src/__tests__/lib/services/test-results-service.test.ts b/ui/src/__tests__/lib/services/test-results-service.test.ts index 189e25396..04f9246cf 100644 --- a/ui/src/__tests__/lib/services/test-results-service.test.ts +++ b/ui/src/__tests__/lib/services/test-results-service.test.ts @@ -1,7 +1,7 @@ -jest.mock("@/settings", () => ({ backendUrl: "http://mock-backend" })); - import testResultsService from "@/lib/services/test-results-service"; +jest.mock("@/settings", () => ({ backendUrl: "http://mock-backend" })); + const mockFetch = jest.fn(); globalThis.fetch = mockFetch; @@ -30,8 +30,7 @@ describe("TestResultsService", () => { { coding: [ { - system: - "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", + system: "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", code: "N", }, ], @@ -77,8 +76,7 @@ describe("TestResultsService", () => { { coding: [ { - system: - "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", + system: "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", code: "A", }, ], diff --git a/ui/src/__tests__/lib/utils/AppDevtools.test.tsx b/ui/src/__tests__/lib/utils/AppDevtools.test.tsx index 160054b44..649daf3cc 100644 --- a/ui/src/__tests__/lib/utils/AppDevtools.test.tsx +++ b/ui/src/__tests__/lib/utils/AppDevtools.test.tsx @@ -1,6 +1,6 @@ import "@testing-library/jest-dom"; - import { render } from "@testing-library/react"; + import { AppDevtools } from "@/lib/utils/AppDevtools"; import { AuthProvider } from "@/state"; diff --git a/ui/src/__tests__/lib/utils/JourneyDevtools.test.tsx b/ui/src/__tests__/lib/utils/JourneyDevtools.test.tsx index bd53e45dc..c795fe6ec 100644 --- a/ui/src/__tests__/lib/utils/JourneyDevtools.test.tsx +++ b/ui/src/__tests__/lib/utils/JourneyDevtools.test.tsx @@ -1,7 +1,7 @@ import "@testing-library/jest-dom"; - import { render } from "@testing-library/react"; import { MemoryRouter } from "react-router-dom"; + import { JourneyDevtools } from "@/lib/utils/JourneyDevtools"; import { CreateOrderProvider, JourneyNavigationProvider, PostcodeLookupProvider } from "@/state"; @@ -10,9 +10,7 @@ function JourneyWrapper({ children }: { children: React.ReactNode }) { - - {children} - + {children} @@ -27,12 +25,20 @@ describe("JourneyDevtools", () => { }); afterEach(() => { - Object.defineProperty(process.env, "NODE_ENV", { value: originalEnv, configurable: true, writable: true }); + Object.defineProperty(process.env, "NODE_ENV", { + value: originalEnv, + configurable: true, + writable: true, + }); delete globalThis.__appDebug; }); it("renders nothing to the DOM", () => { - Object.defineProperty(process.env, "NODE_ENV", { value: "development", configurable: true, writable: true }); + Object.defineProperty(process.env, "NODE_ENV", { + value: "development", + configurable: true, + writable: true, + }); const { container } = render( @@ -44,7 +50,11 @@ describe("JourneyDevtools", () => { }); it("registers order, navigation and postcode debug state in development", () => { - Object.defineProperty(process.env, "NODE_ENV", { value: "development", configurable: true, writable: true }); + Object.defineProperty(process.env, "NODE_ENV", { + value: "development", + configurable: true, + writable: true, + }); render( @@ -59,7 +69,11 @@ describe("JourneyDevtools", () => { }); it("does not register debug state in production", () => { - Object.defineProperty(process.env, "NODE_ENV", { value: "production", configurable: true, writable: true }); + Object.defineProperty(process.env, "NODE_ENV", { + value: "production", + configurable: true, + writable: true, + }); render( @@ -71,7 +85,11 @@ describe("JourneyDevtools", () => { }); it("cleans up all debug slices on unmount", () => { - Object.defineProperty(process.env, "NODE_ENV", { value: "development", configurable: true, writable: true }); + Object.defineProperty(process.env, "NODE_ENV", { + value: "development", + configurable: true, + writable: true, + }); const { unmount } = render( diff --git a/ui/src/__tests__/lib/utils/debug.test.ts b/ui/src/__tests__/lib/utils/debug.test.ts index d87beabfe..47ee49731 100644 --- a/ui/src/__tests__/lib/utils/debug.test.ts +++ b/ui/src/__tests__/lib/utils/debug.test.ts @@ -8,13 +8,21 @@ describe("debug utilities", () => { }); afterEach(() => { - Object.defineProperty(process.env, "NODE_ENV", { value: originalEnv, configurable: true, writable: true }); + Object.defineProperty(process.env, "NODE_ENV", { + value: originalEnv, + configurable: true, + writable: true, + }); delete globalThis.__appDebug; }); describe("registerDebugState", () => { it("registers a state getter accessible via __appDebug in development", () => { - Object.defineProperty(process.env, "NODE_ENV", { value: "development", configurable: true, writable: true }); + Object.defineProperty(process.env, "NODE_ENV", { + value: "development", + configurable: true, + writable: true, + }); const state = { count: 1 }; registerDebugState("test", () => state); @@ -24,7 +32,11 @@ describe("debug utilities", () => { }); it("returns live values via the getter", () => { - Object.defineProperty(process.env, "NODE_ENV", { value: "development", configurable: true, writable: true }); + Object.defineProperty(process.env, "NODE_ENV", { + value: "development", + configurable: true, + writable: true, + }); let state = { count: 1 }; registerDebugState("test", () => state); @@ -35,7 +47,11 @@ describe("debug utilities", () => { }); it("does nothing in production", () => { - Object.defineProperty(process.env, "NODE_ENV", { value: "production", configurable: true, writable: true }); + Object.defineProperty(process.env, "NODE_ENV", { + value: "production", + configurable: true, + writable: true, + }); registerDebugState("test", () => "value"); @@ -43,7 +59,11 @@ describe("debug utilities", () => { }); it("allows registering multiple slices", () => { - Object.defineProperty(process.env, "NODE_ENV", { value: "development", configurable: true, writable: true }); + Object.defineProperty(process.env, "NODE_ENV", { + value: "development", + configurable: true, + writable: true, + }); registerDebugState("auth", () => ({ user: "alice" })); registerDebugState("order", () => ({ id: 1 })); @@ -53,7 +73,11 @@ describe("debug utilities", () => { }); it("overwrites a previously registered slice", () => { - Object.defineProperty(process.env, "NODE_ENV", { value: "development", configurable: true, writable: true }); + Object.defineProperty(process.env, "NODE_ENV", { + value: "development", + configurable: true, + writable: true, + }); registerDebugState("test", () => "first"); registerDebugState("test", () => "second"); @@ -62,20 +86,26 @@ describe("debug utilities", () => { }); it("enumerates registered slices", () => { - Object.defineProperty(process.env, "NODE_ENV", { value: "development", configurable: true, writable: true }); + Object.defineProperty(process.env, "NODE_ENV", { + value: "development", + configurable: true, + writable: true, + }); registerDebugState("a", () => 1); registerDebugState("b", () => 2); - expect(Object.keys(globalThis.__appDebug!)).toEqual( - expect.arrayContaining(["a", "b"]), - ); + expect(Object.keys(globalThis.__appDebug!)).toEqual(expect.arrayContaining(["a", "b"])); }); }); describe("unregisterDebugState", () => { it("removes a registered slice", () => { - Object.defineProperty(process.env, "NODE_ENV", { value: "development", configurable: true, writable: true }); + Object.defineProperty(process.env, "NODE_ENV", { + value: "development", + configurable: true, + writable: true, + }); registerDebugState("test", () => "value"); expect(globalThis.__appDebug!.test).toBe("value"); @@ -85,7 +115,11 @@ describe("debug utilities", () => { }); it("does nothing in production", () => { - Object.defineProperty(process.env, "NODE_ENV", { value: "production", configurable: true, writable: true }); + Object.defineProperty(process.env, "NODE_ENV", { + value: "production", + configurable: true, + writable: true, + }); unregisterDebugState("test"); @@ -93,7 +127,11 @@ describe("debug utilities", () => { }); it("does nothing if __appDebug does not exist", () => { - Object.defineProperty(process.env, "NODE_ENV", { value: "development", configurable: true, writable: true }); + Object.defineProperty(process.env, "NODE_ENV", { + value: "development", + configurable: true, + writable: true, + }); expect(() => unregisterDebugState("test")).not.toThrow(); }); diff --git a/ui/src/__tests__/lib/utils/fhir-utils.test.ts b/ui/src/__tests__/lib/utils/fhir-utils.test.ts index 9e62766ed..9a1627056 100644 --- a/ui/src/__tests__/lib/utils/fhir-utils.test.ts +++ b/ui/src/__tests__/lib/utils/fhir-utils.test.ts @@ -56,10 +56,7 @@ describe("FhirUtils", () => { extension: [{ url: "https://example.test/other" }, expected], }; - const result = FhirUtils.findExtension( - resource, - "https://example.test/target", - ); + const result = FhirUtils.findExtension(resource, "https://example.test/target"); expect(result).toEqual(expected); }); @@ -80,10 +77,7 @@ describe("FhirUtils", () => { ], }; - const result = FhirUtils.findCoding( - concept, - "https://example.test/target", - ); + const result = FhirUtils.findCoding(concept, "https://example.test/target"); expect(result).toEqual({ system: "https://example.test/target", @@ -92,10 +86,7 @@ describe("FhirUtils", () => { }); it("returns null when concept is undefined", () => { - const result = FhirUtils.findCoding( - undefined, - "https://example.test/target", - ); + const result = FhirUtils.findCoding(undefined, "https://example.test/target"); expect(result).toBeNull(); }); @@ -132,10 +123,7 @@ describe("FhirUtils", () => { ], }; - const result = FhirUtils.findIdentifier( - resource, - "https://example.test/target", - ); + const result = FhirUtils.findIdentifier(resource, "https://example.test/target"); expect(result).toEqual({ system: "https://example.test/target", @@ -144,10 +132,7 @@ describe("FhirUtils", () => { }); it("returns null when identifier is missing", () => { - const result = FhirUtils.findIdentifier( - {}, - "https://example.test/target", - ); + const result = FhirUtils.findIdentifier({}, "https://example.test/target"); expect(result).toBeNull(); }); diff --git a/ui/src/__tests__/lib/validation/mobileNumberValidation.test.ts b/ui/src/__tests__/lib/validation/mobileNumberValidation.test.ts index d6b457ead..9e68e2841 100644 --- a/ui/src/__tests__/lib/validation/mobileNumberValidation.test.ts +++ b/ui/src/__tests__/lib/validation/mobileNumberValidation.test.ts @@ -1,5 +1,5 @@ -import { createMobileNumberSchema } from "@/lib/validation/mobile-number-schema"; import type { ValidationMessages } from "@/content"; +import { createMobileNumberSchema } from "@/lib/validation/mobile-number-schema"; const mockValidationMessages: ValidationMessages = { postcode: { @@ -39,8 +39,8 @@ const mockValidationMessages: ValidationMessages = { invalid: "Enter a UK mobile phone number", }, consent: { - required: "" - } + required: "", + }, }; describe("mobile number Zod schema", () => { @@ -50,17 +50,13 @@ describe("mobile number Zod schema", () => { it("should return error for empty string", () => { const result = schema.safeParse(""); if (result.success) return fail("Expected failure for empty string"); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.required - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.required); }); it("should return error for string with only spaces", () => { const result = schema.safeParse(" "); if (result.success) return fail("Expected failure for spaces"); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.required - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.required); }); }); @@ -124,41 +120,31 @@ describe("mobile number Zod schema", () => { it("should reject UK landline numbers (01xxx)", () => { const result = schema.safeParse("01234567890"); if (result.success) return fail("Expected failure for 01xxx"); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.invalid - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.invalid); }); it("should reject UK landline numbers (02xxx)", () => { const result = schema.safeParse("02071234567"); if (result.success) return fail("Expected failure for 02xxx"); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.invalid - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.invalid); }); it("should reject numbers with non-UK mobile prefix (06xxx)", () => { const result = schema.safeParse("06777900900"); if (result.success) return fail("Expected failure for 06xxx"); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.invalid - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.invalid); }); it("should reject numbers with non-UK mobile prefix (08xxx)", () => { const result = schema.safeParse("08777900900"); if (result.success) return fail("Expected failure for 08xxx"); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.invalid - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.invalid); }); it("should reject numbers with non-UK mobile prefix (09xxx)", () => { const result = schema.safeParse("09777900900"); if (result.success) return fail("Expected failure for 09xxx"); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.invalid - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.invalid); }); }); @@ -166,9 +152,7 @@ describe("mobile number Zod schema", () => { it("should reject numbers with letters", () => { const result = schema.safeParse("07771ABC900"); if (result.success) return fail("Expected failure for letters"); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.invalid - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.invalid); }); it("should reject numbers with special characters (not allowed)", () => { @@ -183,38 +167,26 @@ describe("mobile number Zod schema", () => { invalidNumbers.forEach((number) => { const result = schema.safeParse(number); if (result.success) return fail(`Expected failure for ${number}`); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.invalid - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.invalid); }); }); }); describe("Length Validation", () => { it("should reject numbers with too few digits", () => { - const shortNumbers = [ - "077", - "07771", - "07771 900", - "+44 7", - "077719009", - ]; + const shortNumbers = ["077", "07771", "07771 900", "+44 7", "077719009"]; shortNumbers.forEach((number) => { const result = schema.safeParse(number); if (result.success) return fail(`Expected failure for ${number}`); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.invalid - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.invalid); }); }); it("should reject numbers with more than 15 digits", () => { const result = schema.safeParse("07771900900123456"); if (result.success) return fail("Expected failure for too many digits"); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.invalid - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.invalid); }); it("should accept numbers with exactly 10 digits (07 + 9 digits)", () => { @@ -234,33 +206,25 @@ describe("mobile number Zod schema", () => { it("should reject non-UK international numbers (US)", () => { const result = schema.safeParse("+1 555 123 4567"); if (result.success) return fail("Expected failure for US number"); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.invalid - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.invalid); }); it("should reject non-UK international numbers (Ireland)", () => { const result = schema.safeParse("+353 87 123 4567"); if (result.success) return fail("Expected failure for Ireland number"); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.invalid - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.invalid); }); it("should reject non-UK international numbers (France)", () => { const result = schema.safeParse("+33 6 12 34 56 78"); if (result.success) return fail("Expected failure for France number"); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.invalid - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.invalid); }); it("should reject non-UK international numbers (Australia)", () => { const result = schema.safeParse("+61 412 345 678"); if (result.success) return fail("Expected failure for Australia number"); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.invalid - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.invalid); }); }); @@ -274,17 +238,13 @@ describe("mobile number Zod schema", () => { it("should handle number with leading zeros correctly", () => { const result = schema.safeParse("007771900900"); if (result.success) return fail("Expected failure for leading zeros"); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.invalid - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.invalid); }); it("should reject number missing leading 0 from 07xxx format", () => { const result = schema.safeParse("7771900900"); if (result.success) return fail("Expected failure for missing leading 0"); - expect(result.error.issues[0].message).toBe( - mockValidationMessages.mobileNumber.invalid - ); + expect(result.error.issues[0].message).toBe(mockValidationMessages.mobileNumber.invalid); }); }); }); diff --git a/ui/src/__tests__/routes/ServiceErrorPage.test.tsx b/ui/src/__tests__/routes/ServiceErrorPage.test.tsx index b444db0a4..eb6a02591 100644 --- a/ui/src/__tests__/routes/ServiceErrorPage.test.tsx +++ b/ui/src/__tests__/routes/ServiceErrorPage.test.tsx @@ -1,7 +1,7 @@ import "@testing-library/jest-dom"; - -import { MemoryRouter } from "react-router-dom"; import { render, screen } from "@testing-library/react"; +import { MemoryRouter } from "react-router-dom"; + import ServiceErrorPage from "@/routes/ServiceErrorPage"; jest.mock("@/hooks", () => ({ @@ -54,10 +54,7 @@ describe("ServiceErrorPage", () => { renderWithState({ errorMessage: "Something went wrong" }); - expect(consoleSpy).toHaveBeenCalledWith( - "[ServiceErrorPage]", - "Something went wrong", - ); + expect(consoleSpy).toHaveBeenCalledWith("[ServiceErrorPage]", "Something went wrong"); consoleSpy.mockRestore(); }); diff --git a/ui/src/__tests__/routes/SuppliersPrivacyPolicyPage.test.tsx b/ui/src/__tests__/routes/SuppliersPrivacyPolicyPage.test.tsx index 3c446f193..9ae36995c 100644 --- a/ui/src/__tests__/routes/SuppliersPrivacyPolicyPage.test.tsx +++ b/ui/src/__tests__/routes/SuppliersPrivacyPolicyPage.test.tsx @@ -1,7 +1,6 @@ import "@testing-library/jest-dom"; - -import { MemoryRouter, Route, Routes } from "react-router-dom"; import { fireEvent, render, screen } from "@testing-library/react"; +import { MemoryRouter, Route, Routes } from "react-router-dom"; import SuppliersPrivacyPolicyPage from "@/routes/SuppliersPrivacyPolicyPage"; diff --git a/ui/src/__tests__/routes/SuppliersTermsConditionsPage.test.tsx b/ui/src/__tests__/routes/SuppliersTermsConditionsPage.test.tsx index dcee572a8..ada257ee7 100644 --- a/ui/src/__tests__/routes/SuppliersTermsConditionsPage.test.tsx +++ b/ui/src/__tests__/routes/SuppliersTermsConditionsPage.test.tsx @@ -1,7 +1,6 @@ import "@testing-library/jest-dom"; - -import { MemoryRouter, Route, Routes } from "react-router-dom"; import { fireEvent, render, screen } from "@testing-library/react"; +import { MemoryRouter, Route, Routes } from "react-router-dom"; import SuppliersTermsConditionsPage from "@/routes/SuppliersTermsConditionsPage"; diff --git a/ui/src/__tests__/routes/TestResultsPage.test.tsx b/ui/src/__tests__/routes/TestResultsPage.test.tsx index 4b375075b..ffbe597f2 100644 --- a/ui/src/__tests__/routes/TestResultsPage.test.tsx +++ b/ui/src/__tests__/routes/TestResultsPage.test.tsx @@ -1,16 +1,15 @@ -import "@testing-library/jest-dom"; - -import { AuthUser, useAuth } from "@/state"; -import { MemoryRouter, Route, Routes } from "react-router-dom"; -import { OrderDetails, OrderStatus } from "@/lib/models/order-details"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import "@testing-library/jest-dom"; import { render, screen, waitFor } from "@testing-library/react"; -import { TestErrorBoundary } from "@/lib/test-utils/TestErrorBoundary"; - -import TestResultsPage from "@/routes/TestResultsPage"; import { act } from "react"; +import { MemoryRouter, Route, Routes } from "react-router-dom"; + +import { OrderDetails, OrderStatus } from "@/lib/models/order-details"; import orderDetailsService from "@/lib/services/order-details-service"; import testResultsService from "@/lib/services/test-results-service"; +import { TestErrorBoundary } from "@/lib/test-utils/TestErrorBoundary"; +import TestResultsPage from "@/routes/TestResultsPage"; +import { AuthUser, useAuth } from "@/state"; jest.mock("@/lib/services/order-details-service", () => ({ __esModule: true, diff --git a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/ConfirmMobileNumberPage.test.tsx b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/ConfirmMobileNumberPage.test.tsx index b4ede3c32..43f14b5b2 100644 --- a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/ConfirmMobileNumberPage.test.tsx +++ b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/ConfirmMobileNumberPage.test.tsx @@ -1,11 +1,10 @@ import "@testing-library/jest-dom"; - -import { AuthProvider, CreateOrderProvider, JourneyNavigationProvider, useAuth } from "@/state"; import { fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; +import { MemoryRouter } from "react-router-dom"; import ConfirmMobileNumberPage from "@/routes/get-self-test-kit-for-HIV-journey/ConfirmMobileNumberPage"; -import { MemoryRouter } from "react-router-dom"; -import React from "react"; +import { AuthProvider, CreateOrderProvider, JourneyNavigationProvider, useAuth } from "@/state"; const TestWrapper = ({ children }: { children: React.ReactNode }) => ( diff --git a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/EnterDeliveryAddressPage.test.tsx b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/EnterDeliveryAddressPage.test.tsx index d9e24ed33..1eb3064ac 100644 --- a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/EnterDeliveryAddressPage.test.tsx +++ b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/EnterDeliveryAddressPage.test.tsx @@ -1,12 +1,11 @@ import "@testing-library/jest-dom"; - import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; - import React from "react"; +import { MemoryRouter } from "react-router-dom"; + import { TestErrorBoundary } from "@/lib/test-utils/TestErrorBoundary"; -import { CreateOrderProvider, JourneyNavigationProvider, PostcodeLookupProvider } from "@/state"; import EnterDeliveryAddressPage from "@/routes/get-self-test-kit-for-HIV-journey/EnterDeliveryAddressPage"; -import { MemoryRouter } from "react-router-dom"; +import { CreateOrderProvider, JourneyNavigationProvider, PostcodeLookupProvider } from "@/state"; const mockLookupPostcode = jest.fn(); const mockClearAddresses = jest.fn(); diff --git a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/EnterMobileNumberPage.test.tsx b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/EnterMobileNumberPage.test.tsx index 8de5df00c..b28fa0223 100644 --- a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/EnterMobileNumberPage.test.tsx +++ b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/EnterMobileNumberPage.test.tsx @@ -1,8 +1,8 @@ -import { CreateOrderProvider, JourneyNavigationProvider } from "@/state"; import { fireEvent, render, screen } from "@testing-library/react"; +import { MemoryRouter } from "react-router-dom"; import EnterMobileNumberPage from "@/routes/get-self-test-kit-for-HIV-journey/EnterMobileNumberPage"; -import { MemoryRouter } from "react-router-dom"; +import { CreateOrderProvider, JourneyNavigationProvider } from "@/state"; const TestWrapper = ({ children }: { children: React.ReactNode }) => ( diff --git a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/FormSuppliersPrivacyPolicyPage.test.tsx b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/FormSuppliersPrivacyPolicyPage.test.tsx index ead77d7cb..425120928 100644 --- a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/FormSuppliersPrivacyPolicyPage.test.tsx +++ b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/FormSuppliersPrivacyPolicyPage.test.tsx @@ -1,5 +1,4 @@ import "@testing-library/jest-dom"; - import { fireEvent, render, screen } from "@testing-library/react"; import FormSuppliersPrivacyPolicyPage from "@/routes/get-self-test-kit-for-HIV-journey/FormSuppliersPrivacyPolicyPage"; diff --git a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/FormSuppliersTermsConditionsPage.test.tsx b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/FormSuppliersTermsConditionsPage.test.tsx index aff066cd4..9b2bd2242 100644 --- a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/FormSuppliersTermsConditionsPage.test.tsx +++ b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/FormSuppliersTermsConditionsPage.test.tsx @@ -1,5 +1,4 @@ import "@testing-library/jest-dom"; - import { fireEvent, render, screen } from "@testing-library/react"; import FormSuppliersTermsConditionsPage from "@/routes/get-self-test-kit-for-HIV-journey/FormSuppliersTermsConditionsPage"; diff --git a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/GetSelfTestKitPage.test.tsx b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/GetSelfTestKitPage.test.tsx index 1ec866067..c4fa9ba05 100644 --- a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/GetSelfTestKitPage.test.tsx +++ b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/GetSelfTestKitPage.test.tsx @@ -1,11 +1,10 @@ import "@testing-library/jest-dom"; - import { render, screen } from "@testing-library/react"; +import { MemoryRouter } from "react-router-dom"; -import { CreateOrderProvider, JourneyNavigationProvider } from "@/state"; import FormPageLayout from "@/layouts/FormPageLayout"; import GetSelfTestKitPage from "@/routes/get-self-test-kit-for-HIV-journey/GetSelfTestKitPage"; -import { MemoryRouter } from "react-router-dom"; +import { CreateOrderProvider, JourneyNavigationProvider } from "@/state"; const TestWrapper = ({ children }: { children: React.ReactNode }) => ( diff --git a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/GoToClinicPage.test.tsx b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/GoToClinicPage.test.tsx index 76bb71d70..402382e6d 100644 --- a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/GoToClinicPage.test.tsx +++ b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/GoToClinicPage.test.tsx @@ -1,9 +1,8 @@ import "@testing-library/jest-dom"; - import { fireEvent, render, screen } from "@testing-library/react"; -import GoToClinicPage from "@/routes/get-self-test-kit-for-HIV-journey/GoToClinicPage"; import { usePageContent } from "@/hooks"; +import GoToClinicPage from "@/routes/get-self-test-kit-for-HIV-journey/GoToClinicPage"; const mockGoToStep = jest.fn(); const mockGoBack = jest.fn(); diff --git a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/HowComfortablePrickingFingerPage.test.tsx b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/HowComfortablePrickingFingerPage.test.tsx index 28a9fef7f..d5f70a6c4 100644 --- a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/HowComfortablePrickingFingerPage.test.tsx +++ b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/HowComfortablePrickingFingerPage.test.tsx @@ -1,11 +1,10 @@ import "@testing-library/jest-dom"; - -import { AuthProvider, CreateOrderProvider, JourneyNavigationProvider, useAuth } from "@/state"; -import { MemoryRouter, useLocation } from "react-router-dom"; import { fireEvent, render, screen } from "@testing-library/react"; +import { useEffect } from "react"; +import { MemoryRouter, useLocation } from "react-router-dom"; import HowComfortablePrickingFingerPage from "@/routes/get-self-test-kit-for-HIV-journey/HowComfortablePrickingFingerPage"; -import { useEffect } from "react"; +import { AuthProvider, CreateOrderProvider, JourneyNavigationProvider, useAuth } from "@/state"; jest.mock("@/hooks", () => ({ useContent: () => ({ diff --git a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/KitNotAvailableInAreaPage.test.tsx b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/KitNotAvailableInAreaPage.test.tsx index 519ba233a..7931197a3 100644 --- a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/KitNotAvailableInAreaPage.test.tsx +++ b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/KitNotAvailableInAreaPage.test.tsx @@ -1,9 +1,8 @@ import "@testing-library/jest-dom"; - import { fireEvent, render, screen } from "@testing-library/react"; -import KitNotAvailableInAreaPage from "@/routes/get-self-test-kit-for-HIV-journey/KitNotAvailableInAreaPage"; import { usePageContent } from "@/hooks"; +import KitNotAvailableInAreaPage from "@/routes/get-self-test-kit-for-HIV-journey/KitNotAvailableInAreaPage"; const mockGoToStep = jest.fn(); const mockGoBack = jest.fn(); diff --git a/ui/src/app/[[...slug]]/page.tsx b/ui/src/app/[[...slug]]/page.tsx index 0357d2222..0d9273913 100644 --- a/ui/src/app/[[...slug]]/page.tsx +++ b/ui/src/app/[[...slug]]/page.tsx @@ -1,7 +1,8 @@ -import { ClientOnly } from "./client"; -import { DEFAULT_PAGE_TITLE } from "../../lib/utils/page-title"; import type { Metadata } from "next"; +import { DEFAULT_PAGE_TITLE } from "../../lib/utils/page-title"; +import { ClientOnly } from "./client"; + export const metadata: Metadata = { title: DEFAULT_PAGE_TITLE, }; diff --git a/ui/src/components/ErrorRedirect.tsx b/ui/src/components/ErrorRedirect.tsx index 1388d1ad3..c46d5c215 100644 --- a/ui/src/components/ErrorRedirect.tsx +++ b/ui/src/components/ErrorRedirect.tsx @@ -1,5 +1,6 @@ import * as React from "react"; import { isRouteErrorResponse, useNavigate, useRouteError } from "react-router-dom"; + import { RoutePath } from "@/lib/models/route-paths"; function getErrorMessage(error: unknown): string { diff --git a/ui/src/components/FindAnotherSexualHealthClinicLink.tsx b/ui/src/components/FindAnotherSexualHealthClinicLink.tsx index dddc10dd4..dc51c223c 100644 --- a/ui/src/components/FindAnotherSexualHealthClinicLink.tsx +++ b/ui/src/components/FindAnotherSexualHealthClinicLink.tsx @@ -1,4 +1,5 @@ import { ActionLink } from "nhsuk-react-components"; + import { useCommonContent } from "@/hooks"; interface FindAnotherSexualHealthClinicLinkProps { diff --git a/ui/src/components/NearestSexualHealthClinicSection.tsx b/ui/src/components/NearestSexualHealthClinicSection.tsx index 2fb0b6929..c27a3f28e 100644 --- a/ui/src/components/NearestSexualHealthClinicSection.tsx +++ b/ui/src/components/NearestSexualHealthClinicSection.tsx @@ -1,7 +1,9 @@ import { OpensInNewTabLink } from "@/components/OpensInNewTabLink"; // stub, will be replaced later -export function NearestSexualHealthClinicSection({ showTitle = true }: Readonly<{ showTitle?: boolean }>) { +export function NearestSexualHealthClinicSection({ + showTitle = true, +}: Readonly<{ showTitle?: boolean }>) { return ( <> {showTitle &&

Contact your nearest sexual health clinic

} diff --git a/ui/src/layouts/JourneyLayout.tsx b/ui/src/layouts/JourneyLayout.tsx index 923c6084a..741d76a25 100644 --- a/ui/src/layouts/JourneyLayout.tsx +++ b/ui/src/layouts/JourneyLayout.tsx @@ -1,7 +1,7 @@ -import { CreateOrderProvider, JourneyNavigationProvider, PostcodeLookupProvider } from "@/state"; +import { Outlet } from "react-router-dom"; import { JourneyDevtools } from "@/lib/utils/JourneyDevtools"; -import { Outlet } from "react-router-dom"; +import { CreateOrderProvider, JourneyNavigationProvider, PostcodeLookupProvider } from "@/state"; export default function JourneyLayout() { return ( diff --git a/ui/src/layouts/MainLayout.tsx b/ui/src/layouts/MainLayout.tsx index 08663e23b..5939fe208 100644 --- a/ui/src/layouts/MainLayout.tsx +++ b/ui/src/layouts/MainLayout.tsx @@ -1,13 +1,14 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { Container, Footer, Header } from "nhsuk-react-components"; +import type React from "react"; import { Outlet, ScrollRestoration } from "react-router-dom"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { AuthLoader } from "@/lib/auth/AuthLoader"; import { AppDevtools } from "@/lib/utils/AppDevtools"; import { AuthProvider } from "@/state"; -import { DEFAULT_PAGE_TITLE } from "../lib/utils/page-title"; -import type React from "react"; + import { RoutePath } from "../lib/models/route-paths"; +import { DEFAULT_PAGE_TITLE } from "../lib/utils/page-title"; // it will be improved in future const isNhsApp = true; diff --git a/ui/src/lib/mappers/order-details-mapper.ts b/ui/src/lib/mappers/order-details-mapper.ts index 1a9e5ad2c..f683856ca 100644 --- a/ui/src/lib/mappers/order-details-mapper.ts +++ b/ui/src/lib/mappers/order-details-mapper.ts @@ -1,4 +1,5 @@ import { Bundle, ServiceRequest } from "fhir/r4"; + import { OrderDetails, OrderStatus } from "@/lib/models/order-details"; import { FhirConstants } from "../utils/fhir-constants"; diff --git a/ui/src/lib/queries/test-results-query.ts b/ui/src/lib/queries/test-results-query.ts index 3b3a2201f..aad8075e7 100644 --- a/ui/src/lib/queries/test-results-query.ts +++ b/ui/src/lib/queries/test-results-query.ts @@ -1,7 +1,8 @@ +import { useQuery } from "@tanstack/react-query"; + import { Patient } from "@/lib/models/patient"; import { queryKeyNames } from "@/lib/queries/query-keys"; import testResultsService from "@/lib/services/test-results-service"; -import { useQuery } from "@tanstack/react-query"; export function useTestResultsQuery(orderId: string, patient: Patient) { return useQuery({ diff --git a/ui/src/lib/services/order-details-service.ts b/ui/src/lib/services/order-details-service.ts index a103ad009..8e4921172 100644 --- a/ui/src/lib/services/order-details-service.ts +++ b/ui/src/lib/services/order-details-service.ts @@ -1,7 +1,7 @@ import { Bundle, OperationOutcome } from "fhir/r4"; -import { OrderDetails } from "@/lib/models/order-details"; import { OrderDetailsMapper } from "@/lib/mappers/order-details-mapper"; +import { OrderDetails } from "@/lib/models/order-details"; import { Patient } from "@/lib/models/patient"; import { backendUrl } from "@/settings"; @@ -17,10 +17,7 @@ class OrderDetailsService { } const errorMessage = - issue?.details?.text ?? - issue?.diagnostics ?? - issue?.code ?? - "Unknown error"; + issue?.details?.text ?? issue?.diagnostics ?? issue?.code ?? "Unknown error"; if (errorMessage === "") throw new Error(errorMessage); } @@ -28,10 +25,7 @@ class OrderDetailsService { return OrderDetailsMapper.mapBundleToOrderDetails(bundle); } - private async getOrderFromApi( - orderId: string, - patient: Patient, - ): Promise { + private async getOrderFromApi(orderId: string, patient: Patient): Promise { const url = new URL(`${backendUrl}/get-order`); url.searchParams.append("nhs_number", patient.nhsNumber); url.searchParams.append("date_of_birth", patient.dateOfBirth); diff --git a/ui/src/lib/services/order-service.ts b/ui/src/lib/services/order-service.ts index ccc207f29..e9d0460b5 100644 --- a/ui/src/lib/services/order-service.ts +++ b/ui/src/lib/services/order-service.ts @@ -20,8 +20,8 @@ export interface OrderServicePatient { city?: string; postalCode: string; country?: string; - use?: 'home' | 'work' | 'temp' | 'old' | 'billing'; - type?: 'postal' | 'physical' | 'both'; + use?: "home" | "work" | "temp" | "old" | "billing"; + type?: "postal" | "physical" | "both"; }; birthDate: string; nhsNumber: string; @@ -61,9 +61,7 @@ class OrderService { if (!response.ok) { const errorData = await response.json().catch(() => ({})); - throw new Error( - errorData.message || `Failed to submit order: ${response.status}` - ); + throw new Error(errorData.message || `Failed to submit order: ${response.status}`); } return response.json(); diff --git a/ui/src/lib/services/test-results-service.ts b/ui/src/lib/services/test-results-service.ts index 979a54434..34573861e 100644 --- a/ui/src/lib/services/test-results-service.ts +++ b/ui/src/lib/services/test-results-service.ts @@ -1,8 +1,8 @@ import { Observation, OperationOutcome } from "fhir/r4"; -import { FhirUtils } from "@/lib/utils/fhir-utils"; import { Patient } from "@/lib/models/patient"; import { TestResult } from "@/lib/models/test-result"; +import { FhirUtils } from "@/lib/utils/fhir-utils"; import { backendUrl } from "@/settings"; class TestResultsService { @@ -17,10 +17,7 @@ class TestResultsService { const operationOutcome: OperationOutcome = await response.json(); const issue = operationOutcome.issue?.[0]; const errorMessage = - issue?.details?.text ?? - issue?.diagnostics ?? - issue?.code ?? - "Unknown error"; + issue?.details?.text ?? issue?.diagnostics ?? issue?.code ?? "Unknown error"; throw new Error(errorMessage); } @@ -33,10 +30,7 @@ class TestResultsService { }; } - private async getResultFromApi( - orderId: string, - patient: Patient, - ): Promise { + private async getResultFromApi(orderId: string, patient: Patient): Promise { const url = new URL(`${backendUrl}/results`); url.searchParams.append("nhs_number", patient.nhsNumber); url.searchParams.append("date_of_birth", patient.dateOfBirth); diff --git a/ui/src/lib/utils/AppDevtools.tsx b/ui/src/lib/utils/AppDevtools.tsx index 62a8cfb93..0f2c18b35 100644 --- a/ui/src/lib/utils/AppDevtools.tsx +++ b/ui/src/lib/utils/AppDevtools.tsx @@ -1,7 +1,9 @@ "use client"; import { useEffect } from "react"; + import { useAuth } from "@/state"; + import { registerDebugState, unregisterDebugState } from "./debug"; function AppDevtoolsInner() { diff --git a/ui/src/lib/utils/JourneyDevtools.tsx b/ui/src/lib/utils/JourneyDevtools.tsx index b866fb239..d886c5e28 100644 --- a/ui/src/lib/utils/JourneyDevtools.tsx +++ b/ui/src/lib/utils/JourneyDevtools.tsx @@ -1,7 +1,9 @@ "use client"; import { useEffect } from "react"; + import { useCreateOrderContext, useJourneyNavigationContext, usePostcodeLookup } from "@/state"; + import { registerDebugState, unregisterDebugState } from "./debug"; function JourneyDevtoolsInner() { diff --git a/ui/src/lib/utils/debug.ts b/ui/src/lib/utils/debug.ts index e508a3671..2bd965404 100644 --- a/ui/src/lib/utils/debug.ts +++ b/ui/src/lib/utils/debug.ts @@ -3,7 +3,7 @@ declare global { } export function registerDebugState(slice: string, getValue: () => unknown) { - if (process.env.NODE_ENV !== 'development') return; + if (process.env.NODE_ENV !== "development") return; globalThis.__appDebug ??= {}; Object.defineProperty(globalThis.__appDebug, slice, { @@ -14,7 +14,7 @@ export function registerDebugState(slice: string, getValue: () => unknown) { } export function unregisterDebugState(slice: string) { - if (process.env.NODE_ENV !== 'development') return; + if (process.env.NODE_ENV !== "development") return; if (globalThis.__appDebug) { delete globalThis.__appDebug[slice]; diff --git a/ui/src/lib/utils/fhir-utils.ts b/ui/src/lib/utils/fhir-utils.ts index 639b49d3a..6af8c6633 100644 --- a/ui/src/lib/utils/fhir-utils.ts +++ b/ui/src/lib/utils/fhir-utils.ts @@ -16,29 +16,19 @@ export class FhirUtils { resourceType: T["resourceType"], ): T | null { return ( - (bundle.entry?.find((e) => e.resource?.resourceType === resourceType) - ?.resource as T) ?? null + (bundle.entry?.find((e) => e.resource?.resourceType === resourceType)?.resource as T) ?? null ); } - static findExtension( - resource: { extension?: Extension[] }, - url: string, - ): Extension | null { + static findExtension(resource: { extension?: Extension[] }, url: string): Extension | null { return resource.extension?.find((ext) => ext.url === url) ?? null; } - static findCoding( - concept: CodeableConcept | undefined, - system: string, - ): Coding | null { + static findCoding(concept: CodeableConcept | undefined, system: string): Coding | null { return concept?.coding?.find((c) => c.system === system) ?? null; } - static findSubExtension( - ext: Extension | null, - url: string, - ): Extension | null { + static findSubExtension(ext: Extension | null, url: string): Extension | null { return ext?.extension?.find((e) => e.url === url) ?? null; } diff --git a/ui/src/routes/CallbackPage.tsx b/ui/src/routes/CallbackPage.tsx index 25addc74e..4d854007f 100644 --- a/ui/src/routes/CallbackPage.tsx +++ b/ui/src/routes/CallbackPage.tsx @@ -1,14 +1,14 @@ "use client"; -import { useAuth } from "@/state"; -import { mapAuthUser } from "@/lib/auth/mapAuthUser"; -import { consumeLoginCsrf, verifyState } from "@/lib/auth/loginState"; import { useEffect, useRef } from "react"; +import { useNavigate } from "react-router-dom"; +import { useAsyncErrorHandler } from "@/hooks"; +import { consumeLoginCsrf, verifyState } from "@/lib/auth/loginState"; +import { mapAuthUser } from "@/lib/auth/mapAuthUser"; import { RoutePath } from "@/lib/models/route-paths"; import { backendUrl } from "@/settings"; -import { useNavigate } from "react-router-dom"; -import { useAsyncErrorHandler } from "@/hooks"; +import { useAuth } from "@/state"; function safeReturnTo(value: string | null | undefined) { if (!value) return null; diff --git a/ui/src/routes/ServiceErrorPage.tsx b/ui/src/routes/ServiceErrorPage.tsx index 14d92c3b7..64ebeaac4 100644 --- a/ui/src/routes/ServiceErrorPage.tsx +++ b/ui/src/routes/ServiceErrorPage.tsx @@ -1,7 +1,8 @@ -import PageLayout from "@/layouts/PageLayout"; -import { useContent } from "@/hooks"; import { useLocation } from "react-router-dom"; +import { useContent } from "@/hooks"; +import PageLayout from "@/layouts/PageLayout"; + export default function ServiceErrorPage() { const { "service-error": content } = useContent(); const { state } = useLocation(); diff --git a/ui/src/routes/SuppliersPrivacyPolicyPage.tsx b/ui/src/routes/SuppliersPrivacyPolicyPage.tsx index d491f7b62..5d0ea3fe3 100644 --- a/ui/src/routes/SuppliersPrivacyPolicyPage.tsx +++ b/ui/src/routes/SuppliersPrivacyPolicyPage.tsx @@ -1,7 +1,7 @@ import { useNavigate, useSearchParams } from "react-router-dom"; -import PageLayout from "@/layouts/PageLayout"; import { SupplierLegalDocumentContent } from "@/components/SupplierLegalDocumentContent"; +import PageLayout from "@/layouts/PageLayout"; export default function SuppliersPrivacyPolicyPage() { const navigate = useNavigate(); diff --git a/ui/src/routes/SuppliersTermsConditionsPage.tsx b/ui/src/routes/SuppliersTermsConditionsPage.tsx index 57889aecf..fc8c15fb7 100644 --- a/ui/src/routes/SuppliersTermsConditionsPage.tsx +++ b/ui/src/routes/SuppliersTermsConditionsPage.tsx @@ -1,7 +1,7 @@ import { useNavigate, useSearchParams } from "react-router-dom"; -import PageLayout from "@/layouts/PageLayout"; import { SupplierLegalDocumentContent } from "@/components/SupplierLegalDocumentContent"; +import PageLayout from "@/layouts/PageLayout"; export default function SuppliersTermsConditionsPage() { const navigate = useNavigate(); diff --git a/ui/src/routes/TestResultsPage.tsx b/ui/src/routes/TestResultsPage.tsx index 9c0a7dad7..ebc3f3956 100644 --- a/ui/src/routes/TestResultsPage.tsx +++ b/ui/src/routes/TestResultsPage.tsx @@ -1,16 +1,16 @@ -import { OrderDetails, OrderStatus } from "@/lib/models/order-details"; +import { useEffect } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { NegativeTestResult } from "@/components/test-results/NegativeTestResult"; +import { usePageContent } from "@/hooks"; import PageLayout from "@/layouts/PageLayout"; +import { OrderDetails, OrderStatus } from "@/lib/models/order-details"; import { Patient } from "@/lib/models/patient"; import { RoutePath } from "@/lib/models/route-paths"; -import { isValidGuid } from "@/lib/utils/guid"; -import { useAuth } from "@/state"; -import { useEffect } from "react"; import { useOrderStatusQuery } from "@/lib/queries/order-status-query"; -import { usePageContent } from "@/hooks"; import { useTestResultsQuery } from "@/lib/queries/test-results-query"; +import { isValidGuid } from "@/lib/utils/guid"; +import { useAuth } from "@/state"; function TestResultsContent({ orderId, diff --git a/ui/src/routes/get-self-test-kit-for-HIV-journey/BloodSampleGuidePage.tsx b/ui/src/routes/get-self-test-kit-for-HIV-journey/BloodSampleGuidePage.tsx index 8dcbe4045..f918e4834 100644 --- a/ui/src/routes/get-self-test-kit-for-HIV-journey/BloodSampleGuidePage.tsx +++ b/ui/src/routes/get-self-test-kit-for-HIV-journey/BloodSampleGuidePage.tsx @@ -2,9 +2,9 @@ import { Details, Images } from "nhsuk-react-components"; +import { useContent } from "@/hooks"; import FormPageLayout from "@/layouts/FormPageLayout"; import { RoutePath } from "@/lib/models/route-paths"; -import { useContent } from "@/hooks"; import { useJourneyNavigationContext } from "@/state"; export default function BloodSampleGuidePage() { diff --git a/ui/src/routes/get-self-test-kit-for-HIV-journey/FormSuppliersPrivacyPolicyPage.tsx b/ui/src/routes/get-self-test-kit-for-HIV-journey/FormSuppliersPrivacyPolicyPage.tsx index ae1afc47d..23548d858 100644 --- a/ui/src/routes/get-self-test-kit-for-HIV-journey/FormSuppliersPrivacyPolicyPage.tsx +++ b/ui/src/routes/get-self-test-kit-for-HIV-journey/FormSuppliersPrivacyPolicyPage.tsx @@ -1,8 +1,7 @@ -import { useCreateOrderContext, useJourneyNavigationContext } from "@/state"; - +import { SupplierLegalDocumentContent } from "@/components/SupplierLegalDocumentContent"; import FormPageLayout from "@/layouts/FormPageLayout"; import { JourneyStepNames } from "@/lib/models/route-paths"; -import { SupplierLegalDocumentContent } from "@/components/SupplierLegalDocumentContent"; +import { useCreateOrderContext, useJourneyNavigationContext } from "@/state"; export default function FormSuppliersPrivacyPolicyPage() { const { goToStep, goBack, stepHistory } = useJourneyNavigationContext(); diff --git a/ui/src/routes/get-self-test-kit-for-HIV-journey/FormSuppliersTermsConditionsPage.tsx b/ui/src/routes/get-self-test-kit-for-HIV-journey/FormSuppliersTermsConditionsPage.tsx index eaaf41762..1bbbed23d 100644 --- a/ui/src/routes/get-self-test-kit-for-HIV-journey/FormSuppliersTermsConditionsPage.tsx +++ b/ui/src/routes/get-self-test-kit-for-HIV-journey/FormSuppliersTermsConditionsPage.tsx @@ -1,8 +1,7 @@ -import { useCreateOrderContext, useJourneyNavigationContext } from "@/state"; - +import { SupplierLegalDocumentContent } from "@/components/SupplierLegalDocumentContent"; import FormPageLayout from "@/layouts/FormPageLayout"; import { JourneyStepNames } from "@/lib/models/route-paths"; -import { SupplierLegalDocumentContent } from "@/components/SupplierLegalDocumentContent"; +import { useCreateOrderContext, useJourneyNavigationContext } from "@/state"; export default function FormSuppliersTermsConditionsPage() { const { goToStep, goBack, stepHistory } = useJourneyNavigationContext(); diff --git a/ui/src/routes/get-self-test-kit-for-HIV-journey/GoToClinicPage.tsx b/ui/src/routes/get-self-test-kit-for-HIV-journey/GoToClinicPage.tsx index b9e64d2f0..b87adbdc1 100644 --- a/ui/src/routes/get-self-test-kit-for-HIV-journey/GoToClinicPage.tsx +++ b/ui/src/routes/get-self-test-kit-for-HIV-journey/GoToClinicPage.tsx @@ -1,14 +1,13 @@ "use client"; -import { useCreateOrderContext, useJourneyNavigationContext } from "@/state"; - import { FeedbackSection } from "@/components/FeedbackSection"; import { FindAnotherSexualHealthClinicLink } from "@/components/FindAnotherSexualHealthClinicLink"; -import { JourneyStepNames } from "@/lib/models/route-paths"; import { LearnMoreAboutHivAndAidsLink } from "@/components/LearnMoreAboutHivAndAidsLink"; import { NearestSexualHealthClinicSection } from "@/components/NearestSexualHealthClinicSection"; import { usePageContent } from "@/hooks"; import FormPageLayout from "@/layouts/FormPageLayout"; +import { JourneyStepNames } from "@/lib/models/route-paths"; +import { useCreateOrderContext, useJourneyNavigationContext } from "@/state"; export default function GoToClinicPage() { const { goToStep, goBack, stepHistory } = useJourneyNavigationContext(); @@ -28,9 +27,7 @@ export default function GoToClinicPage() { >

{content.title}

- +

{content.moreOptionsHeading}

diff --git a/ui/src/routes/get-self-test-kit-for-HIV-journey/HowComfortablePrickingFingerPage.tsx b/ui/src/routes/get-self-test-kit-for-HIV-journey/HowComfortablePrickingFingerPage.tsx index c8ebf6616..635312aa3 100644 --- a/ui/src/routes/get-self-test-kit-for-HIV-journey/HowComfortablePrickingFingerPage.tsx +++ b/ui/src/routes/get-self-test-kit-for-HIV-journey/HowComfortablePrickingFingerPage.tsx @@ -1,12 +1,12 @@ "use client"; import { Button, ErrorSummary, Images, Radios } from "nhsuk-react-components"; -import { useAuth, useCreateOrderContext, useJourneyNavigationContext } from "@/state"; +import { useState } from "react"; +import { useContent } from "@/hooks"; import FormPageLayout from "@/layouts/FormPageLayout"; import { JourneyStepNames } from "@/lib/models/route-paths"; -import { useContent } from "@/hooks"; -import { useState } from "react"; +import { useAuth, useCreateOrderContext, useJourneyNavigationContext } from "@/state"; export default function HowComfortablePrickingFingerPage() { const { goToStep, goBack, stepHistory, returnToStep, setReturnToStep } = diff --git a/ui/src/routes/get-self-test-kit-for-HIV-journey/KitNotAvailableInAreaPage.tsx b/ui/src/routes/get-self-test-kit-for-HIV-journey/KitNotAvailableInAreaPage.tsx index 080401834..2da7c04cf 100644 --- a/ui/src/routes/get-self-test-kit-for-HIV-journey/KitNotAvailableInAreaPage.tsx +++ b/ui/src/routes/get-self-test-kit-for-HIV-journey/KitNotAvailableInAreaPage.tsx @@ -1,14 +1,13 @@ "use client"; -import { useCreateOrderContext, useJourneyNavigationContext } from "@/state"; - import { FeedbackSection } from "@/components/FeedbackSection"; import { FindAnotherSexualHealthClinicLink } from "@/components/FindAnotherSexualHealthClinicLink"; -import FormPageLayout from "@/layouts/FormPageLayout"; -import { JourneyStepNames } from "@/lib/models/route-paths"; import { LearnMoreAboutHivAndAidsLink } from "@/components/LearnMoreAboutHivAndAidsLink"; import { NearestSexualHealthClinicSection } from "@/components/NearestSexualHealthClinicSection"; import { usePageContent } from "@/hooks"; +import FormPageLayout from "@/layouts/FormPageLayout"; +import { JourneyStepNames } from "@/lib/models/route-paths"; +import { useCreateOrderContext, useJourneyNavigationContext } from "@/state"; export default function KitNotAvailableInAreaPage() { const { goToStep, goBack, stepHistory } = useJourneyNavigationContext(); diff --git a/ui/src/routes/get-self-test-kit-for-HIV-journey/NoAddressFoundPage.tsx b/ui/src/routes/get-self-test-kit-for-HIV-journey/NoAddressFoundPage.tsx index 72053ba04..cf54c23d6 100644 --- a/ui/src/routes/get-self-test-kit-for-HIV-journey/NoAddressFoundPage.tsx +++ b/ui/src/routes/get-self-test-kit-for-HIV-journey/NoAddressFoundPage.tsx @@ -1,10 +1,9 @@ "use client"; -import { useCreateOrderContext, useJourneyNavigationContext } from "@/state"; - +import { useContent } from "@/hooks"; import FormPageLayout from "@/layouts/FormPageLayout"; import { JourneyStepNames } from "@/lib/models/route-paths"; -import { useContent } from "@/hooks"; +import { useCreateOrderContext, useJourneyNavigationContext } from "@/state"; export default function NoAddressFoundPage() { const { goToStep, goBack, stepHistory } = useJourneyNavigationContext(); diff --git a/ui/src/state/index.ts b/ui/src/state/index.ts index 8950c83d2..7d52978fe 100644 --- a/ui/src/state/index.ts +++ b/ui/src/state/index.ts @@ -1,4 +1,4 @@ export * from "./AuthContext"; export * from "./OrderContext"; export * from "./NavigationContext"; -export * from './PostcodeLookupContext'; +export * from "./PostcodeLookupContext"; From 4f4874104d9ee5426904d35fa8b45b1cf028abf8 Mon Sep 17 00:00:00 2001 From: Mikolaj Miotk Date: Thu, 16 Apr 2026 15:59:27 +0200 Subject: [PATCH 2/2] Fix tests --- lambdas/src/lib/auth/auth-token-service.test.ts | 13 ++++++++----- .../src/lib/auth/auth-token-verifier.test.ts | 17 ++++++++++------- .../src/lib/login/nhs-login-jwt-helper.test.ts | 13 ++++++++----- lambdas/src/lib/login/token-service.test.ts | 12 +++++++----- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/lambdas/src/lib/auth/auth-token-service.test.ts b/lambdas/src/lib/auth/auth-token-service.test.ts index d62b36fa8..e1f444bf9 100644 --- a/lambdas/src/lib/auth/auth-token-service.test.ts +++ b/lambdas/src/lib/auth/auth-token-service.test.ts @@ -1,19 +1,22 @@ -import { AuthTokenService } from "./auth-token-service"; +import jwt from "jsonwebtoken"; -const mockSign = jest.fn(); -const mockCleanupKey = jest.fn(); +import { AuthTokenService } from "./auth-token-service"; +import { cleanupKey } from "./auth-utils"; jest.mock("jsonwebtoken", () => ({ __esModule: true, default: { - sign: mockSign, + sign: jest.fn(), }, })); jest.mock("./auth-utils", () => ({ - cleanupKey: mockCleanupKey, + cleanupKey: jest.fn(), })); +const mockSign = jwt.sign as jest.Mock; +const mockCleanupKey = cleanupKey as jest.Mock; + describe("AuthTokenService", () => { const authConfig = { keyId: "test-key-id", diff --git a/lambdas/src/lib/auth/auth-token-verifier.test.ts b/lambdas/src/lib/auth/auth-token-verifier.test.ts index 7bef0ed34..b06b52986 100644 --- a/lambdas/src/lib/auth/auth-token-verifier.test.ts +++ b/lambdas/src/lib/auth/auth-token-verifier.test.ts @@ -1,21 +1,24 @@ -import { AuthTokenVerifier } from "./auth-token-verifier"; +import jwt from "jsonwebtoken"; -const mockDecode = jest.fn(); -const mockVerify = jest.fn(); -const mockCleanupKey = jest.fn(); +import { AuthTokenVerifier } from "./auth-token-verifier"; +import { cleanupKey } from "./auth-utils"; jest.mock("jsonwebtoken", () => ({ __esModule: true, default: { - decode: mockDecode, - verify: mockVerify, + decode: jest.fn(), + verify: jest.fn(), }, })); jest.mock("./auth-utils", () => ({ - cleanupKey: mockCleanupKey, + cleanupKey: jest.fn(), })); +const mockDecode = jwt.decode as jest.Mock; +const mockVerify = jwt.verify as jest.Mock; +const mockCleanupKey = cleanupKey as jest.Mock; + describe("AuthTokenVerifier", () => { const authConfig = { keyId: "default-key-id", diff --git a/lambdas/src/lib/login/nhs-login-jwt-helper.test.ts b/lambdas/src/lib/login/nhs-login-jwt-helper.test.ts index 8ca3aad9e..f5e97b079 100644 --- a/lambdas/src/lib/login/nhs-login-jwt-helper.test.ts +++ b/lambdas/src/lib/login/nhs-login-jwt-helper.test.ts @@ -1,19 +1,22 @@ -import { NhsLoginJwtHelper } from "./nhs-login-jwt-helper"; +import jwt from "jsonwebtoken"; +import { v4 as uuidv4 } from "uuid"; -const mockSign = jest.fn(); -const mockUuid = jest.fn(); +import { NhsLoginJwtHelper } from "./nhs-login-jwt-helper"; jest.mock("jsonwebtoken", () => ({ __esModule: true, default: { - sign: mockSign, + sign: jest.fn(), }, })); jest.mock("uuid", () => ({ - v4: mockUuid, + v4: jest.fn(), })); +const mockSign = jwt.sign as jest.Mock; +const mockUuid = uuidv4 as jest.Mock; + describe("NhsLoginJwtHelper", () => { const nhsLoginConfig = { clientId: "client-id", diff --git a/lambdas/src/lib/login/token-service.test.ts b/lambdas/src/lib/login/token-service.test.ts index 47104c5d1..be522f262 100644 --- a/lambdas/src/lib/login/token-service.test.ts +++ b/lambdas/src/lib/login/token-service.test.ts @@ -1,18 +1,20 @@ +import jwt from "jsonwebtoken"; + import { type INhsLoginConfig } from "../models/nhs-login/nhs-login-config"; import { type INhsLoginClient } from "./nhs-login-client"; import { TokenService } from "./token-service"; -const mockDecode = jest.fn(); -const mockVerify = jest.fn(); - jest.mock("jsonwebtoken", () => ({ __esModule: true, default: { - decode: mockDecode, - verify: mockVerify, + decode: jest.fn(), + verify: jest.fn(), }, })); +const mockDecode = jwt.decode as jest.Mock; +const mockVerify = jwt.verify as jest.Mock; + describe("TokenService", () => { const nhsLoginConfig: INhsLoginConfig = { clientId: "client-id",