From 2cdd0dede07399cf6f8d6f52aa2f5efbcf2759f3 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Wed, 10 Apr 2024 09:19:06 +0100
Subject: [PATCH 01/56] feat(DTFS2-7052): new API modules and endpoint
 /api/v1/geospatial/addresses/postcode?postcode=W1A1AA

---
 .cspell.json                                  |  14 +-
 .env.sample                                   |   6 +
 src/config/index.ts                           |   3 +-
 src/config/ordnance-survey.config.test.ts     |  39 ++++++
 src/config/ordnance-survey.config.ts          |  21 +++
 .../dto/get-addresses-response.dto.ts         |  90 ++++++++++++
 .../dto/get-search-postcode-query.dto.ts      |   3 +
 ...-search-places-v1-postcode-no-results.json |  15 ++
 ...esponse-for-search-places-v1-postcode.json |  53 ++++++++
 .../ordnance-survey.exception.test.ts         |  28 ++++
 .../exception/ordnance-survey.exception.ts    |   9 ++
 .../ordnance-survey/known-errors.ts           |  13 ++
 .../ordnance-survey/ordnance-survey.module.ts |  26 ++++
 .../ordnance-survey.service.test.ts           | 128 ++++++++++++++++++
 .../ordnance-survey.service.ts                |  35 +++++
 ...rap-ordnance-survey-http-error-callback.ts |  32 +++++
 16 files changed, 513 insertions(+), 2 deletions(-)
 create mode 100644 src/config/ordnance-survey.config.test.ts
 create mode 100644 src/config/ordnance-survey.config.ts
 create mode 100644 src/helper_modules/ordnance-survey/dto/get-addresses-response.dto.ts
 create mode 100644 src/helper_modules/ordnance-survey/dto/get-search-postcode-query.dto.ts
 create mode 100644 src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode-no-results.json
 create mode 100644 src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode.json
 create mode 100644 src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.test.ts
 create mode 100644 src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.ts
 create mode 100644 src/helper_modules/ordnance-survey/known-errors.ts
 create mode 100644 src/helper_modules/ordnance-survey/ordnance-survey.module.ts
 create mode 100644 src/helper_modules/ordnance-survey/ordnance-survey.service.test.ts
 create mode 100644 src/helper_modules/ordnance-survey/ordnance-survey.service.ts
 create mode 100644 src/helper_modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts

diff --git a/.cspell.json b/.cspell.json
index c2e875a8..0a7de18b 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -51,7 +51,19 @@
     "ukef",
     "venv",
     "VNET",
-    "CICD"
+    "CICD",
+    "DPA",
+    "UPRN",
+    "UDPRN",
+    "BLPU",
+    "TOID",
+    "EPSG",
+    "WOGAN",
+    "osgb",
+    "HJLNP",
+    "Zabd",
+    "hjlnp",
+    "BLPUs"
   ],
   "dictionaries": [
     "en-gb",
diff --git a/.env.sample b/.env.sample
index 673bc1e0..68141bee 100644
--- a/.env.sample
+++ b/.env.sample
@@ -39,3 +39,9 @@ APIM_INFORMATICA_USERNAME=
 APIM_INFORMATICA_PASSWORD=
 APIM_INFORMATICA_MAX_REDIRECTS=
 APIM_INFORMATICA_TIMEOUT=
+
+# ORDANANCE SURVEY
+ORDNANCE_SURVEY_URL=
+ORDNANCE_SURVEY_KEY=
+ORDNANCE_SURVEY_MAX_REDIRECTS=
+ORDNANCE_SURVEY_TIMEOUT=
diff --git a/src/config/index.ts b/src/config/index.ts
index 4e89f16d..29fe1c91 100644
--- a/src/config/index.ts
+++ b/src/config/index.ts
@@ -2,5 +2,6 @@ import AppConfig from './app.config';
 import DatabaseConfig from './database.config';
 import DocConfig from './doc.config';
 import InformaticaConfig from './informatica.config';
+import OrdnanceSurveyConfig from './ordnance-survey.config';
 
-export default [AppConfig, DocConfig, DatabaseConfig, InformaticaConfig];
+export default [AppConfig, DocConfig, DatabaseConfig, InformaticaConfig, OrdnanceSurveyConfig];
diff --git a/src/config/ordnance-survey.config.test.ts b/src/config/ordnance-survey.config.test.ts
new file mode 100644
index 00000000..5b22020e
--- /dev/null
+++ b/src/config/ordnance-survey.config.test.ts
@@ -0,0 +1,39 @@
+import { withEnvironmentVariableParsingUnitTests } from '@ukef-test/common-tests/environment-variable-parsing-unit-tests';
+
+import ordnanceSurveyConfig, { OrdnanceSurveyConfig } from './ordnance-survey.config';
+
+describe('ordnanceSurveyConfig', () => {
+  const configDirectlyFromEnvironmentVariables: { configPropertyName: keyof OrdnanceSurveyConfig; environmentVariableName: string }[] = [
+    {
+      configPropertyName: 'baseUrl',
+      environmentVariableName: 'ORDNANCE_SURVEY_URL',
+    },
+    {
+      configPropertyName: 'key',
+      environmentVariableName: 'ORDNANCE_SURVEY_KEY',
+    },
+  ];
+
+  const configParsedAsIntFromEnvironmentVariablesWithDefault: {
+    configPropertyName: keyof OrdnanceSurveyConfig;
+    environmentVariableName: string;
+    defaultConfigValue: number;
+  }[] = [
+    {
+      configPropertyName: 'maxRedirects',
+      environmentVariableName: 'ORDNANCE_SURVEY_MAX_REDIRECTS',
+      defaultConfigValue: 5,
+    },
+    {
+      configPropertyName: 'timeout',
+      environmentVariableName: 'ORDNANCE_SURVEY_TIMEOUT',
+      defaultConfigValue: 30000,
+    },
+  ];
+
+  withEnvironmentVariableParsingUnitTests({
+    configDirectlyFromEnvironmentVariables,
+    configParsedAsIntFromEnvironmentVariablesWithDefault,
+    getConfig: () => ordnanceSurveyConfig(),
+  });
+});
diff --git a/src/config/ordnance-survey.config.ts b/src/config/ordnance-survey.config.ts
new file mode 100644
index 00000000..e30acf8b
--- /dev/null
+++ b/src/config/ordnance-survey.config.ts
@@ -0,0 +1,21 @@
+import { registerAs } from '@nestjs/config';
+import { getIntConfig } from '@ukef/helpers/get-int-config';
+
+export const KEY = 'ordnanceSurvey';
+
+export interface OrdnanceSurveyConfig {
+  baseUrl: string;
+  key: string;
+  maxRedirects: number;
+  timeout: number;
+}
+
+export default registerAs(
+  KEY,
+  (): OrdnanceSurveyConfig => ({
+    baseUrl: process.env.ORDNANCE_SURVEY_URL,
+    key: process.env.ORDNANCE_SURVEY_KEY,
+    maxRedirects: getIntConfig(process.env.ORDNANCE_SURVEY_MAX_REDIRECTS, 5),
+    timeout: getIntConfig(process.env.ORDNANCE_SURVEY_TIMEOUT, 30000),
+  }),
+);
diff --git a/src/helper_modules/ordnance-survey/dto/get-addresses-response.dto.ts b/src/helper_modules/ordnance-survey/dto/get-addresses-response.dto.ts
new file mode 100644
index 00000000..5a9f6201
--- /dev/null
+++ b/src/helper_modules/ordnance-survey/dto/get-addresses-response.dto.ts
@@ -0,0 +1,90 @@
+export type GetAddressResponse = {
+  header: {
+      uri: string,
+      query: string,
+      offset: number,
+      totalresults: number,
+      format: string,
+      dataset: string,
+      lr: string,
+      maxresults: number,
+      epoch: string,
+      lastupdate: string,
+      output_srs: string
+  },
+  results?: GetAddressResponseItem[],
+};
+
+interface GetAddressResponseItem {
+  DPA: {
+    UPRN: string,
+    UDPRN: string,
+    ADDRESS: string,
+    BUILDING_NAME?: string,
+    BUILDING_NUMBER?: string,
+    ORGANISATION_NAME?: string;
+    DEPENDENT_LOCALITY?: string;
+    THOROUGHFARE_NAME: string,
+    POST_TOWN: string,
+    POSTCODE: string,
+    RPC: string,
+    X_COORDINATE: number,
+    Y_COORDINATE: number,
+    STATUS: string,
+    LOGICAL_STATUS_CODE: string,
+    CLASSIFICATION_CODE: string,
+    CLASSIFICATION_CODE_DESCRIPTION: string,
+    LOCAL_CUSTODIAN_CODE: number,
+    LOCAL_CUSTODIAN_CODE_DESCRIPTION: string,
+    COUNTRY_CODE: string,
+    COUNTRY_CODE_DESCRIPTION: string,
+    POSTAL_ADDRESS_CODE: string,
+    POSTAL_ADDRESS_CODE_DESCRIPTION: string,
+    BLPU_STATE_CODE: string,
+    BLPU_STATE_CODE_DESCRIPTION: string,
+    TOPOGRAPHY_LAYER_TOID: string,
+    LAST_UPDATE_DATE: string,
+    ENTRY_DATE: string,
+    BLPU_STATE_DATE: string,
+    LANGUAGE: string,
+    MATCH: number,
+    MATCH_DESCRIPTION: string,
+    DELIVERY_POINT_SUFFIX: string
+  }
+}
+
+// interface GetAddressResponseAddress {
+//   UPRN: string,
+//   UDPRN: string,
+//   ADDRESS: string,
+//   BUILDING_NAME?: string,
+//   BUILDING_NUMBER?: string,
+//   ORGANISATION_NAME?: string;
+//   DEPENDENT_LOCALITY?: string;
+//   THOROUGHFARE_NAME: string,
+//   POST_TOWN: string,
+//   POSTCODE: string,
+//   RPC: string,
+//   X_COORDINATE: number,
+//   Y_COORDINATE: number,
+//   STATUS: string,
+//   LOGICAL_STATUS_CODE: string,
+//   CLASSIFICATION_CODE: string,
+//   CLASSIFICATION_CODE_DESCRIPTION: string,
+//   LOCAL_CUSTODIAN_CODE: number,
+//   LOCAL_CUSTODIAN_CODE_DESCRIPTION: string,
+//   COUNTRY_CODE: string,
+//   COUNTRY_CODE_DESCRIPTION: string,
+//   POSTAL_ADDRESS_CODE: string,
+//   POSTAL_ADDRESS_CODE_DESCRIPTION: string,
+//   BLPU_STATE_CODE: string,
+//   BLPU_STATE_CODE_DESCRIPTION: string,
+//   TOPOGRAPHY_LAYER_TOID: string,
+//   LAST_UPDATE_DATE: string,
+//   ENTRY_DATE: string,
+//   BLPU_STATE_DATE: string,
+//   LANGUAGE: string,
+//   MATCH: number,
+//   MATCH_DESCRIPTION: string,
+//   DELIVERY_POINT_SUFFIX: string
+// }
diff --git a/src/helper_modules/ordnance-survey/dto/get-search-postcode-query.dto.ts b/src/helper_modules/ordnance-survey/dto/get-search-postcode-query.dto.ts
new file mode 100644
index 00000000..8f8ea0f2
--- /dev/null
+++ b/src/helper_modules/ordnance-survey/dto/get-search-postcode-query.dto.ts
@@ -0,0 +1,3 @@
+export class GetSearchPostcodeOrdnanceSurveyQueryDto {
+  public postcode: string;
+}
diff --git a/src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode-no-results.json b/src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode-no-results.json
new file mode 100644
index 00000000..2223c9e4
--- /dev/null
+++ b/src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode-no-results.json
@@ -0,0 +1,15 @@
+{
+  "header": {
+      "uri": "https://api.os.uk/search/places/v1/postcode?postcode=CV1%2011M",
+      "query": "postcode=CV1 11M",
+      "offset": 0,
+      "totalresults": 0,
+      "format": "JSON",
+      "dataset": "DPA",
+      "lr": "EN,CY",
+      "maxresults": 100,
+      "epoch": "109",
+      "lastupdate": "2024-04-05",
+      "output_srs": "EPSG:27700"
+  }
+}
\ No newline at end of file
diff --git a/src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode.json b/src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode.json
new file mode 100644
index 00000000..335f7c1e
--- /dev/null
+++ b/src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode.json
@@ -0,0 +1,53 @@
+{
+  "header": {
+      "uri": "https://api.os.uk/search/places/v1/postcode?postcode=W1A%201AA",
+      "query": "postcode=W1A 1AA",
+      "offset": 0,
+      "totalresults": 1,
+      "format": "JSON",
+      "dataset": "DPA",
+      "lr": "EN,CY",
+      "maxresults": 100,
+      "epoch": "109",
+      "lastupdate": "2024-04-09",
+      "output_srs": "EPSG:27700"
+  },
+  "results": [
+      {
+          "DPA": {
+              "UPRN": "10092008000",
+              "UDPRN": "25733000",
+              "ADDRESS": "BRITISH BROADCASTING CORPORATION, WOGAN HOUSE, PORTLAND PLACE, LONDON, W1A 1AA",
+              "ORGANISATION_NAME": "BRITISH BROADCASTING CORPORATION",
+              "BUILDING_NAME": "WOGAN HOUSE",
+              "THOROUGHFARE_NAME": "PORTLAND PLACE",
+              "POST_TOWN": "LONDON",
+              "POSTCODE": "W1A 1AA",
+              "RPC": "2",
+              "X_COORDINATE": 528960.0,
+              "Y_COORDINATE": 181680.0,
+              "STATUS": "APPROVED",
+              "LOGICAL_STATUS_CODE": "1",
+              "CLASSIFICATION_CODE": "OR00",
+              "CLASSIFICATION_CODE_DESCRIPTION": "Additional Mail / Packet Addressee",
+              "LOCAL_CUSTODIAN_CODE": 7600,
+              "LOCAL_CUSTODIAN_CODE_DESCRIPTION": "ORDNANCE SURVEY",
+              "COUNTRY_CODE": "E",
+              "COUNTRY_CODE_DESCRIPTION": "This record is within England",
+              "POSTAL_ADDRESS_CODE": "D",
+              "POSTAL_ADDRESS_CODE_DESCRIPTION": "A record which is linked to PAF",
+              "BLPU_STATE_CODE": "2",
+              "BLPU_STATE_CODE_DESCRIPTION": "In use",
+              "TOPOGRAPHY_LAYER_TOID": "osgb1000005200000",
+              "PARENT_UPRN": "100023600000",
+              "LAST_UPDATE_DATE": "29/01/2023",
+              "ENTRY_DATE": "19/01/2012",
+              "BLPU_STATE_DATE": "01/01/2020",
+              "LANGUAGE": "EN",
+              "MATCH": 1.0,
+              "MATCH_DESCRIPTION": "EXACT",
+              "DELIVERY_POINT_SUFFIX": "1A"
+          }
+      }
+  ]
+}
diff --git a/src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.test.ts b/src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.test.ts
new file mode 100644
index 00000000..aa7c8d96
--- /dev/null
+++ b/src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.test.ts
@@ -0,0 +1,28 @@
+import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator';
+
+import { OrdnanceSurveyException } from './ordnance-survey.exception';
+
+describe('OrdnanceSurveyException', () => {
+  const valueGenerator = new RandomValueGenerator();
+  const message = valueGenerator.string();
+
+  it('exposes the message it was created with', () => {
+    const exception = new OrdnanceSurveyException(message);
+
+    expect(exception.message).toBe(message);
+  });
+
+  it('exposes the name of the exception', () => {
+    const exception = new OrdnanceSurveyException(message);
+
+    expect(exception.name).toBe('OrdnanceSurveyException');
+  });
+
+  it('exposes the inner error it was created with', () => {
+    const innerError = new Error();
+
+    const exception = new OrdnanceSurveyException(message, innerError);
+
+    expect(exception.innerError).toBe(innerError);
+  });
+});
diff --git a/src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.ts b/src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.ts
new file mode 100644
index 00000000..64ff9ebe
--- /dev/null
+++ b/src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.ts
@@ -0,0 +1,9 @@
+export class OrdnanceSurveyException extends Error {
+  constructor(
+    message: string,
+    public readonly innerError?: Error,
+  ) {
+    super(message);
+    this.name = this.constructor.name;
+  }
+}
diff --git a/src/helper_modules/ordnance-survey/known-errors.ts b/src/helper_modules/ordnance-survey/known-errors.ts
new file mode 100644
index 00000000..5684be7d
--- /dev/null
+++ b/src/helper_modules/ordnance-survey/known-errors.ts
@@ -0,0 +1,13 @@
+import { NotFoundException } from '@nestjs/common';
+import { AxiosError } from 'axios';
+
+export type KnownErrors = KnownError[];
+
+type KnownError = { caseInsensitiveSubstringToFind: string; throwError: (error: AxiosError) => never };
+
+export const getCustomersNotFoundKnownOrdnanceSurveyError = (): KnownError => ({
+  caseInsensitiveSubstringToFind: 'Company registration not found',
+  throwError: (error) => {
+    throw new NotFoundException('Customer not found.', error);
+  },
+});
diff --git a/src/helper_modules/ordnance-survey/ordnance-survey.module.ts b/src/helper_modules/ordnance-survey/ordnance-survey.module.ts
new file mode 100644
index 00000000..553f756b
--- /dev/null
+++ b/src/helper_modules/ordnance-survey/ordnance-survey.module.ts
@@ -0,0 +1,26 @@
+import { Module } from '@nestjs/common';
+import { ConfigModule, ConfigService } from '@nestjs/config';
+import { OrdnanceSurveyConfig, KEY as ORDNANCE_SURVEY_CONFIG_KEY } from '@ukef/config/ordnance-survey.config';
+import { HttpModule } from '@ukef/modules/http/http.module';
+
+import { OrdnanceSurveyService } from './ordnance-survey.service';
+
+@Module({
+  imports: [
+    HttpModule.registerAsync({
+      imports: [ConfigModule],
+      inject: [ConfigService],
+      useFactory: (configService: ConfigService) => {
+        const { baseUrl, maxRedirects, timeout } = configService.get<OrdnanceSurveyConfig>(ORDNANCE_SURVEY_CONFIG_KEY);
+        return {
+          baseURL: baseUrl,
+          maxRedirects,
+          timeout,
+        };
+      },
+    }),
+  ],
+  providers: [OrdnanceSurveyService],
+  exports: [OrdnanceSurveyService],
+})
+export class OrdnanceSurveyModule {}
diff --git a/src/helper_modules/ordnance-survey/ordnance-survey.service.test.ts b/src/helper_modules/ordnance-survey/ordnance-survey.service.test.ts
new file mode 100644
index 00000000..e895730a
--- /dev/null
+++ b/src/helper_modules/ordnance-survey/ordnance-survey.service.test.ts
@@ -0,0 +1,128 @@
+import { HttpService } from '@nestjs/axios';
+import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator';
+import { AxiosError } from 'axios';
+import { when } from 'jest-when';
+import { of, throwError } from 'rxjs';
+
+import { OrdnanceSurveyException } from './exception/ordnance-survey.exception';
+import { OrdnanceSurveyService } from './ordnance-survey.service';
+import { ConfigService } from '@nestjs/config';
+
+const expectedResponse = require('./examples/example-response-for-search-places-v1-postcode.json');
+const noResultsResponse = require('./examples/example-response-for-search-places-v1-postcode.json');
+
+describe('OrdnanceSurveyService', () => {
+  const valueGenerator = new RandomValueGenerator();
+
+  let httpServiceGet: jest.Mock;
+  let configServiceGet: jest.Mock;
+  let service: OrdnanceSurveyService;
+
+  const testPostcode = 'W1A 1AA';
+  const testKey = valueGenerator.string({length: 10});
+  const basePath = '/search/places/v1/postcode';
+
+  beforeEach(() => {
+    const httpService = new HttpService();
+    const configService = new ConfigService();
+    httpServiceGet = jest.fn();
+    httpService.get = httpServiceGet;
+
+    configServiceGet = jest.fn().mockReturnValue({key: testKey});
+    configService.get = configServiceGet;
+
+    service = new OrdnanceSurveyService(httpService, configService);
+  });
+
+
+
+  describe('getAddressesByPostcode', () => {
+    const expectedPath = `${basePath}?postcode=${testPostcode}&key=${testKey}`;
+
+    const expectedHttpServiceGetArgs: [string, object] = [expectedPath, { headers: { 'Content-Type': 'application/json' } }];
+
+    it('sends a GET to the Ordnance Survey API /search endpoint with the specified request', async () => {
+      when(httpServiceGet)
+        .calledWith(...expectedHttpServiceGetArgs)
+        .mockReturnValueOnce(
+          of({
+            data: expectedResponse,
+            status: 200,
+            statusText: 'OK',
+            config: undefined,
+            headers: undefined,
+          }),
+        );
+
+      await service.getAddressesByPostcode(testPostcode);
+
+      expect(httpServiceGet).toHaveBeenCalledTimes(1);
+      expect(httpServiceGet).toHaveBeenCalledWith(...expectedHttpServiceGetArgs);
+    });
+
+    it.each([
+      {
+        postcode: 'W1A 1AA',
+        expectedUrlQueryPart: `?postcode=W1A%201AA`,
+      },
+      {
+        postcode: 'W1A1AA',
+        expectedUrlQueryPart: '?postcode=W1A1AA',
+      },
+    ])('call Ordnance Survey API with correct and safe query parameters "$expectedUrlQueryPart"', async ({ postcode, expectedUrlQueryPart }) => {
+      // const expectedPath = `${basePath}${expectedUrlQueryPart}&key=${testKey}`;
+
+      // const expectedHttpServiceGetArgs: [string, object] = [expectedPath, { headers: { 'Content-Type': 'application/json' } }];
+
+      when(httpServiceGet)
+        .calledWith(...expectedHttpServiceGetArgs)
+        .mockReturnValueOnce(
+          of({
+            data: expectedResponse,
+            status: 200,
+            statusText: 'OK',
+            config: undefined,
+            headers: undefined,
+          }),
+        );
+
+      await service.getAddressesByPostcode(testPostcode);
+
+      expect(httpServiceGet).toHaveBeenCalledTimes(1);
+      expect(httpServiceGet).toHaveBeenCalledWith(...expectedHttpServiceGetArgs);
+    });
+
+    it("no results - returns 200 without results", async () => {
+      when(httpServiceGet)
+        .calledWith(...expectedHttpServiceGetArgs)
+        .mockReturnValueOnce(
+          of({
+            data: noResultsResponse,
+            status: 200,
+            statusText: 'OK',
+            config: undefined,
+            headers: undefined,
+          }),
+        );
+
+      const results = await service.getAddressesByPostcode(testPostcode);
+
+      expect(httpServiceGet).toHaveBeenCalledTimes(1);
+      expect(httpServiceGet).toHaveBeenCalledWith(...expectedHttpServiceGetArgs);
+      expect(results).toBe(noResultsResponse);
+    });
+
+    it('throws an OrdnanceSurveyException if the request to Ordnance Survey fails', async () => {
+      const axiosRequestError = new AxiosError();
+      when(httpServiceGet)
+        .calledWith(...expectedHttpServiceGetArgs)
+        .mockReturnValueOnce(throwError(() => axiosRequestError));
+
+      const getCustomersPromise = service.getAddressesByPostcode(testPostcode);
+
+      await expect(getCustomersPromise).rejects.toBeInstanceOf(OrdnanceSurveyException);
+      await expect(getCustomersPromise).rejects.toThrow('Failed to get response from Ordnance Survey API.');
+      await expect(getCustomersPromise).rejects.toHaveProperty('innerError', axiosRequestError);
+    });
+  });
+});
diff --git a/src/helper_modules/ordnance-survey/ordnance-survey.service.ts b/src/helper_modules/ordnance-survey/ordnance-survey.service.ts
new file mode 100644
index 00000000..7443a9a5
--- /dev/null
+++ b/src/helper_modules/ordnance-survey/ordnance-survey.service.ts
@@ -0,0 +1,35 @@
+import { HttpService } from '@nestjs/axios';
+import { Injectable } from '@nestjs/common';
+import { HttpClient } from '@ukef/modules/http/http.client';
+
+import { GetAddressResponse } from './dto/get-addresses-response.dto';
+// import { getCustomersNotFoundKnownOrdnanceSurveyError } from './known-errors';
+import { createWrapOrdnanceSurveyHttpGetErrorCallback } from './wrap-ordnance-survey-http-error-callback';
+import { ConfigService } from '@nestjs/config';
+import { OrdnanceSurveyConfig, KEY as ORDNANCE_SURVEY_CONFIG_KEY } from '@ukef/config/ordnance-survey.config';
+
+@Injectable()
+export class OrdnanceSurveyService {
+  private readonly httpClient: HttpClient;
+  private readonly key: string;
+
+  constructor(httpService: HttpService, configService: ConfigService) {
+    this.httpClient = new HttpClient(httpService);
+    const { key } = configService.get<OrdnanceSurveyConfig>(ORDNANCE_SURVEY_CONFIG_KEY);
+    this.key = key;
+  }
+
+  async getAddressesByPostcode(postcode): Promise<GetAddressResponse> {
+    const path = '/search/places/v1/postcode?postcode=' + postcode + '&key=' + this.key;
+    const { data } = await this.httpClient.get<GetAddressResponse>({
+      path,
+      headers: { 'Content-Type': 'application/json' },
+      onError: createWrapOrdnanceSurveyHttpGetErrorCallback({
+        messageForUnknownError: `Failed to get response from Ordnance Survey API.`,
+        knownErrors: [],
+        // knownErrors: [getCustomersNotFoundKnownOrdnanceSurveyError()], // TODO: should we change 200 no results to 404?
+      }),
+    });
+    return data;
+  }
+}
diff --git a/src/helper_modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts b/src/helper_modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts
new file mode 100644
index 00000000..cf1e4c1a
--- /dev/null
+++ b/src/helper_modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts
@@ -0,0 +1,32 @@
+import { AxiosError } from 'axios';
+import { ObservableInput, throwError } from 'rxjs';
+
+import { OrdnanceSurveyException } from './exception/ordnance-survey.exception';
+import { KnownErrors } from './known-errors';
+
+type AcbsHttpErrorCallback = (error: Error) => ObservableInput<never>;
+
+export const createWrapOrdnanceSurveyHttpGetErrorCallback =
+  ({ messageForUnknownError, knownErrors }: { messageForUnknownError: string; knownErrors: KnownErrors }): AcbsHttpErrorCallback =>
+  (error: Error) => {
+    let errorString;
+    if (error instanceof AxiosError && error.response) {
+      if (typeof error.response.data === 'object') {
+        errorString = JSON.stringify(error.response.data);
+      }
+      if (typeof error.response.data === 'string') {
+        errorString = error.response.data;
+      }
+      if (errorString) {
+        const errorStringInLowerCase = errorString.toLowerCase();
+
+        knownErrors.forEach(({ caseInsensitiveSubstringToFind, throwError }) => {
+          if (errorStringInLowerCase.includes(caseInsensitiveSubstringToFind.toLowerCase())) {
+            return throwError(error);
+          }
+        });
+      }
+    }
+
+    return throwError(() => new OrdnanceSurveyException(messageForUnknownError, error));
+  };

From 14a6a08485888f854935909f4039cd8eb9d261ae Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Wed, 10 Apr 2024 09:50:43 +0100
Subject: [PATCH 02/56] feat(DTFS2-7052): new module geospatial

---
 .../dto/get-address-by-postcode-query.dto.ts  | 16 +++++++
 .../dto/get-search-addresses-response.dto.ts  | 47 +++++++++++++++++++
 .../geospatial/geospatial.controller.ts       | 29 ++++++++++++
 src/modules/geospatial/geospatial.module.ts   | 12 +++++
 src/modules/geospatial/geospatial.service.ts  | 33 +++++++++++++
 5 files changed, 137 insertions(+)
 create mode 100644 src/modules/geospatial/dto/get-address-by-postcode-query.dto.ts
 create mode 100644 src/modules/geospatial/dto/get-search-addresses-response.dto.ts
 create mode 100644 src/modules/geospatial/geospatial.controller.ts
 create mode 100644 src/modules/geospatial/geospatial.module.ts
 create mode 100644 src/modules/geospatial/geospatial.service.ts

diff --git a/src/modules/geospatial/dto/get-address-by-postcode-query.dto.ts b/src/modules/geospatial/dto/get-address-by-postcode-query.dto.ts
new file mode 100644
index 00000000..d01a159f
--- /dev/null
+++ b/src/modules/geospatial/dto/get-address-by-postcode-query.dto.ts
@@ -0,0 +1,16 @@
+import { ApiProperty } from '@nestjs/swagger';
+import { Matches, MaxLength, MinLength } from 'class-validator';
+
+const UK_POSTCODE = /^[A-Za-z]{1,2}[0-9Rr][0-9A-Za-z]?\s?[0-9][ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$/;
+
+export class GetAddressByPostcodeQueryDto {
+  @ApiProperty({
+    example: 'SW1A 2AQ',
+    description: 'Postcode to search for',
+  })
+  @MinLength(5)
+  @MaxLength(8)
+  @Matches(UK_POSTCODE)
+  public postcode: string;
+}
+
diff --git a/src/modules/geospatial/dto/get-search-addresses-response.dto.ts b/src/modules/geospatial/dto/get-search-addresses-response.dto.ts
new file mode 100644
index 00000000..d4b84090
--- /dev/null
+++ b/src/modules/geospatial/dto/get-search-addresses-response.dto.ts
@@ -0,0 +1,47 @@
+import { ApiProperty } from '@nestjs/swagger';
+
+export type GetSearchAddressesResponse = GetSearchAddressesResponseItem[];
+
+export class GetSearchAddressesResponseItem {
+  @ApiProperty({
+    description: 'Organisation name if available',
+    example: 'CHURCHILL MUSEUM & CABINET WAR ROOMS',
+  })
+  readonly organisationName: string | null;
+
+  @ApiProperty({
+    description: 'Address line 1',
+    example: 'CLIVE STEPS  KING CHARLES STREET',
+  })
+  readonly addressLine1: string;
+
+  @ApiProperty({
+    description: 'Address line 2',
+    example: null,
+  })
+  readonly addressLine2: string | null;
+
+  @ApiProperty({
+    description: 'Address line 3',
+    example: null,
+  })
+  readonly addressLine3: string | null;
+
+  @ApiProperty({
+    description: 'Locality, Town',
+    example: 'LONDON',
+  })
+  readonly locality: string | null;
+
+  @ApiProperty({
+    description: 'Postcode',
+    example: 'SW1A 2AQ',
+  })
+  readonly postalCode: string | null;
+
+  @ApiProperty({
+    description: 'Country of address record',
+    example: null,
+  })
+  readonly country: string | null;
+}
diff --git a/src/modules/geospatial/geospatial.controller.ts b/src/modules/geospatial/geospatial.controller.ts
new file mode 100644
index 00000000..74d4e8ff
--- /dev/null
+++ b/src/modules/geospatial/geospatial.controller.ts
@@ -0,0 +1,29 @@
+import { BadRequestException, Controller, Get, Query } from '@nestjs/common';
+import { ApiNotFoundResponse, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
+
+import { GetSearchPostcodeOrdnanceSurveyQueryDto } from '@ukef/helper-modules/ordnance-survey/dto/get-search-postcode-query.dto';
+import { GeospatialService } from './geospatial.service';
+import { GetAddressByPostcodeQueryDto } from './dto/get-address-by-postcode-query.dto';
+import { GetSearchAddressesResponse, GetSearchAddressesResponseItem } from './dto/get-search-addresses-response.dto';
+
+@ApiTags('geospatial')
+@Controller('geospatial')
+export class GeospatialController {
+  constructor(private readonly geospatialService: GeospatialService) {}
+
+  @Get('addresses/postcode')
+  @ApiOperation({
+    summary: "A search based on a property's postcode. Will accept a full postcode consisting of the area, district, sector and unit e.g. SO16 0AS.",
+  })
+  @ApiResponse({
+    status: 200,
+    description: 'AddressBase® Premium Basic Land and Property Units (BLPUs) can reference two types of address and with the OS Places API it is possible to search for one at a time, or both. These are the Delivery Point Address (DPA) and the Land and Property Identifier (LPI).',
+    type: [GetSearchAddressesResponseItem],
+  })
+  @ApiNotFoundResponse({
+    description: 'Customer not found.',
+  })
+  getGeospatial(@Query() query: GetAddressByPostcodeQueryDto): Promise<GetSearchAddressesResponse> {
+    return this.geospatialService.getAddressesByPostcode(query.postcode);
+  }
+}
diff --git a/src/modules/geospatial/geospatial.module.ts b/src/modules/geospatial/geospatial.module.ts
new file mode 100644
index 00000000..8d4ae4aa
--- /dev/null
+++ b/src/modules/geospatial/geospatial.module.ts
@@ -0,0 +1,12 @@
+import { Module } from '@nestjs/common';
+import { OrdnanceSurveyModule } from '@ukef/helper-modules/ordnance-survey/ordnance-survey.module';
+
+import { GeospatialController } from './geospatial.controller';
+import { GeospatialService } from './geospatial.service';
+
+@Module({
+  imports: [OrdnanceSurveyModule],
+  controllers: [GeospatialController],
+  providers: [GeospatialService],
+})
+export class GeospatialModule {}
diff --git a/src/modules/geospatial/geospatial.service.ts b/src/modules/geospatial/geospatial.service.ts
new file mode 100644
index 00000000..e48de375
--- /dev/null
+++ b/src/modules/geospatial/geospatial.service.ts
@@ -0,0 +1,33 @@
+import { Injectable } from '@nestjs/common';
+import { OrdnanceSurveyService } from '@ukef/helper-modules/ordnance-survey/ordnance-survey.service';
+
+import { GetSearchAddressesResponse } from './dto/get-search-addresses-response.dto';
+
+@Injectable()
+export class GeospatialService {
+  constructor(private readonly ordnanceSurveyService: OrdnanceSurveyService) {}
+
+  async getAddressesByPostcode(postcode: string): Promise<GetSearchAddressesResponse> {
+    let addresses = [];
+    const response = await this.ordnanceSurveyService.getAddressesByPostcode(postcode);
+
+    response.results.forEach((item) => {
+      // if (item.DPA.LANGUAGE === (req.query.language ? req.query.language : 'EN')) {
+      if (item.DPA.LANGUAGE === 'EN') {
+        // Ordnance survey sends duplicated results with the welsh version too via 'CY'
+
+        addresses.push({
+          organisationName: item.DPA.ORGANISATION_NAME || null,
+          addressLine1: `${item.DPA.BUILDING_NAME || ''} ${item.DPA.BUILDING_NUMBER || ''} ${item.DPA.THOROUGHFARE_NAME || ''}`.trim(),
+          addressLine2: item.DPA.DEPENDENT_LOCALITY || null,
+          addressLine3: null, // keys to match registered Address as requested, but not available in OS Places
+          locality: item.DPA.POST_TOWN || null,
+          postalCode: item.DPA.POSTCODE || null,
+          country: null, // keys to match registered Address as requested, but not available in OS Places
+        });
+      }
+    });
+
+    return addresses;
+  }
+}

From 17fea546a3cf1e0945e85e653c7829544de41bfc Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Wed, 10 Apr 2024 12:07:42 +0100
Subject: [PATCH 03/56] feat(DTFS2-7052): fixed lint errors

---
 .eslintrc.json                                |  9 +-
 jest.config.ts                                |  1 +
 .../dto/get-addresses-response.dto.ts         | 56 ++++++++++++
 .../dto/get-search-postcode-query.dto.ts      |  0
 ...-search-places-v1-postcode-no-results.json |  0
 ...esponse-for-search-places-v1-postcode.json |  0
 .../ordnance-survey.exception.test.ts         |  0
 .../exception/ordnance-survey.exception.ts    |  0
 .../ordnance-survey/known-errors.ts           |  0
 .../ordnance-survey/ordnance-survey.module.ts |  2 +-
 .../ordnance-survey.service.test.ts           | 24 +++--
 .../ordnance-survey.service.ts                |  7 +-
 ...rap-ordnance-survey-http-error-callback.ts |  0
 .../dto/get-addresses-response.dto.ts         | 90 -------------------
 .../dto/get-address-by-postcode-query.dto.ts  |  3 +-
 .../geospatial/geospatial.controller.ts       |  8 +-
 src/modules/geospatial/geospatial.service.ts  |  5 +-
 src/modules/mdm.module.ts                     |  6 ++
 tsconfig.json                                 |  3 +-
 19 files changed, 93 insertions(+), 121 deletions(-)
 create mode 100644 src/helper-modules/ordnance-survey/dto/get-addresses-response.dto.ts
 rename src/{helper_modules => helper-modules}/ordnance-survey/dto/get-search-postcode-query.dto.ts (100%)
 rename src/{helper_modules => helper-modules}/ordnance-survey/examples/example-response-for-search-places-v1-postcode-no-results.json (100%)
 rename src/{helper_modules => helper-modules}/ordnance-survey/examples/example-response-for-search-places-v1-postcode.json (100%)
 rename src/{helper_modules => helper-modules}/ordnance-survey/exception/ordnance-survey.exception.test.ts (100%)
 rename src/{helper_modules => helper-modules}/ordnance-survey/exception/ordnance-survey.exception.ts (100%)
 rename src/{helper_modules => helper-modules}/ordnance-survey/known-errors.ts (100%)
 rename src/{helper_modules => helper-modules}/ordnance-survey/ordnance-survey.module.ts (92%)
 rename src/{helper_modules => helper-modules}/ordnance-survey/ordnance-survey.service.test.ts (83%)
 rename src/{helper_modules => helper-modules}/ordnance-survey/ordnance-survey.service.ts (88%)
 rename src/{helper_modules => helper-modules}/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts (100%)
 delete mode 100644 src/helper_modules/ordnance-survey/dto/get-addresses-response.dto.ts

diff --git a/.eslintrc.json b/.eslintrc.json
index 7688571f..1b37c0d3 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -15,14 +15,14 @@
     "plugin:eslint-comments/recommended",
     "plugin:optimize-regex/recommended",
     "plugin:switch-case/recommended",
-    "plugin:security/recommended",
+    "plugin:security/recommended-legacy",
     "plugin:import/recommended",
     "plugin:import/typescript",
     "prettier"
   ],
   "parserOptions": {
     "project": "tsconfig.json",
-    "ecmaVersion": 2020,
+    "ecmaVersion": "latest",
     "sourceType": "module"
   },
   "env": {
@@ -110,7 +110,10 @@
     "consistent-return": "off",
     "no-unused-vars": "off",
     "unused-imports/no-unused-imports": "error",
-    "unused-imports/no-unused-vars": "error",
+    "unused-imports/no-unused-vars": [
+      "error",
+      { "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
+    ],
     "@typescript-eslint/no-unused-vars": "off",
     "@typescript-eslint/ban-ts-comment": "off",
     "@typescript-eslint/no-explicit-any": "off",
diff --git a/jest.config.ts b/jest.config.ts
index a4dfdb06..48c400f1 100644
--- a/jest.config.ts
+++ b/jest.config.ts
@@ -12,6 +12,7 @@ const defaultSettings = {
     '@ukef/config/(.*)': '<rootDir>/../src/config/$1',
     '@ukef/database/(.*)': '<rootDir>/../src/modules/database/$1',
     '@ukef/helpers/(.*)': '<rootDir>/../src/helpers/$1',
+    '@ukef/helper-modules/(.*)': '<rootDir>/../src/helper-modules/$1',
     '@ukef/modules/(.*)': '<rootDir>/../src/modules/$1',
     '@ukef/auth/(.*)': '<rootDir>/../src/modules/auth/$1',
     '@ukef/(.*)': '<rootDir>/../src/$1',
diff --git a/src/helper-modules/ordnance-survey/dto/get-addresses-response.dto.ts b/src/helper-modules/ordnance-survey/dto/get-addresses-response.dto.ts
new file mode 100644
index 00000000..755ef606
--- /dev/null
+++ b/src/helper-modules/ordnance-survey/dto/get-addresses-response.dto.ts
@@ -0,0 +1,56 @@
+export type GetAddressResponse = {
+  header: {
+    uri: string;
+    query: string;
+    offset: number;
+    totalresults: number;
+    format: string;
+    dataset: string;
+    lr: string;
+    maxresults: number;
+    epoch: string;
+    lastupdate: string;
+    output_srs: string;
+  };
+  results?: GetAddressResponseItem[];
+};
+
+interface GetAddressResponseItem {
+  DPA: GetAddressResponseAddress;
+}
+
+interface GetAddressResponseAddress {
+  UPRN: string;
+  UDPRN: string;
+  ADDRESS: string;
+  BUILDING_NAME?: string;
+  BUILDING_NUMBER?: string;
+  ORGANISATION_NAME?: string;
+  DEPENDENT_LOCALITY?: string;
+  THOROUGHFARE_NAME: string;
+  POST_TOWN: string;
+  POSTCODE: string;
+  RPC: string;
+  X_COORDINATE: number;
+  Y_COORDINATE: number;
+  STATUS: string;
+  LOGICAL_STATUS_CODE: string;
+  CLASSIFICATION_CODE: string;
+  CLASSIFICATION_CODE_DESCRIPTION: string;
+  LOCAL_CUSTODIAN_CODE: number;
+  LOCAL_CUSTODIAN_CODE_DESCRIPTION: string;
+  COUNTRY_CODE: string;
+  COUNTRY_CODE_DESCRIPTION: string;
+  POSTAL_ADDRESS_CODE: string;
+  POSTAL_ADDRESS_CODE_DESCRIPTION: string;
+  BLPU_STATE_CODE: string;
+  BLPU_STATE_CODE_DESCRIPTION: string;
+  TOPOGRAPHY_LAYER_TOID: string;
+  LAST_UPDATE_DATE: string;
+  ENTRY_DATE: string;
+  BLPU_STATE_DATE: string;
+  LANGUAGE: string;
+  MATCH: number;
+  MATCH_DESCRIPTION: string;
+  DELIVERY_POINT_SUFFIX: string;
+}
diff --git a/src/helper_modules/ordnance-survey/dto/get-search-postcode-query.dto.ts b/src/helper-modules/ordnance-survey/dto/get-search-postcode-query.dto.ts
similarity index 100%
rename from src/helper_modules/ordnance-survey/dto/get-search-postcode-query.dto.ts
rename to src/helper-modules/ordnance-survey/dto/get-search-postcode-query.dto.ts
diff --git a/src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode-no-results.json b/src/helper-modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode-no-results.json
similarity index 100%
rename from src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode-no-results.json
rename to src/helper-modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode-no-results.json
diff --git a/src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode.json b/src/helper-modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode.json
similarity index 100%
rename from src/helper_modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode.json
rename to src/helper-modules/ordnance-survey/examples/example-response-for-search-places-v1-postcode.json
diff --git a/src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.test.ts b/src/helper-modules/ordnance-survey/exception/ordnance-survey.exception.test.ts
similarity index 100%
rename from src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.test.ts
rename to src/helper-modules/ordnance-survey/exception/ordnance-survey.exception.test.ts
diff --git a/src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.ts b/src/helper-modules/ordnance-survey/exception/ordnance-survey.exception.ts
similarity index 100%
rename from src/helper_modules/ordnance-survey/exception/ordnance-survey.exception.ts
rename to src/helper-modules/ordnance-survey/exception/ordnance-survey.exception.ts
diff --git a/src/helper_modules/ordnance-survey/known-errors.ts b/src/helper-modules/ordnance-survey/known-errors.ts
similarity index 100%
rename from src/helper_modules/ordnance-survey/known-errors.ts
rename to src/helper-modules/ordnance-survey/known-errors.ts
diff --git a/src/helper_modules/ordnance-survey/ordnance-survey.module.ts b/src/helper-modules/ordnance-survey/ordnance-survey.module.ts
similarity index 92%
rename from src/helper_modules/ordnance-survey/ordnance-survey.module.ts
rename to src/helper-modules/ordnance-survey/ordnance-survey.module.ts
index 553f756b..2afb21df 100644
--- a/src/helper_modules/ordnance-survey/ordnance-survey.module.ts
+++ b/src/helper-modules/ordnance-survey/ordnance-survey.module.ts
@@ -1,6 +1,6 @@
 import { Module } from '@nestjs/common';
 import { ConfigModule, ConfigService } from '@nestjs/config';
-import { OrdnanceSurveyConfig, KEY as ORDNANCE_SURVEY_CONFIG_KEY } from '@ukef/config/ordnance-survey.config';
+import { KEY as ORDNANCE_SURVEY_CONFIG_KEY, OrdnanceSurveyConfig } from '@ukef/config/ordnance-survey.config';
 import { HttpModule } from '@ukef/modules/http/http.module';
 
 import { OrdnanceSurveyService } from './ordnance-survey.service';
diff --git a/src/helper_modules/ordnance-survey/ordnance-survey.service.test.ts b/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
similarity index 83%
rename from src/helper_modules/ordnance-survey/ordnance-survey.service.test.ts
rename to src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
index e895730a..6ed18f36 100644
--- a/src/helper_modules/ordnance-survey/ordnance-survey.service.test.ts
+++ b/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
@@ -1,15 +1,14 @@
 import { HttpService } from '@nestjs/axios';
+import { ConfigService } from '@nestjs/config';
 import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator';
 import { AxiosError } from 'axios';
 import { when } from 'jest-when';
 import { of, throwError } from 'rxjs';
+import expectedResponse = require('./examples/example-response-for-search-places-v1-postcode.json');
+import noResultsResponse = require('./examples/example-response-for-search-places-v1-postcode-no-results.json');
 
 import { OrdnanceSurveyException } from './exception/ordnance-survey.exception';
 import { OrdnanceSurveyService } from './ordnance-survey.service';
-import { ConfigService } from '@nestjs/config';
-
-const expectedResponse = require('./examples/example-response-for-search-places-v1-postcode.json');
-const noResultsResponse = require('./examples/example-response-for-search-places-v1-postcode.json');
 
 describe('OrdnanceSurveyService', () => {
   const valueGenerator = new RandomValueGenerator();
@@ -19,7 +18,7 @@ describe('OrdnanceSurveyService', () => {
   let service: OrdnanceSurveyService;
 
   const testPostcode = 'W1A 1AA';
-  const testKey = valueGenerator.string({length: 10});
+  const testKey = valueGenerator.string({ length: 10 });
   const basePath = '/search/places/v1/postcode';
 
   beforeEach(() => {
@@ -28,16 +27,14 @@ describe('OrdnanceSurveyService', () => {
     httpServiceGet = jest.fn();
     httpService.get = httpServiceGet;
 
-    configServiceGet = jest.fn().mockReturnValue({key: testKey});
+    configServiceGet = jest.fn().mockReturnValue({ key: testKey });
     configService.get = configServiceGet;
 
     service = new OrdnanceSurveyService(httpService, configService);
   });
 
-
-
   describe('getAddressesByPostcode', () => {
-    const expectedPath = `${basePath}?postcode=${testPostcode}&key=${testKey}`;
+    const expectedPath = `${basePath}?postcode=${encodeURIComponent(testPostcode)}&key=${encodeURIComponent(testKey)}`;
 
     const expectedHttpServiceGetArgs: [string, object] = [expectedPath, { headers: { 'Content-Type': 'application/json' } }];
 
@@ -70,9 +67,8 @@ describe('OrdnanceSurveyService', () => {
         expectedUrlQueryPart: '?postcode=W1A1AA',
       },
     ])('call Ordnance Survey API with correct and safe query parameters "$expectedUrlQueryPart"', async ({ postcode, expectedUrlQueryPart }) => {
-      // const expectedPath = `${basePath}${expectedUrlQueryPart}&key=${testKey}`;
-
-      // const expectedHttpServiceGetArgs: [string, object] = [expectedPath, { headers: { 'Content-Type': 'application/json' } }];
+      const expectedPath = `${basePath}${expectedUrlQueryPart}&key=${encodeURIComponent(testKey)}`;
+      const expectedHttpServiceGetArgs: [string, object] = [expectedPath, { headers: { 'Content-Type': 'application/json' } }];
 
       when(httpServiceGet)
         .calledWith(...expectedHttpServiceGetArgs)
@@ -86,13 +82,13 @@ describe('OrdnanceSurveyService', () => {
           }),
         );
 
-      await service.getAddressesByPostcode(testPostcode);
+      await service.getAddressesByPostcode(postcode);
 
       expect(httpServiceGet).toHaveBeenCalledTimes(1);
       expect(httpServiceGet).toHaveBeenCalledWith(...expectedHttpServiceGetArgs);
     });
 
-    it("no results - returns 200 without results", async () => {
+    it('no results - returns 200 without results', async () => {
       when(httpServiceGet)
         .calledWith(...expectedHttpServiceGetArgs)
         .mockReturnValueOnce(
diff --git a/src/helper_modules/ordnance-survey/ordnance-survey.service.ts b/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
similarity index 88%
rename from src/helper_modules/ordnance-survey/ordnance-survey.service.ts
rename to src/helper-modules/ordnance-survey/ordnance-survey.service.ts
index 7443a9a5..5464166e 100644
--- a/src/helper_modules/ordnance-survey/ordnance-survey.service.ts
+++ b/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
@@ -1,12 +1,12 @@
 import { HttpService } from '@nestjs/axios';
 import { Injectable } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { KEY as ORDNANCE_SURVEY_CONFIG_KEY, OrdnanceSurveyConfig } from '@ukef/config/ordnance-survey.config';
 import { HttpClient } from '@ukef/modules/http/http.client';
 
 import { GetAddressResponse } from './dto/get-addresses-response.dto';
 // import { getCustomersNotFoundKnownOrdnanceSurveyError } from './known-errors';
 import { createWrapOrdnanceSurveyHttpGetErrorCallback } from './wrap-ordnance-survey-http-error-callback';
-import { ConfigService } from '@nestjs/config';
-import { OrdnanceSurveyConfig, KEY as ORDNANCE_SURVEY_CONFIG_KEY } from '@ukef/config/ordnance-survey.config';
 
 @Injectable()
 export class OrdnanceSurveyService {
@@ -20,7 +20,8 @@ export class OrdnanceSurveyService {
   }
 
   async getAddressesByPostcode(postcode): Promise<GetAddressResponse> {
-    const path = '/search/places/v1/postcode?postcode=' + postcode + '&key=' + this.key;
+    const path = `/search/places/v1/postcode?postcode=${encodeURIComponent(postcode)}&key=${encodeURIComponent(this.key)}`;
+
     const { data } = await this.httpClient.get<GetAddressResponse>({
       path,
       headers: { 'Content-Type': 'application/json' },
diff --git a/src/helper_modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts b/src/helper-modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts
similarity index 100%
rename from src/helper_modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts
rename to src/helper-modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts
diff --git a/src/helper_modules/ordnance-survey/dto/get-addresses-response.dto.ts b/src/helper_modules/ordnance-survey/dto/get-addresses-response.dto.ts
deleted file mode 100644
index 5a9f6201..00000000
--- a/src/helper_modules/ordnance-survey/dto/get-addresses-response.dto.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-export type GetAddressResponse = {
-  header: {
-      uri: string,
-      query: string,
-      offset: number,
-      totalresults: number,
-      format: string,
-      dataset: string,
-      lr: string,
-      maxresults: number,
-      epoch: string,
-      lastupdate: string,
-      output_srs: string
-  },
-  results?: GetAddressResponseItem[],
-};
-
-interface GetAddressResponseItem {
-  DPA: {
-    UPRN: string,
-    UDPRN: string,
-    ADDRESS: string,
-    BUILDING_NAME?: string,
-    BUILDING_NUMBER?: string,
-    ORGANISATION_NAME?: string;
-    DEPENDENT_LOCALITY?: string;
-    THOROUGHFARE_NAME: string,
-    POST_TOWN: string,
-    POSTCODE: string,
-    RPC: string,
-    X_COORDINATE: number,
-    Y_COORDINATE: number,
-    STATUS: string,
-    LOGICAL_STATUS_CODE: string,
-    CLASSIFICATION_CODE: string,
-    CLASSIFICATION_CODE_DESCRIPTION: string,
-    LOCAL_CUSTODIAN_CODE: number,
-    LOCAL_CUSTODIAN_CODE_DESCRIPTION: string,
-    COUNTRY_CODE: string,
-    COUNTRY_CODE_DESCRIPTION: string,
-    POSTAL_ADDRESS_CODE: string,
-    POSTAL_ADDRESS_CODE_DESCRIPTION: string,
-    BLPU_STATE_CODE: string,
-    BLPU_STATE_CODE_DESCRIPTION: string,
-    TOPOGRAPHY_LAYER_TOID: string,
-    LAST_UPDATE_DATE: string,
-    ENTRY_DATE: string,
-    BLPU_STATE_DATE: string,
-    LANGUAGE: string,
-    MATCH: number,
-    MATCH_DESCRIPTION: string,
-    DELIVERY_POINT_SUFFIX: string
-  }
-}
-
-// interface GetAddressResponseAddress {
-//   UPRN: string,
-//   UDPRN: string,
-//   ADDRESS: string,
-//   BUILDING_NAME?: string,
-//   BUILDING_NUMBER?: string,
-//   ORGANISATION_NAME?: string;
-//   DEPENDENT_LOCALITY?: string;
-//   THOROUGHFARE_NAME: string,
-//   POST_TOWN: string,
-//   POSTCODE: string,
-//   RPC: string,
-//   X_COORDINATE: number,
-//   Y_COORDINATE: number,
-//   STATUS: string,
-//   LOGICAL_STATUS_CODE: string,
-//   CLASSIFICATION_CODE: string,
-//   CLASSIFICATION_CODE_DESCRIPTION: string,
-//   LOCAL_CUSTODIAN_CODE: number,
-//   LOCAL_CUSTODIAN_CODE_DESCRIPTION: string,
-//   COUNTRY_CODE: string,
-//   COUNTRY_CODE_DESCRIPTION: string,
-//   POSTAL_ADDRESS_CODE: string,
-//   POSTAL_ADDRESS_CODE_DESCRIPTION: string,
-//   BLPU_STATE_CODE: string,
-//   BLPU_STATE_CODE_DESCRIPTION: string,
-//   TOPOGRAPHY_LAYER_TOID: string,
-//   LAST_UPDATE_DATE: string,
-//   ENTRY_DATE: string,
-//   BLPU_STATE_DATE: string,
-//   LANGUAGE: string,
-//   MATCH: number,
-//   MATCH_DESCRIPTION: string,
-//   DELIVERY_POINT_SUFFIX: string
-// }
diff --git a/src/modules/geospatial/dto/get-address-by-postcode-query.dto.ts b/src/modules/geospatial/dto/get-address-by-postcode-query.dto.ts
index d01a159f..698dbe25 100644
--- a/src/modules/geospatial/dto/get-address-by-postcode-query.dto.ts
+++ b/src/modules/geospatial/dto/get-address-by-postcode-query.dto.ts
@@ -1,7 +1,7 @@
 import { ApiProperty } from '@nestjs/swagger';
 import { Matches, MaxLength, MinLength } from 'class-validator';
 
-const UK_POSTCODE = /^[A-Za-z]{1,2}[0-9Rr][0-9A-Za-z]?\s?[0-9][ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$/;
+const UK_POSTCODE = /^[A-Za-z]{1,2}[\dRr][\dA-Za-z]?\s?\d[ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$/;
 
 export class GetAddressByPostcodeQueryDto {
   @ApiProperty({
@@ -13,4 +13,3 @@ export class GetAddressByPostcodeQueryDto {
   @Matches(UK_POSTCODE)
   public postcode: string;
 }
-
diff --git a/src/modules/geospatial/geospatial.controller.ts b/src/modules/geospatial/geospatial.controller.ts
index 74d4e8ff..4c25561a 100644
--- a/src/modules/geospatial/geospatial.controller.ts
+++ b/src/modules/geospatial/geospatial.controller.ts
@@ -1,10 +1,9 @@
-import { BadRequestException, Controller, Get, Query } from '@nestjs/common';
+import { Controller, Get, Query } from '@nestjs/common';
 import { ApiNotFoundResponse, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
 
-import { GetSearchPostcodeOrdnanceSurveyQueryDto } from '@ukef/helper-modules/ordnance-survey/dto/get-search-postcode-query.dto';
-import { GeospatialService } from './geospatial.service';
 import { GetAddressByPostcodeQueryDto } from './dto/get-address-by-postcode-query.dto';
 import { GetSearchAddressesResponse, GetSearchAddressesResponseItem } from './dto/get-search-addresses-response.dto';
+import { GeospatialService } from './geospatial.service';
 
 @ApiTags('geospatial')
 @Controller('geospatial')
@@ -17,7 +16,8 @@ export class GeospatialController {
   })
   @ApiResponse({
     status: 200,
-    description: 'AddressBase® Premium Basic Land and Property Units (BLPUs) can reference two types of address and with the OS Places API it is possible to search for one at a time, or both. These are the Delivery Point Address (DPA) and the Land and Property Identifier (LPI).',
+    description:
+      'AddressBase® Premium Basic Land and Property Units (BLPUs) can reference two types of address and with the OS Places API it is possible to search for one at a time, or both. These are the Delivery Point Address (DPA) and the Land and Property Identifier (LPI).',
     type: [GetSearchAddressesResponseItem],
   })
   @ApiNotFoundResponse({
diff --git a/src/modules/geospatial/geospatial.service.ts b/src/modules/geospatial/geospatial.service.ts
index e48de375..af45adab 100644
--- a/src/modules/geospatial/geospatial.service.ts
+++ b/src/modules/geospatial/geospatial.service.ts
@@ -8,14 +8,13 @@ export class GeospatialService {
   constructor(private readonly ordnanceSurveyService: OrdnanceSurveyService) {}
 
   async getAddressesByPostcode(postcode: string): Promise<GetSearchAddressesResponse> {
-    let addresses = [];
+    const addresses = [];
     const response = await this.ordnanceSurveyService.getAddressesByPostcode(postcode);
 
     response.results.forEach((item) => {
       // if (item.DPA.LANGUAGE === (req.query.language ? req.query.language : 'EN')) {
+      // Ordnance survey sends duplicated results with the welsh version too via 'CY'
       if (item.DPA.LANGUAGE === 'EN') {
-        // Ordnance survey sends duplicated results with the welsh version too via 'CY'
-
         addresses.push({
           organisationName: item.DPA.ORGANISATION_NAME || null,
           addressLine1: `${item.DPA.BUILDING_NAME || ''} ${item.DPA.BUILDING_NUMBER || ''} ${item.DPA.THOROUGHFARE_NAME || ''}`.trim(),
diff --git a/src/modules/mdm.module.ts b/src/modules/mdm.module.ts
index 2c16ce9b..9d3b7bfb 100644
--- a/src/modules/mdm.module.ts
+++ b/src/modules/mdm.module.ts
@@ -1,9 +1,11 @@
 import { Module } from '@nestjs/common';
 import { AuthModule } from '@ukef/auth/auth.module';
 import { DatabaseModule } from '@ukef/database/database.module';
+import { OrdnanceSurveyModule } from '@ukef/helper-modules/ordnance-survey/ordnance-survey.module';
 import { CurrenciesModule } from '@ukef/modules/currencies/currencies.module';
 import { CustomersModule } from '@ukef/modules/customers/customers.module';
 import { ExposurePeriodModule } from '@ukef/modules/exposure-period/exposure-period.module';
+import { GeospatialModule } from '@ukef/modules/geospatial/geospatial.module';
 import { HealthcheckModule } from '@ukef/modules/healthcheck/healthcheck.module';
 import { InterestRatesModule } from '@ukef/modules/interest-rates/interest-rates.module';
 import { MarketsModule } from '@ukef/modules/markets/markets.module';
@@ -26,6 +28,8 @@ import { YieldRatesModule } from '@ukef/modules/yield-rates/yield-rates.module';
     PremiumSchedulesModule,
     SectorIndustriesModule,
     YieldRatesModule,
+    OrdnanceSurveyModule,
+    GeospatialModule,
   ],
   exports: [
     AuthModule,
@@ -40,6 +44,8 @@ import { YieldRatesModule } from '@ukef/modules/yield-rates/yield-rates.module';
     PremiumSchedulesModule,
     SectorIndustriesModule,
     YieldRatesModule,
+    OrdnanceSurveyModule,
+    GeospatialModule,
   ],
 })
 export class MdmModule {}
diff --git a/tsconfig.json b/tsconfig.json
index 0a1bd4bf..98d8c83e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -32,6 +32,7 @@
       "@ukef/config/*": ["src/config/*"],
       "@ukef/database/*": ["src/modules/database/*"],
       "@ukef/helpers/*": ["src/helpers/*"],
+      "@ukef/helper-modules/*": ["src/helper-modules/*"],
       "@ukef/modules/*": ["src/modules/*"],
       "@ukef/auth/*": ["src/modules/auth/*"],
       "@ukef/swagger/*": ["src/swagger"],
@@ -41,7 +42,7 @@
   "include": [
     "src",
     "test",
-    "jest.config.ts"
+    "jest.config.ts",
   ],
   "exclude": [
     "node_modules",

From a3d54338610f50f4824d6c6eeffa50ebdb6e91a2 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Wed, 10 Apr 2024 12:52:00 +0100
Subject: [PATCH 04/56] feat(DTFS2-7052): adding constants and examples

---
 src/constants/enums.ts                        |  2 ++
 src/constants/enums/geospatial.ts             |  6 +++++
 src/constants/geospatial.constant.ts          |  9 ++++++++
 src/constants/index.ts                        |  2 ++
 .../ordnance-survey/known-errors.ts           |  6 ++---
 .../ordnance-survey.service.test.ts           |  8 ++++---
 .../ordnance-survey.service.ts                |  3 ++-
 .../dto/get-address-by-postcode-query.dto.ts  |  3 ++-
 .../dto/get-search-addresses-response.dto.ts  |  6 +++--
 .../geospatial/geospatial.controller.ts       |  3 +--
 src/modules/geospatial/geospatial.service.ts  | 23 +++++++++----------
 11 files changed, 47 insertions(+), 24 deletions(-)
 create mode 100644 src/constants/enums/geospatial.ts
 create mode 100644 src/constants/geospatial.constant.ts

diff --git a/src/constants/enums.ts b/src/constants/enums.ts
index 848cb76b..dc997052 100644
--- a/src/constants/enums.ts
+++ b/src/constants/enums.ts
@@ -1,7 +1,9 @@
 import * as FALLBACK_TO_LEGACY_DATA from './enums/fallbackToLegacyData';
+import * as GEOSPATIAL from './enums/geospatial';
 import * as PRODUCTS from './enums/products';
 
 export const ENUMS = {
   PRODUCTS: PRODUCTS.QueryParamProductsEnum,
   FALLBACK_TO_LEGACY_DATA: FALLBACK_TO_LEGACY_DATA.FallbackToLegacyDataEnum,
+  GEOSPATIAL_COUNTRIES: GEOSPATIAL.GeospatialCountriesEnum,
 };
diff --git a/src/constants/enums/geospatial.ts b/src/constants/enums/geospatial.ts
new file mode 100644
index 00000000..f181289d
--- /dev/null
+++ b/src/constants/enums/geospatial.ts
@@ -0,0 +1,6 @@
+export enum GeospatialCountriesEnum {
+  E = 'England',
+  S = 'Scotland',
+  W = 'Wales',
+  N = 'Northern Ireland',
+}
diff --git a/src/constants/geospatial.constant.ts b/src/constants/geospatial.constant.ts
new file mode 100644
index 00000000..08bd74c2
--- /dev/null
+++ b/src/constants/geospatial.constant.ts
@@ -0,0 +1,9 @@
+export const GEOSPATIAL = {
+  DEFAULT: {
+    RESULT_LANGUAGE: 'EN',
+    DATASET: 'DPA',
+  },
+  EXAMPLES: {
+    POSTCODE: 'SW1A 2AQ',
+  },
+};
diff --git a/src/constants/index.ts b/src/constants/index.ts
index 7bd70773..dd7a98e7 100644
--- a/src/constants/index.ts
+++ b/src/constants/index.ts
@@ -9,6 +9,7 @@
  * 5. Customers
  * 6. Strings to redact
  * 7. Strings locations to redact
+ * 8. Module geospatial
  */
 
 export * from './auth.constant';
@@ -16,6 +17,7 @@ export * from './customers.constant';
 export * from './database-name.constant';
 export * from './date.constant';
 export * from './enums';
+export * from './geospatial.constant';
 export * from './products.constant';
 export * from './redact-strings.constant';
 export * from './redact-strings-paths.constant';
diff --git a/src/helper-modules/ordnance-survey/known-errors.ts b/src/helper-modules/ordnance-survey/known-errors.ts
index 5684be7d..48c9d029 100644
--- a/src/helper-modules/ordnance-survey/known-errors.ts
+++ b/src/helper-modules/ordnance-survey/known-errors.ts
@@ -5,9 +5,9 @@ export type KnownErrors = KnownError[];
 
 type KnownError = { caseInsensitiveSubstringToFind: string; throwError: (error: AxiosError) => never };
 
-export const getCustomersNotFoundKnownOrdnanceSurveyError = (): KnownError => ({
-  caseInsensitiveSubstringToFind: 'Company registration not found',
+export const getAddressNotFoundKnownOrdnanceSurveyError = (): KnownError => ({
+  caseInsensitiveSubstringToFind: 'Address not found',
   throwError: (error) => {
-    throw new NotFoundException('Customer not found.', error);
+    throw new NotFoundException('Address not found.', error);
   },
 });
diff --git a/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts b/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
index 6ed18f36..e3cfceca 100644
--- a/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
+++ b/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
@@ -7,6 +7,8 @@ import { of, throwError } from 'rxjs';
 import expectedResponse = require('./examples/example-response-for-search-places-v1-postcode.json');
 import noResultsResponse = require('./examples/example-response-for-search-places-v1-postcode-no-results.json');
 
+import { GEOSPATIAL } from '@ukef/constants';
+
 import { OrdnanceSurveyException } from './exception/ordnance-survey.exception';
 import { OrdnanceSurveyService } from './ordnance-survey.service';
 
@@ -17,7 +19,7 @@ describe('OrdnanceSurveyService', () => {
   let configServiceGet: jest.Mock;
   let service: OrdnanceSurveyService;
 
-  const testPostcode = 'W1A 1AA';
+  const testPostcode = GEOSPATIAL.EXAMPLES.POSTCODE;
   const testKey = valueGenerator.string({ length: 10 });
   const basePath = '/search/places/v1/postcode';
 
@@ -34,7 +36,7 @@ describe('OrdnanceSurveyService', () => {
   });
 
   describe('getAddressesByPostcode', () => {
-    const expectedPath = `${basePath}?postcode=${encodeURIComponent(testPostcode)}&key=${encodeURIComponent(testKey)}`;
+    const expectedPath = `${basePath}?postcode=${encodeURIComponent(testPostcode)}&lr=EN&key=${encodeURIComponent(testKey)}`;
 
     const expectedHttpServiceGetArgs: [string, object] = [expectedPath, { headers: { 'Content-Type': 'application/json' } }];
 
@@ -67,7 +69,7 @@ describe('OrdnanceSurveyService', () => {
         expectedUrlQueryPart: '?postcode=W1A1AA',
       },
     ])('call Ordnance Survey API with correct and safe query parameters "$expectedUrlQueryPart"', async ({ postcode, expectedUrlQueryPart }) => {
-      const expectedPath = `${basePath}${expectedUrlQueryPart}&key=${encodeURIComponent(testKey)}`;
+      const expectedPath = `${basePath}${expectedUrlQueryPart}&lr=EN&key=${encodeURIComponent(testKey)}`;
       const expectedHttpServiceGetArgs: [string, object] = [expectedPath, { headers: { 'Content-Type': 'application/json' } }];
 
       when(httpServiceGet)
diff --git a/src/helper-modules/ordnance-survey/ordnance-survey.service.ts b/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
index 5464166e..dfa35330 100644
--- a/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
+++ b/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
@@ -2,6 +2,7 @@ import { HttpService } from '@nestjs/axios';
 import { Injectable } from '@nestjs/common';
 import { ConfigService } from '@nestjs/config';
 import { KEY as ORDNANCE_SURVEY_CONFIG_KEY, OrdnanceSurveyConfig } from '@ukef/config/ordnance-survey.config';
+import { GEOSPATIAL } from '@ukef/constants';
 import { HttpClient } from '@ukef/modules/http/http.client';
 
 import { GetAddressResponse } from './dto/get-addresses-response.dto';
@@ -20,7 +21,7 @@ export class OrdnanceSurveyService {
   }
 
   async getAddressesByPostcode(postcode): Promise<GetAddressResponse> {
-    const path = `/search/places/v1/postcode?postcode=${encodeURIComponent(postcode)}&key=${encodeURIComponent(this.key)}`;
+    const path = `/search/places/v1/postcode?postcode=${encodeURIComponent(postcode)}&lr=${GEOSPATIAL.DEFAULT.RESULT_LANGUAGE}&key=${encodeURIComponent(this.key)}`;
 
     const { data } = await this.httpClient.get<GetAddressResponse>({
       path,
diff --git a/src/modules/geospatial/dto/get-address-by-postcode-query.dto.ts b/src/modules/geospatial/dto/get-address-by-postcode-query.dto.ts
index 698dbe25..6202fdd1 100644
--- a/src/modules/geospatial/dto/get-address-by-postcode-query.dto.ts
+++ b/src/modules/geospatial/dto/get-address-by-postcode-query.dto.ts
@@ -1,11 +1,12 @@
 import { ApiProperty } from '@nestjs/swagger';
+import { GEOSPATIAL } from '@ukef/constants';
 import { Matches, MaxLength, MinLength } from 'class-validator';
 
 const UK_POSTCODE = /^[A-Za-z]{1,2}[\dRr][\dA-Za-z]?\s?\d[ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$/;
 
 export class GetAddressByPostcodeQueryDto {
   @ApiProperty({
-    example: 'SW1A 2AQ',
+    example: GEOSPATIAL.EXAMPLES.POSTCODE,
     description: 'Postcode to search for',
   })
   @MinLength(5)
diff --git a/src/modules/geospatial/dto/get-search-addresses-response.dto.ts b/src/modules/geospatial/dto/get-search-addresses-response.dto.ts
index d4b84090..5ba8a8c7 100644
--- a/src/modules/geospatial/dto/get-search-addresses-response.dto.ts
+++ b/src/modules/geospatial/dto/get-search-addresses-response.dto.ts
@@ -1,4 +1,5 @@
 import { ApiProperty } from '@nestjs/swagger';
+import { ENUMS, GEOSPATIAL } from '@ukef/constants';
 
 export type GetSearchAddressesResponse = GetSearchAddressesResponseItem[];
 
@@ -35,13 +36,14 @@ export class GetSearchAddressesResponseItem {
 
   @ApiProperty({
     description: 'Postcode',
-    example: 'SW1A 2AQ',
+    example: GEOSPATIAL.EXAMPLES.POSTCODE,
   })
   readonly postalCode: string | null;
 
   @ApiProperty({
     description: 'Country of address record',
-    example: null,
+    example: ENUMS.GEOSPATIAL_COUNTRIES.E,
+    enum: ENUMS.GEOSPATIAL_COUNTRIES,
   })
   readonly country: string | null;
 }
diff --git a/src/modules/geospatial/geospatial.controller.ts b/src/modules/geospatial/geospatial.controller.ts
index 4c25561a..5c210dc5 100644
--- a/src/modules/geospatial/geospatial.controller.ts
+++ b/src/modules/geospatial/geospatial.controller.ts
@@ -16,8 +16,7 @@ export class GeospatialController {
   })
   @ApiResponse({
     status: 200,
-    description:
-      'AddressBase® Premium Basic Land and Property Units (BLPUs) can reference two types of address and with the OS Places API it is possible to search for one at a time, or both. These are the Delivery Point Address (DPA) and the Land and Property Identifier (LPI).',
+    description: 'Returns addresses from Ordanance survey Delivery Point Address (DPA) system.',
     type: [GetSearchAddressesResponseItem],
   })
   @ApiNotFoundResponse({
diff --git a/src/modules/geospatial/geospatial.service.ts b/src/modules/geospatial/geospatial.service.ts
index af45adab..ca4f4a69 100644
--- a/src/modules/geospatial/geospatial.service.ts
+++ b/src/modules/geospatial/geospatial.service.ts
@@ -1,4 +1,5 @@
 import { Injectable } from '@nestjs/common';
+import { ENUMS } from '@ukef/constants';
 import { OrdnanceSurveyService } from '@ukef/helper-modules/ordnance-survey/ordnance-survey.service';
 
 import { GetSearchAddressesResponse } from './dto/get-search-addresses-response.dto';
@@ -12,19 +13,17 @@ export class GeospatialService {
     const response = await this.ordnanceSurveyService.getAddressesByPostcode(postcode);
 
     response.results.forEach((item) => {
-      // if (item.DPA.LANGUAGE === (req.query.language ? req.query.language : 'EN')) {
       // Ordnance survey sends duplicated results with the welsh version too via 'CY'
-      if (item.DPA.LANGUAGE === 'EN') {
-        addresses.push({
-          organisationName: item.DPA.ORGANISATION_NAME || null,
-          addressLine1: `${item.DPA.BUILDING_NAME || ''} ${item.DPA.BUILDING_NUMBER || ''} ${item.DPA.THOROUGHFARE_NAME || ''}`.trim(),
-          addressLine2: item.DPA.DEPENDENT_LOCALITY || null,
-          addressLine3: null, // keys to match registered Address as requested, but not available in OS Places
-          locality: item.DPA.POST_TOWN || null,
-          postalCode: item.DPA.POSTCODE || null,
-          country: null, // keys to match registered Address as requested, but not available in OS Places
-        });
-      }
+      const item_data = item[Object.keys(item)[0]];
+      addresses.push({
+        organisationName: item_data.ORGANISATION_NAME || null,
+        addressLine1: `${item_data.BUILDING_NAME || ''} ${item_data.BUILDING_NUMBER || ''} ${item_data.THOROUGHFARE_NAME || ''}`.trim(),
+        addressLine2: item_data.DEPENDENT_LOCALITY || null,
+        addressLine3: null,
+        locality: item_data.POST_TOWN || null,
+        postalCode: item_data.POSTCODE || null,
+        country: ENUMS.GEOSPATIAL_COUNTRIES[item_data.COUNTRY_CODE],
+      });
     });
 
     return addresses;

From 0b79772256a36fc8e5c9b38e3f67a0433984f2ab Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Wed, 10 Apr 2024 12:54:02 +0100
Subject: [PATCH 05/56] feat(DTFS2-7052): adding typescript include for json
 files, to satify lint. I added big examples to json files

---
 tsconfig.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tsconfig.json b/tsconfig.json
index 98d8c83e..8b8ce17a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -43,6 +43,7 @@
     "src",
     "test",
     "jest.config.ts",
+    "src/**/*.json",
   ],
   "exclude": [
     "node_modules",

From 1b6cf30cb4e960e64a2efb2b699aed75b61cd4d2 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Wed, 10 Apr 2024 12:58:59 +0100
Subject: [PATCH 06/56] feat(DTFS2-7052): trying to automate husky run on
 commit

---
 package.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 916ad6eb..506f1cec 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,8 @@
     "start:debug": "nest start --debug --watch",
     "start:dev": "nest start --tsc --watch",
     "start:prod": "node dist/src/main",
-    "unit-test": "jest --selectProjects=Unit"
+    "unit-test": "jest --selectProjects=Unit",
+    "prepare": "husky"
   },
   "lint-staged": {
     "**/package.json": "sort-package-json",

From 5fbf3d78f38c1ce7847b482112ce2815ba2e4c7b Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Wed, 10 Apr 2024 12:59:15 +0100
Subject: [PATCH 07/56] feat(DTFS2-7052): trying to automate husky run on
 commit

---
 package.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/package.json b/package.json
index 506f1cec..d5ef544c 100644
--- a/package.json
+++ b/package.json
@@ -11,13 +11,13 @@
     "housekeeping": "npm update --save --legacy-peer-deps && npm i --legacy-peer-deps && npm audit --fix && npm run spellcheck",
     "lint": "eslint . --ext .ts",
     "lint:fix": "eslint . --ext .ts --fix",
+    "prepare": "husky",
     "spellcheck": "cspell lint --gitignore --no-must-find-files --unique --no-progress --show-suggestions --color '**/*'",
     "start": "nest start",
     "start:debug": "nest start --debug --watch",
     "start:dev": "nest start --tsc --watch",
     "start:prod": "node dist/src/main",
-    "unit-test": "jest --selectProjects=Unit",
-    "prepare": "husky"
+    "unit-test": "jest --selectProjects=Unit"
   },
   "lint-staged": {
     "**/package.json": "sort-package-json",

From 889fe1b0c12152a2964afbf75cdede3ef1e6b48b Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Wed, 10 Apr 2024 14:16:20 +0100
Subject: [PATCH 08/56] feat(DTFS2-7052): change husky install to same way as
 in DTFS project

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index d5ef544c..731bb763 100644
--- a/package.json
+++ b/package.json
@@ -9,9 +9,9 @@
     "api-test": "jest --selectProjects=API",
     "build": "nest build -p tsconfig.build.json",
     "housekeeping": "npm update --save --legacy-peer-deps && npm i --legacy-peer-deps && npm audit --fix && npm run spellcheck",
+    "postinstall": "husky install",
     "lint": "eslint . --ext .ts",
     "lint:fix": "eslint . --ext .ts --fix",
-    "prepare": "husky",
     "spellcheck": "cspell lint --gitignore --no-must-find-files --unique --no-progress --show-suggestions --color '**/*'",
     "start": "nest start",
     "start:debug": "nest start --debug --watch",

From 21f800652587e12cf8cdcd457353b8a163221311 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 11 Apr 2024 09:14:36 +0100
Subject: [PATCH 09/56] feat(DTFS2-7052): work in progress of
 geospatial-get-address api tests

---
 ...addresses-ordnance-survey-response.dto.ts} |  10 +-
 ...rch-postcode-ordnance-survey-query.dto.ts} |   0
 .../ordnance-survey.service.ts                |   6 +-
 ...e.dto.ts => get-addresses-response.dto.ts} |   4 +-
 .../geospatial/geospatial.controller.ts       |   6 +-
 src/modules/geospatial/geospatial.service.ts  |   7 +-
 .../get-address-by-postcode.api-test.ts       | 213 ++++++++++++++++++
 .../get-geospatial-addresses-generator.ts     | 148 ++++++++++++
 .../generator/random-value-generator.ts       |  12 +
 9 files changed, 390 insertions(+), 16 deletions(-)
 rename src/helper-modules/ordnance-survey/dto/{get-addresses-response.dto.ts => get-addresses-ordnance-survey-response.dto.ts} (82%)
 rename src/helper-modules/ordnance-survey/dto/{get-search-postcode-query.dto.ts => get-search-postcode-ordnance-survey-query.dto.ts} (100%)
 rename src/modules/geospatial/dto/{get-search-addresses-response.dto.ts => get-addresses-response.dto.ts} (89%)
 create mode 100644 test/geospatial/get-address-by-postcode.api-test.ts
 create mode 100644 test/support/generator/get-geospatial-addresses-generator.ts

diff --git a/src/helper-modules/ordnance-survey/dto/get-addresses-response.dto.ts b/src/helper-modules/ordnance-survey/dto/get-addresses-ordnance-survey-response.dto.ts
similarity index 82%
rename from src/helper-modules/ordnance-survey/dto/get-addresses-response.dto.ts
rename to src/helper-modules/ordnance-survey/dto/get-addresses-ordnance-survey-response.dto.ts
index 755ef606..f1c5b1f5 100644
--- a/src/helper-modules/ordnance-survey/dto/get-addresses-response.dto.ts
+++ b/src/helper-modules/ordnance-survey/dto/get-addresses-ordnance-survey-response.dto.ts
@@ -1,4 +1,4 @@
-export type GetAddressResponse = {
+export type GetAddressOrdnanceSurveyResponse = {
   header: {
     uri: string;
     query: string;
@@ -12,14 +12,14 @@ export type GetAddressResponse = {
     lastupdate: string;
     output_srs: string;
   };
-  results?: GetAddressResponseItem[];
+  results?: GetAddressOrdnanceSurveyResponseItem[];
 };
 
-interface GetAddressResponseItem {
-  DPA: GetAddressResponseAddress;
+interface GetAddressOrdnanceSurveyResponseItem {
+  DPA: GetAddressOrdnanceSurveyResponseAddress;
 }
 
-interface GetAddressResponseAddress {
+interface GetAddressOrdnanceSurveyResponseAddress {
   UPRN: string;
   UDPRN: string;
   ADDRESS: string;
diff --git a/src/helper-modules/ordnance-survey/dto/get-search-postcode-query.dto.ts b/src/helper-modules/ordnance-survey/dto/get-search-postcode-ordnance-survey-query.dto.ts
similarity index 100%
rename from src/helper-modules/ordnance-survey/dto/get-search-postcode-query.dto.ts
rename to src/helper-modules/ordnance-survey/dto/get-search-postcode-ordnance-survey-query.dto.ts
diff --git a/src/helper-modules/ordnance-survey/ordnance-survey.service.ts b/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
index dfa35330..4dbd137c 100644
--- a/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
+++ b/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
@@ -5,7 +5,7 @@ import { KEY as ORDNANCE_SURVEY_CONFIG_KEY, OrdnanceSurveyConfig } from '@ukef/c
 import { GEOSPATIAL } from '@ukef/constants';
 import { HttpClient } from '@ukef/modules/http/http.client';
 
-import { GetAddressResponse } from './dto/get-addresses-response.dto';
+import { GetAddressOrdnanceSurveyResponse } from './dto/get-addresses-ordnance-survey-response.dto';
 // import { getCustomersNotFoundKnownOrdnanceSurveyError } from './known-errors';
 import { createWrapOrdnanceSurveyHttpGetErrorCallback } from './wrap-ordnance-survey-http-error-callback';
 
@@ -20,10 +20,10 @@ export class OrdnanceSurveyService {
     this.key = key;
   }
 
-  async getAddressesByPostcode(postcode): Promise<GetAddressResponse> {
+  async getAddressesByPostcode(postcode): Promise<GetAddressOrdnanceSurveyResponse> {
     const path = `/search/places/v1/postcode?postcode=${encodeURIComponent(postcode)}&lr=${GEOSPATIAL.DEFAULT.RESULT_LANGUAGE}&key=${encodeURIComponent(this.key)}`;
 
-    const { data } = await this.httpClient.get<GetAddressResponse>({
+    const { data } = await this.httpClient.get<GetAddressOrdnanceSurveyResponse>({
       path,
       headers: { 'Content-Type': 'application/json' },
       onError: createWrapOrdnanceSurveyHttpGetErrorCallback({
diff --git a/src/modules/geospatial/dto/get-search-addresses-response.dto.ts b/src/modules/geospatial/dto/get-addresses-response.dto.ts
similarity index 89%
rename from src/modules/geospatial/dto/get-search-addresses-response.dto.ts
rename to src/modules/geospatial/dto/get-addresses-response.dto.ts
index 5ba8a8c7..b458bf19 100644
--- a/src/modules/geospatial/dto/get-search-addresses-response.dto.ts
+++ b/src/modules/geospatial/dto/get-addresses-response.dto.ts
@@ -1,9 +1,9 @@
 import { ApiProperty } from '@nestjs/swagger';
 import { ENUMS, GEOSPATIAL } from '@ukef/constants';
 
-export type GetSearchAddressesResponse = GetSearchAddressesResponseItem[];
+export type GetAddressesResponse = GetAddressesResponseItem[];
 
-export class GetSearchAddressesResponseItem {
+export class GetAddressesResponseItem {
   @ApiProperty({
     description: 'Organisation name if available',
     example: 'CHURCHILL MUSEUM & CABINET WAR ROOMS',
diff --git a/src/modules/geospatial/geospatial.controller.ts b/src/modules/geospatial/geospatial.controller.ts
index 5c210dc5..4d1580b7 100644
--- a/src/modules/geospatial/geospatial.controller.ts
+++ b/src/modules/geospatial/geospatial.controller.ts
@@ -2,7 +2,7 @@ import { Controller, Get, Query } from '@nestjs/common';
 import { ApiNotFoundResponse, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
 
 import { GetAddressByPostcodeQueryDto } from './dto/get-address-by-postcode-query.dto';
-import { GetSearchAddressesResponse, GetSearchAddressesResponseItem } from './dto/get-search-addresses-response.dto';
+import { GetAddressesResponse, GetAddressesResponseItem } from './dto/get-addresses-response.dto';
 import { GeospatialService } from './geospatial.service';
 
 @ApiTags('geospatial')
@@ -17,12 +17,12 @@ export class GeospatialController {
   @ApiResponse({
     status: 200,
     description: 'Returns addresses from Ordanance survey Delivery Point Address (DPA) system.',
-    type: [GetSearchAddressesResponseItem],
+    type: [GetAddressesResponseItem],
   })
   @ApiNotFoundResponse({
     description: 'Customer not found.',
   })
-  getGeospatial(@Query() query: GetAddressByPostcodeQueryDto): Promise<GetSearchAddressesResponse> {
+  getGeospatial(@Query() query: GetAddressByPostcodeQueryDto): Promise<GetAddressesResponse> {
     return this.geospatialService.getAddressesByPostcode(query.postcode);
   }
 }
diff --git a/src/modules/geospatial/geospatial.service.ts b/src/modules/geospatial/geospatial.service.ts
index ca4f4a69..0c1b999e 100644
--- a/src/modules/geospatial/geospatial.service.ts
+++ b/src/modules/geospatial/geospatial.service.ts
@@ -1,16 +1,17 @@
 import { Injectable } from '@nestjs/common';
 import { ENUMS } from '@ukef/constants';
+import { GetAddressOrdnanceSurveyResponse } from '@ukef/helper-modules/ordnance-survey/dto/get-addresses-ordnance-survey-response.dto';
 import { OrdnanceSurveyService } from '@ukef/helper-modules/ordnance-survey/ordnance-survey.service';
 
-import { GetSearchAddressesResponse } from './dto/get-search-addresses-response.dto';
+import { GetAddressesResponse } from './dto/get-addresses-response.dto';
 
 @Injectable()
 export class GeospatialService {
   constructor(private readonly ordnanceSurveyService: OrdnanceSurveyService) {}
 
-  async getAddressesByPostcode(postcode: string): Promise<GetSearchAddressesResponse> {
+  async getAddressesByPostcode(postcode: string): Promise<GetAddressesResponse> {
     const addresses = [];
-    const response = await this.ordnanceSurveyService.getAddressesByPostcode(postcode);
+    const response: GetAddressOrdnanceSurveyResponse = await this.ordnanceSurveyService.getAddressesByPostcode(postcode);
 
     response.results.forEach((item) => {
       // Ordnance survey sends duplicated results with the welsh version too via 'CY'
diff --git a/test/geospatial/get-address-by-postcode.api-test.ts b/test/geospatial/get-address-by-postcode.api-test.ts
new file mode 100644
index 00000000..a1522d29
--- /dev/null
+++ b/test/geospatial/get-address-by-postcode.api-test.ts
@@ -0,0 +1,213 @@
+// import { CUSTOMERS, ENUMS } from '@ukef/constants';
+// import { IncorrectAuthArg, withClientAuthenticationTests } from '@ukef-test/common-tests/client-authentication-api-tests';
+import { Api } from '@ukef-test/support/api';
+// import { ENVIRONMENT_VARIABLES, TIME_EXCEEDING_INFORMATICA_TIMEOUT } from '@ukef-test/support/environment-variables';
+import { ENVIRONMENT_VARIABLES } from '@ukef-test/support/environment-variables';
+import { GetGeospatialAddressesGenerator } from '@ukef-test/support/generator/get-geospatial-addresses-generator';
+import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator';
+import nock from 'nock';
+
+describe('GET /geospatial/addresses/postcode?postcode=', () => {
+  const valueGenerator = new RandomValueGenerator();
+
+  let api: Api;
+
+  const { ordnanceSurveyPath, mdmPath, getAddressesByPostcodeResponse, getAddressOrdnanceSurveyResponse } = new GetGeospatialAddressesGenerator(
+    valueGenerator,
+  ).generate({
+    numberToGenerate: 2,
+  });
+
+  // const getMdmUrl = (postcode: string) => `/api/v1/geospatial/addresses/postcode?postcode=${encodeURIComponent(postcode)}`;
+
+  beforeAll(async () => {
+    api = await Api.create();
+  });
+
+  afterAll(async () => {
+    await api.destroy();
+  });
+
+  afterEach(() => {
+    nock.abortPendingRequests();
+    nock.cleanAll();
+  });
+
+  // withClientAuthenticationTests({
+  //   givenTheRequestWouldOtherwiseSucceed: () => {
+  //     requestToGetCustomers(mdmPath[0]).reply(200, getAddressesByPostcodeResponse[0]);
+  //   },
+  //   makeRequestWithoutAuth: (incorrectAuth?: IncorrectAuthArg) => api.getWithoutAuth(mdmPath[0], incorrectAuth?.headerName, incorrectAuth?.headerValue),
+  // });
+
+  it.only('returns a 200 response with the customers if they are returned by Informatica', async () => {
+    requestToGetCustomers(ordnanceSurveyPath[0]).reply(200, getAddressOrdnanceSurveyResponse[0]);
+
+    const { status, body } = await api.get(mdmPath[0]);
+
+    expect(status).toBe(200);
+    expect(body).toStrictEqual(getAddressesByPostcodeResponse[0]);
+  });
+
+  // it.each([
+  //   {
+  //     query: { name: CUSTOMERS.EXAMPLES.NAME, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.YES },
+  //   },
+  //   {
+  //     query: { companyReg: CUSTOMERS.EXAMPLES.COMPANYREG, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.YES },
+  //   },
+  //   {
+  //     query: { partyUrn: CUSTOMERS.EXAMPLES.PARTYURN, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.YES },
+  //   },
+  //   {
+  //     query: { name: CUSTOMERS.EXAMPLES.NAME, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.NO },
+  //   },
+  //   {
+  //     query: { companyReg: CUSTOMERS.EXAMPLES.COMPANYREG, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.NO },
+  //   },
+  //   {
+  //     query: { partyUrn: CUSTOMERS.EXAMPLES.PARTYURN, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.NO },
+  //   },
+  //   {
+  //     query: { name: CUSTOMERS.EXAMPLES.NAME, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.LEGACY_ONLY },
+  //   },
+  //   {
+  //     query: { companyReg: CUSTOMERS.EXAMPLES.COMPANYREG, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.LEGACY_ONLY },
+  //   },
+  //   {
+  //     query: { partyUrn: CUSTOMERS.EXAMPLES.PARTYURN, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.LEGACY_ONLY },
+  //   },
+  //   {
+  //     query: { name: CUSTOMERS.EXAMPLES.NAME },
+  //   },
+  //   {
+  //     query: { companyReg: CUSTOMERS.EXAMPLES.COMPANYREG },
+  //   },
+  //   {
+  //     query: { partyUrn: CUSTOMERS.EXAMPLES.PARTYURN },
+  //   },
+  // ])('returns a 200 response with the customers if query is "$query"', async ({ query }) => {
+  //   const { mdmPath, informaticaPath, getCustomersResponse } = new GetCustomersGenerator(valueGenerator).generate({
+  //     numberToGenerate: 1,
+  //     query,
+  //   });
+  //   requestToGetCustomers(informaticaPath).reply(200, getCustomersResponse[0]);
+
+  //   const { status, body } = await api.get(mdmPath);
+
+  //   expect(status).toBe(200);
+  //   expect(body).toStrictEqual(getCustomersResponse[0]);
+  // });
+
+  // it('returns a 404 response if Informatica returns a 404 response with the string "null"', async () => {
+  //   requestToGetCustomers(informaticaPath).reply(404, [
+  //     {
+  //       errorCode: '404',
+  //       errorDateTime: '2023-06-30T13:41:33Z',
+  //       errorMessage: 'Company registration not found',
+  //       errorDescription: 'Party details request for the requested company registration not found.',
+  //     },
+  //   ]);
+
+  //   const { status, body } = await api.get(mdmPath);
+
+  //   expect(status).toBe(404);
+  //   expect(body).toStrictEqual({
+  //     statusCode: 404,
+  //     message: 'Customer not found.',
+  //   });
+  // });
+
+  // it('returns a 500 response if Informatica returns a status code that is NOT 200', async () => {
+  //   requestToGetCustomers(informaticaPath).reply(401);
+
+  //   const { status, body } = await api.get(mdmPath);
+
+  //   expect(status).toBe(500);
+  //   expect(body).toStrictEqual({
+  //     statusCode: 500,
+  //     message: 'Internal server error',
+  //   });
+  // });
+
+  // it('returns a 500 response if getting the facility investors from ACBS times out', async () => {
+  //   requestToGetCustomers(informaticaPath).delay(TIME_EXCEEDING_INFORMATICA_TIMEOUT).reply(200, getCustomersResponse[0]);
+
+  //   const { status, body } = await api.get(mdmPath);
+
+  //   expect(status).toBe(500);
+  //   expect(body).toStrictEqual({
+  //     statusCode: 500,
+  //     message: 'Internal server error',
+  //   });
+  // });
+
+  // it.each([
+  //   {
+  //     query: { name: valueGenerator.string({ length: 1 }) },
+  //     expectedError: 'name must be longer than or equal to 2 characters',
+  //   },
+  //   {
+  //     query: { name: valueGenerator.string({ length: 256 }) },
+  //     expectedError: 'name must be shorter than or equal to 255 characters',
+  //   },
+  //   {
+  //     query: { name: valueGenerator.word(), extraParameter: valueGenerator.word() },
+  //     expectedError: 'property extraParameter should not exist',
+  //   },
+  //   {
+  //     query: { companyReg: valueGenerator.string({ length: 7 }) },
+  //     expectedError: 'companyReg must be longer than or equal to 8 characters',
+  //   },
+  //   {
+  //     query: { companyReg: valueGenerator.string({ length: 11 }) },
+  //     expectedError: 'companyReg must be shorter than or equal to 10 characters',
+  //   },
+  //   {
+  //     query: { partyUrn: valueGenerator.stringOfNumericCharacters({ length: 7 }) },
+  //     expectedError: 'partyUrn must match /^\\d{8}$/ regular expression',
+  //   },
+  //   {
+  //     query: { partyUrn: valueGenerator.stringOfNumericCharacters({ length: 9 }) },
+  //     expectedError: 'partyUrn must match /^\\d{8}$/ regular expression',
+  //   },
+  //   {
+  //     query: { partyUrn: valueGenerator.word() },
+  //     expectedError: 'partyUrn must match /^\\d{8}$/ regular expression',
+  //   },
+  // ])('returns a 400 response with error array if query is "$query"', async ({ query, expectedError }) => {
+  //   const { status, body } = await api.get(getMdmUrl(query));
+
+  //   expect(status).toBe(400);
+  //   expect(body).toMatchObject({
+  //     error: 'Bad Request',
+  //     message: expect.arrayContaining([expectedError]),
+  //     statusCode: 400,
+  //   });
+  // });
+
+  // it.each([
+  //   {
+  //     query: {},
+  //     expectedError: 'One and just one search parameter is required',
+  //   },
+  //   {
+  //     query: { name: valueGenerator.word(), companyReg: valueGenerator.string({ length: 8 }) },
+  //     expectedError: 'One and just one search parameter is required',
+  //   },
+  // ])('returns a 400 response with error string if query is "$query"', async ({ query, expectedError }) => {
+  //   const { status, body } = await api.get(getMdmUrl(query));
+
+  //   expect(status).toBe(400);
+  //   expect(body).toMatchObject({
+  //     error: 'Bad Request',
+  //     message: expectedError,
+  //     statusCode: 400,
+  //   });
+  // });
+
+  const basicAuth = Buffer.from(`${ENVIRONMENT_VARIABLES.APIM_INFORMATICA_USERNAME}:${ENVIRONMENT_VARIABLES.APIM_INFORMATICA_PASSWORD}`).toString('base64');
+
+  const requestToGetCustomers = (informaticaPath: string): nock.Interceptor =>
+    nock(ENVIRONMENT_VARIABLES.APIM_INFORMATICA_URL).get(informaticaPath).matchHeader('authorization', `Basic ${basicAuth}`);
+});
diff --git a/test/support/generator/get-geospatial-addresses-generator.ts b/test/support/generator/get-geospatial-addresses-generator.ts
new file mode 100644
index 00000000..e24fab8a
--- /dev/null
+++ b/test/support/generator/get-geospatial-addresses-generator.ts
@@ -0,0 +1,148 @@
+import { ENUMS, GEOSPATIAL } from '@ukef/constants';
+import { GetAddressOrdnanceSurveyResponse } from '@ukef/helper-modules/ordnance-survey/dto/get-addresses-ordnance-survey-response.dto';
+import { GetAddressByPostcodeQueryDto } from '@ukef/modules/geospatial/dto/get-address-by-postcode-query.dto';
+import { GetAddressesResponse } from '@ukef/modules/geospatial/dto/get-addresses-response.dto';
+
+import { AbstractGenerator } from './abstract-generator';
+import { RandomValueGenerator } from './random-value-generator';
+
+export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressValues, GenerateResult, GenerateOptions> {
+  constructor(protected readonly valueGenerator: RandomValueGenerator) {
+    super(valueGenerator);
+  }
+
+  protected generateValues(): AddressValues {
+    return {
+      ORGANISATION_NAME: this.valueGenerator.word({ length: 5 }),
+      BUILDING_NAME: this.valueGenerator.word(),
+      BUILDING_NUMBER: this.valueGenerator.nonnegativeInteger().toString(),
+      THOROUGHFARE_NAME: this.valueGenerator.word({ length: 7 }),
+      DEPENDENT_LOCALITY: this.valueGenerator.word(),
+      POST_TOWN: this.valueGenerator.word(),
+      POSTCODE: this.valueGenerator.postcode(),
+      COUNTRY_CODE: this.valueGenerator.enumValue(ENUMS.GEOSPATIAL_COUNTRIES),
+    };
+  }
+
+  protected transformRawValuesToGeneratedValues(values: AddressValues[], { postcode, key }: GenerateOptions): GenerateResult {
+    const useKey = key || 'test';
+    // let request: GetAddressByPostcodeQueryDto = ;
+    // let ordnanceSurveyRequest: GetCustomersInformaticaQueryDto[];
+    // let ordnanceSurveyPath: 'test',
+    // let mdmPath: 'test',
+    // let getAddressesByPostcodeResponse: GetAddressesResponse[];
+
+    const request: GetAddressByPostcodeQueryDto[] = values.map((v) => ({ postcode: postcode || v.POSTCODE }) as GetAddressByPostcodeQueryDto);
+
+    const ordnanceSurveyPath: string[] = values.map((v) => {
+      const usePostcode = postcode || v.POSTCODE;
+      return `/search/places/v1/postcode?postcode=${encodeURIComponent(usePostcode)}&lr=${GEOSPATIAL.DEFAULT.RESULT_LANGUAGE}&key=${encodeURIComponent(useKey)}`;
+    });
+
+    const mdmPath: string[] = values.map((v) => {
+      const usePostcode = postcode || v.POSTCODE;
+      return `/api/v1/geospatial/addresses/postcode?postcode=${usePostcode}`;
+    });
+
+    const getAddressesByPostcodeResponse: GetAddressesResponse[] = values.map((v) => [
+      {
+        organisationName: v.ORGANISATION_NAME,
+        addressLine1: `${v.BUILDING_NAME} ${v.BUILDING_NUMBER} ${v.THOROUGHFARE_NAME}`,
+        addressLine2: v.DEPENDENT_LOCALITY,
+        addressLine3: null,
+        locality: v.POST_TOWN || null,
+        postalCode: v.POSTCODE || null,
+        country: ENUMS.GEOSPATIAL_COUNTRIES[v.COUNTRY_CODE],
+      },
+    ]);
+
+    const getAddressOrdnanceSurveyResponse: GetAddressOrdnanceSurveyResponse[] = values.map((v) => ({
+      header: {
+        uri: 'test',
+        query: 'test',
+        offset: 0,
+        totalresults: 1,
+        format: 'test',
+        dataset: 'test',
+        lr: 'test',
+        maxresults: 100,
+        epoch: 'test',
+        lastupdate: 'test',
+        output_srs: 'test',
+      },
+      results: [
+        {
+          DPA: {
+            UPRN: 'test',
+            UDPRN: 'test',
+            ADDRESS: 'test',
+            BUILDING_NAME: v.BUILDING_NAME,
+            BUILDING_NUMBER: v.BUILDING_NUMBER,
+            ORGANISATION_NAME: v.ORGANISATION_NAME,
+            DEPENDENT_LOCALITY: v.DEPENDENT_LOCALITY,
+            THOROUGHFARE_NAME: v.THOROUGHFARE_NAME,
+            POST_TOWN: v.POST_TOWN,
+            POSTCODE: v.POSTCODE,
+            RPC: 'test',
+            X_COORDINATE: 12345,
+            Y_COORDINATE: 12345,
+            STATUS: 'test',
+            LOGICAL_STATUS_CODE: 'test',
+            CLASSIFICATION_CODE: 'test',
+            CLASSIFICATION_CODE_DESCRIPTION: 'test',
+            LOCAL_CUSTODIAN_CODE: 12345,
+            LOCAL_CUSTODIAN_CODE_DESCRIPTION: 'test',
+            COUNTRY_CODE: v.COUNTRY_CODE,
+            COUNTRY_CODE_DESCRIPTION: 'test',
+            POSTAL_ADDRESS_CODE: 'test',
+            POSTAL_ADDRESS_CODE_DESCRIPTION: 'test',
+            BLPU_STATE_CODE: 'test',
+            BLPU_STATE_CODE_DESCRIPTION: 'test',
+            TOPOGRAPHY_LAYER_TOID: 'test',
+            LAST_UPDATE_DATE: 'test',
+            ENTRY_DATE: 'test',
+            BLPU_STATE_DATE: 'test',
+            LANGUAGE: 'test',
+            MATCH: 12345,
+            MATCH_DESCRIPTION: 'test',
+            DELIVERY_POINT_SUFFIX: 'test',
+          },
+        },
+      ],
+    }));
+
+    return {
+      request,
+      // ordnanceSurveyRequest,
+      ordnanceSurveyPath,
+      mdmPath,
+      getAddressesByPostcodeResponse,
+      getAddressOrdnanceSurveyResponse,
+    };
+  }
+}
+
+interface AddressValues {
+  ORGANISATION_NAME: string;
+  BUILDING_NAME: string;
+  BUILDING_NUMBER: string;
+  THOROUGHFARE_NAME: string;
+  DEPENDENT_LOCALITY: string;
+  POST_TOWN: string;
+  POSTCODE: string;
+  COUNTRY_CODE: string;
+}
+
+interface GenerateOptions {
+  postcode?: string;
+  key?: string;
+}
+
+interface GenerateResult {
+  request: GetAddressByPostcodeQueryDto[];
+  //ordnanceSurveyRequest: GetCustomersInformaticaQueryDto[];
+  ordnanceSurveyPath: string[];
+  mdmPath: string[];
+  getAddressesByPostcodeResponse: GetAddressesResponse[];
+  getAddressOrdnanceSurveyResponse: GetAddressOrdnanceSurveyResponse[];
+}
diff --git a/test/support/generator/random-value-generator.ts b/test/support/generator/random-value-generator.ts
index ff1c1983..612f9d59 100644
--- a/test/support/generator/random-value-generator.ts
+++ b/test/support/generator/random-value-generator.ts
@@ -1,5 +1,8 @@
 import { Chance } from 'chance';
 
+interface Enum {
+  [key: number | string]: string | number;
+}
 export class RandomValueGenerator {
   private static readonly seed = 0;
   private readonly chance: Chance.Chance;
@@ -63,4 +66,13 @@ export class RandomValueGenerator {
   nonnegativeInteger({ max }: { max?: number } = {}): number {
     return this.integer({ min: 0, max });
   }
+
+  postcode(): string {
+    return this.chance.postcode();
+  }
+
+  enumValue<T = string>(theEnum: Enum): T {
+    const possibleValues = Object.values(theEnum);
+    return possibleValues[this.integer({ min: 0, max: possibleValues.length - 1 })] as T;
+  }
 }

From 32593478c1939697f5d75cea3917716d64711d9d Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 11 Apr 2024 13:37:15 +0100
Subject: [PATCH 10/56] feat(DTFS2-7052): package update

---
 package-lock.json | 3054 ++++++++++++++++++++++++---------------------
 package.json      |   68 +-
 2 files changed, 1657 insertions(+), 1465 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index b8a3a398..ad4b8d2d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,22 +7,23 @@
     "": {
       "name": "mdm-api",
       "version": "1.17.0",
+      "hasInstallScript": true,
       "license": "MIT",
       "dependencies": {
-        "@nestjs/axios": "^3.0.1",
-        "@nestjs/common": "^10.3.1",
-        "@nestjs/config": "^3.1.1",
-        "@nestjs/core": "^10.3.1",
+        "@nestjs/axios": "^3.0.2",
+        "@nestjs/common": "^10.3.7",
+        "@nestjs/config": "^3.2.2",
+        "@nestjs/core": "^10.3.7",
         "@nestjs/passport": "^10.0.3",
-        "@nestjs/platform-express": "^10.3.1",
-        "@nestjs/swagger": "^7.2.0",
-        "@nestjs/terminus": "^10.2.1",
-        "@nestjs/typeorm": "^10.0.1",
-        "axios": "^1.6.7",
+        "@nestjs/platform-express": "^10.3.7",
+        "@nestjs/swagger": "^7.3.1",
+        "@nestjs/terminus": "^10.2.3",
+        "@nestjs/typeorm": "^10.0.2",
+        "axios": "^1.6.8",
         "class-transformer": "^0.5.1",
         "class-validator": "^0.14.1",
         "compression": "^1.7.4",
-        "date-fns": "^3.3.1",
+        "date-fns": "^3.6.0",
         "dotenv": "^16.4.1",
         "express-basic-auth": "^1.2.1",
         "lodash": "^4.17.21",
@@ -32,33 +33,33 @@
         "passport-headerapikey": "^1.2.2",
         "pino-http": "^9.0.0",
         "pino-pretty": "^10.3.1",
-        "reflect-metadata": "^0.2.1",
+        "reflect-metadata": "^0.2.2",
         "rxjs": "^7.8.1",
         "tsconfig-paths": "^4.2.0",
         "tslib": "^2.6.2",
         "typeorm": "^0.3.20",
-        "typeorm-extension": "^3.4.0",
-        "typescript": "^5.3.3"
+        "typeorm-extension": "^3.5.1",
+        "typescript": "^5.4.5"
       },
       "devDependencies": {
-        "@commitlint/cli": "^18.6.0",
-        "@commitlint/config-conventional": "^18.6.0",
-        "@nestjs/cli": "^10.3.0",
-        "@nestjs/schematics": "^10.1.0",
-        "@nestjs/testing": "^10.3.1",
-        "@tsconfig/node21": "^21.0.1",
+        "@commitlint/cli": "^18.6.1",
+        "@commitlint/config-conventional": "^18.6.3",
+        "@nestjs/cli": "^10.3.2",
+        "@nestjs/schematics": "^10.1.1",
+        "@nestjs/testing": "^10.3.7",
+        "@tsconfig/node21": "^21.0.3",
         "@types/chance": "^1.1.6",
         "@types/compression": "^1.7.5",
         "@types/express": "^4.17.21",
-        "@types/jest": "^29.5.11",
-        "@types/lodash": "^4.14.202",
-        "@types/node": "^20.11.10",
+        "@types/jest": "^29.5.12",
+        "@types/lodash": "^4.17.0",
+        "@types/node": "^20.12.7",
         "@types/supertest": "^6.0.2",
-        "@typescript-eslint/eslint-plugin": "^6.19.1",
-        "@typescript-eslint/parser": "^6.19.1",
+        "@typescript-eslint/eslint-plugin": "^6.21.0",
+        "@typescript-eslint/parser": "^6.21.0",
         "chance": "^1.1.11",
-        "cspell": "^8.3.2",
-        "eslint": "^8.56.0",
+        "cspell": "^8.7.0",
+        "eslint": "^8.57.0",
         "eslint-config-airbnb-base": "^15.0.0",
         "eslint-config-airbnb-typescript": "^17.1.0",
         "eslint-config-airbnb-typescript-prettier": "^5.0.0",
@@ -67,22 +68,22 @@
         "eslint-plugin-deprecation": "^2.0.0",
         "eslint-plugin-eslint-comments": "^3.2.0",
         "eslint-plugin-import": "^2.29.1",
-        "eslint-plugin-jest": "^27.6.3",
+        "eslint-plugin-jest": "^27.9.0",
         "eslint-plugin-jest-formatting": "^3.1.0",
         "eslint-plugin-node": "^11.1.0",
         "eslint-plugin-optimize-regex": "^1.2.1",
         "eslint-plugin-prettier": "^5.1.3",
-        "eslint-plugin-security": "^2.1.0",
+        "eslint-plugin-security": "^2.1.1",
         "eslint-plugin-simple-import-sort": "^10.0.0",
         "eslint-plugin-switch-case": "^1.1.2",
-        "eslint-plugin-unused-imports": "^3.0.0",
-        "husky": "^9.0.6",
+        "eslint-plugin-unused-imports": "^3.1.0",
+        "husky": "^9.0.11",
         "jest": "29.7.0",
         "jest-when": "^3.6.0",
-        "lint-staged": "^15.2.0",
-        "nock": "^13.5.1",
-        "prettier": "^3.2.4",
-        "sort-package-json": "^2.6.0",
+        "lint-staged": "^15.2.2",
+        "nock": "^13.5.4",
+        "prettier": "^3.2.5",
+        "sort-package-json": "^2.10.0",
         "source-map-support": "^0.5.21",
         "supertest": "^6.3.4",
         "ts-jest": "29.1.2",
@@ -104,22 +105,22 @@
       }
     },
     "node_modules/@ampproject/remapping": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
-      "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+      "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
       "dev": true,
       "dependencies": {
-        "@jridgewell/gen-mapping": "^0.3.0",
-        "@jridgewell/trace-mapping": "^0.3.9"
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.24"
       },
       "engines": {
         "node": ">=6.0.0"
       }
     },
     "node_modules/@angular-devkit/core": {
-      "version": "17.0.9",
-      "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.9.tgz",
-      "integrity": "sha512-r5jqwpWOgowqe9KSDqJ3iSbmsEt2XPjSvRG4DSI2T9s31bReoMtreo8b7wkRa2B3hbcDnstFbn8q27VvJDqRaQ==",
+      "version": "17.1.2",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.1.2.tgz",
+      "integrity": "sha512-ku+/W/HMCBacSWFppenr9y6Lx8mDuTuQvn1IkTyBLiJOpWnzgVbx9kHDeaDchGa1PwLlJUBBrv27t3qgJOIDPw==",
       "dev": true,
       "dependencies": {
         "ajv": "8.12.0",
@@ -143,35 +144,13 @@
         }
       }
     },
-    "node_modules/@angular-devkit/core/node_modules/ajv": {
-      "version": "8.12.0",
-      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
-      "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
-      "dev": true,
-      "dependencies": {
-        "fast-deep-equal": "^3.1.1",
-        "json-schema-traverse": "^1.0.0",
-        "require-from-string": "^2.0.2",
-        "uri-js": "^4.2.2"
-      },
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/epoberezkin"
-      }
-    },
-    "node_modules/@angular-devkit/core/node_modules/json-schema-traverse": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
-      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
-      "dev": true
-    },
     "node_modules/@angular-devkit/schematics": {
-      "version": "17.0.9",
-      "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.0.9.tgz",
-      "integrity": "sha512-5ti7g45F2KjDJS0DbgnOGI1GyKxGpn4XsKTYJFJrSAWj6VpuvPy/DINRrXNuRVo09VPEkqA+IW7QwaG9icptQg==",
+      "version": "17.1.2",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.1.2.tgz",
+      "integrity": "sha512-8S9RuM8olFN/gwN+mjbuF1CwHX61f0i59EGXz9tXLnKRUTjsRR+8vVMTAmX0dvVAT5fJTG/T69X+HX7FeumdqA==",
       "dev": true,
       "dependencies": {
-        "@angular-devkit/core": "17.0.9",
+        "@angular-devkit/core": "17.1.2",
         "jsonc-parser": "3.2.0",
         "magic-string": "0.30.5",
         "ora": "5.4.1",
@@ -184,15 +163,15 @@
       }
     },
     "node_modules/@angular-devkit/schematics-cli": {
-      "version": "17.0.9",
-      "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-17.0.9.tgz",
-      "integrity": "sha512-tznzzB26sy8jVUlV9HhXcbFYZcIIFMAiDMOuyLko2LZFjfoqW+OPvwa1mwAQwvVVSQZVAKvdndFhzwyl/axwFQ==",
+      "version": "17.1.2",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-17.1.2.tgz",
+      "integrity": "sha512-bvXykYzSST05qFdlgIzUguNOb3z0hCa8HaTwtqdmQo9aFPf+P+/AC56I64t1iTchMjQtf3JrBQhYM25gUdcGbg==",
       "dev": true,
       "dependencies": {
-        "@angular-devkit/core": "17.0.9",
-        "@angular-devkit/schematics": "17.0.9",
+        "@angular-devkit/core": "17.1.2",
+        "@angular-devkit/schematics": "17.1.2",
         "ansi-colors": "4.1.3",
-        "inquirer": "9.2.11",
+        "inquirer": "9.2.12",
         "symbol-observable": "4.0.0",
         "yargs-parser": "21.1.1"
       },
@@ -264,12 +243,12 @@
       }
     },
     "node_modules/@angular-devkit/schematics-cli/node_modules/inquirer": {
-      "version": "9.2.11",
-      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.11.tgz",
-      "integrity": "sha512-B2LafrnnhbRzCWfAdOXisUzL89Kg8cVJlYmhqoi3flSiV/TveO+nsXwgKr9h9PIo+J1hz7nBSk6gegRIMBBf7g==",
+      "version": "9.2.12",
+      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz",
+      "integrity": "sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==",
       "dev": true,
       "dependencies": {
-        "@ljharb/through": "^2.3.9",
+        "@ljharb/through": "^2.3.11",
         "ansi-escapes": "^4.3.2",
         "chalk": "^5.3.0",
         "cli-cursor": "^3.1.0",
@@ -357,112 +336,178 @@
       }
     },
     "node_modules/@azure/core-auth": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.5.0.tgz",
-      "integrity": "sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw==",
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.7.2.tgz",
+      "integrity": "sha512-Igm/S3fDYmnMq1uKS38Ae1/m37B3zigdlZw+kocwEhh5GjyKjPrXKO2J6rzpC1wAxrNil/jX9BJRqBshyjnF3g==",
       "dependencies": {
-        "@azure/abort-controller": "^1.0.0",
+        "@azure/abort-controller": "^2.0.0",
         "@azure/core-util": "^1.1.0",
-        "tslib": "^2.2.0"
+        "tslib": "^2.6.2"
       },
       "engines": {
-        "node": ">=14.0.0"
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@azure/core-auth/node_modules/@azure/abort-controller": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
+      "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
+      "dependencies": {
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18.0.0"
       }
     },
     "node_modules/@azure/core-client": {
-      "version": "1.7.3",
-      "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.7.3.tgz",
-      "integrity": "sha512-kleJ1iUTxcO32Y06dH9Pfi9K4U+Tlb111WXEnbt7R/ne+NLRwppZiTGJuTD5VVoxTMK5NTbEtm5t2vcdNCFe2g==",
+      "version": "1.9.2",
+      "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.2.tgz",
+      "integrity": "sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==",
       "dependencies": {
-        "@azure/abort-controller": "^1.0.0",
+        "@azure/abort-controller": "^2.0.0",
         "@azure/core-auth": "^1.4.0",
         "@azure/core-rest-pipeline": "^1.9.1",
         "@azure/core-tracing": "^1.0.0",
-        "@azure/core-util": "^1.0.0",
+        "@azure/core-util": "^1.6.1",
         "@azure/logger": "^1.0.0",
-        "tslib": "^2.2.0"
+        "tslib": "^2.6.2"
       },
       "engines": {
-        "node": ">=14.0.0"
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@azure/core-client/node_modules/@azure/abort-controller": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
+      "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
+      "dependencies": {
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18.0.0"
       }
     },
     "node_modules/@azure/core-http-compat": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-1.3.0.tgz",
-      "integrity": "sha512-ZN9avruqbQ5TxopzG3ih3KRy52n8OAbitX3fnZT5go4hzu0J+KVPSzkL+Wt3hpJpdG8WIfg1sBD1tWkgUdEpBA==",
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.1.2.tgz",
+      "integrity": "sha512-5MnV1yqzZwgNLLjlizsU3QqOeQChkIXw781Fwh1xdAqJR5AA32IUaq6xv1BICJvfbHoa+JYcaij2HFkhLbNTJQ==",
       "dependencies": {
-        "@azure/abort-controller": "^1.0.4",
+        "@azure/abort-controller": "^2.0.0",
         "@azure/core-client": "^1.3.0",
         "@azure/core-rest-pipeline": "^1.3.0"
       },
       "engines": {
-        "node": ">=12.0.0"
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@azure/core-http-compat/node_modules/@azure/abort-controller": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
+      "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
+      "dependencies": {
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18.0.0"
       }
     },
     "node_modules/@azure/core-lro": {
-      "version": "2.5.4",
-      "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.4.tgz",
-      "integrity": "sha512-3GJiMVH7/10bulzOKGrrLeG/uCBH/9VtxqaMcB9lIqAeamI/xYQSHJL/KcsLDuH+yTjYpro/u6D/MuRe4dN70Q==",
+      "version": "2.7.2",
+      "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz",
+      "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==",
       "dependencies": {
-        "@azure/abort-controller": "^1.0.0",
+        "@azure/abort-controller": "^2.0.0",
         "@azure/core-util": "^1.2.0",
         "@azure/logger": "^1.0.0",
-        "tslib": "^2.2.0"
+        "tslib": "^2.6.2"
       },
       "engines": {
-        "node": ">=14.0.0"
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@azure/core-lro/node_modules/@azure/abort-controller": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
+      "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
+      "dependencies": {
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18.0.0"
       }
     },
     "node_modules/@azure/core-paging": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.5.0.tgz",
-      "integrity": "sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==",
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz",
+      "integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==",
       "dependencies": {
-        "tslib": "^2.2.0"
+        "tslib": "^2.6.2"
       },
       "engines": {
-        "node": ">=14.0.0"
+        "node": ">=18.0.0"
       }
     },
     "node_modules/@azure/core-rest-pipeline": {
-      "version": "1.13.0",
-      "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.13.0.tgz",
-      "integrity": "sha512-a62aP/wppgmnfIkJLfcB4ssPBcH94WzrzPVJ3tlJt050zX4lfmtnvy95D3igDo3f31StO+9BgPrzvkj4aOxnoA==",
+      "version": "1.15.2",
+      "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.15.2.tgz",
+      "integrity": "sha512-BmWfpjc/QXc2ipHOh6LbUzp3ONCaa6xzIssTU0DwH9bbYNXJlGUL6tujx5TrbVd/QQknmS+vlQJGrCq2oL1gZA==",
       "dependencies": {
-        "@azure/abort-controller": "^1.1.0",
+        "@azure/abort-controller": "^2.0.0",
         "@azure/core-auth": "^1.4.0",
         "@azure/core-tracing": "^1.0.1",
         "@azure/core-util": "^1.3.0",
         "@azure/logger": "^1.0.0",
-        "http-proxy-agent": "^5.0.0",
-        "https-proxy-agent": "^5.0.0",
-        "tslib": "^2.2.0"
+        "http-proxy-agent": "^7.0.0",
+        "https-proxy-agent": "^7.0.0",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@azure/core-rest-pipeline/node_modules/@azure/abort-controller": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
+      "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
+      "dependencies": {
+        "tslib": "^2.6.2"
       },
       "engines": {
         "node": ">=18.0.0"
       }
     },
     "node_modules/@azure/core-tracing": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz",
-      "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==",
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.1.2.tgz",
+      "integrity": "sha512-dawW9ifvWAWmUm9/h+/UQ2jrdvjCJ7VJEuCJ6XVNudzcOwm53BFZH4Q845vjfgoUAM8ZxokvVNxNxAITc502YA==",
       "dependencies": {
-        "tslib": "^2.2.0"
+        "tslib": "^2.6.2"
       },
       "engines": {
-        "node": ">=12.0.0"
+        "node": ">=18.0.0"
       }
     },
     "node_modules/@azure/core-util": {
-      "version": "1.6.1",
-      "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.6.1.tgz",
-      "integrity": "sha512-h5taHeySlsV9qxuK64KZxy4iln1BtMYlNt5jbuEFN3UFSAd1EwKg/Gjl5a6tZ/W8t6li3xPnutOx7zbDyXnPmQ==",
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz",
+      "integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==",
       "dependencies": {
-        "@azure/abort-controller": "^1.0.0",
-        "tslib": "^2.2.0"
+        "@azure/abort-controller": "^2.0.0",
+        "tslib": "^2.6.2"
       },
       "engines": {
-        "node": ">=16.0.0"
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@azure/core-util/node_modules/@azure/abort-controller": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
+      "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
+      "dependencies": {
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18.0.0"
       }
     },
     "node_modules/@azure/identity": {
@@ -490,14 +535,14 @@
       }
     },
     "node_modules/@azure/keyvault-keys": {
-      "version": "4.7.2",
-      "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.7.2.tgz",
-      "integrity": "sha512-VdIH6PjbQ3J5ntK+xeI8eOe1WsDxF9ndXw8BPR/9MZVnIj0vQNtNCS6gpR7EFQeGcs8XjzMfHm0AvKGErobqJQ==",
+      "version": "4.8.0",
+      "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.8.0.tgz",
+      "integrity": "sha512-jkuYxgkw0aaRfk40OQhFqDIupqblIOIlYESWB6DKCVDxQet1pyv86Tfk9M+5uFM0+mCs6+MUHU+Hxh3joiUn4Q==",
       "dependencies": {
         "@azure/abort-controller": "^1.0.0",
         "@azure/core-auth": "^1.3.0",
         "@azure/core-client": "^1.5.0",
-        "@azure/core-http-compat": "^1.3.0",
+        "@azure/core-http-compat": "^2.0.1",
         "@azure/core-lro": "^2.2.0",
         "@azure/core-paging": "^1.1.1",
         "@azure/core-rest-pipeline": "^1.8.1",
@@ -507,45 +552,45 @@
         "tslib": "^2.2.0"
       },
       "engines": {
-        "node": ">=14.0.0"
+        "node": ">=18.0.0"
       }
     },
     "node_modules/@azure/logger": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz",
-      "integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==",
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.1.2.tgz",
+      "integrity": "sha512-l170uE7bsKpIU6B/giRc9i4NI0Mj+tANMMMxf7Zi/5cKzEqPayP7+X1WPrG7e+91JgY8N+7K7nF2WOi7iVhXvg==",
       "dependencies": {
-        "tslib": "^2.2.0"
+        "tslib": "^2.6.2"
       },
       "engines": {
-        "node": ">=14.0.0"
+        "node": ">=18.0.0"
       }
     },
     "node_modules/@azure/msal-browser": {
-      "version": "3.7.1",
-      "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.7.1.tgz",
-      "integrity": "sha512-EZnk81zn1/5/jv/VVN2Tp+dUVchHmwbbt7pn654Eqa+ua7wtEIg1btuW/mowB13BV2nGYcvniY9Mf+3Sbe0cCg==",
+      "version": "3.11.1",
+      "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.11.1.tgz",
+      "integrity": "sha512-tZFJnP5ZpgkmazSriEDW+Xl3/4WI823uhnYhWCHPkGywFWEZoPA5VkiCK8x4x8ECXp3mGr5qEI82MU43PBiaKA==",
       "dependencies": {
-        "@azure/msal-common": "14.6.1"
+        "@azure/msal-common": "14.8.1"
       },
       "engines": {
         "node": ">=0.8.0"
       }
     },
     "node_modules/@azure/msal-common": {
-      "version": "14.6.1",
-      "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.6.1.tgz",
-      "integrity": "sha512-yL97p2La0WrgU3MdXThOLOpdmBMvH8J69vwQ/skOqORYwOW/UYPdp9nZpvvfBO+zFZB5M3JkqA2NKtn4GfVBHw==",
+      "version": "14.8.1",
+      "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.8.1.tgz",
+      "integrity": "sha512-9HfBMDTIgtFFkils+o6gO/aGEoLLuc4z+QLLfhy/T1bTNPiVsX/9CjaBPMZGnMltN/IlMkU5SGGNggGh55p5xA==",
       "engines": {
         "node": ">=0.8.0"
       }
     },
     "node_modules/@azure/msal-node": {
-      "version": "2.6.2",
-      "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.6.2.tgz",
-      "integrity": "sha512-XyP+5lUZxTpWpLCC2wAFGA9wXrUhHp1t4NLmQW0mQZzUdcSay3rG7kGGqxxeLf8mRdwoR0B70TCLmIGX6cfK/g==",
+      "version": "2.6.6",
+      "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.6.6.tgz",
+      "integrity": "sha512-j+1hW81ccglIYWukXufzRA4O71BCmpbmCO66ECDyE9FuPno6SjiR+K+mIk4tg6aQ7/UO2QA/EnRmT6YN0EF1Hw==",
       "dependencies": {
-        "@azure/msal-common": "14.6.1",
+        "@azure/msal-common": "14.8.1",
         "jsonwebtoken": "^9.0.0",
         "uuid": "^8.3.0"
       },
@@ -562,114 +607,43 @@
       }
     },
     "node_modules/@babel/code-frame": {
-      "version": "7.23.5",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
-      "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
+      "version": "7.24.2",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
+      "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==",
       "dev": true,
       "dependencies": {
-        "@babel/highlight": "^7.23.4",
-        "chalk": "^2.4.2"
+        "@babel/highlight": "^7.24.2",
+        "picocolors": "^1.0.0"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
-    "node_modules/@babel/code-frame/node_modules/ansi-styles": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
-      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
-      "dev": true,
-      "dependencies": {
-        "color-convert": "^1.9.0"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/@babel/code-frame/node_modules/chalk": {
-      "version": "2.4.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
-      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
-      "dev": true,
-      "dependencies": {
-        "ansi-styles": "^3.2.1",
-        "escape-string-regexp": "^1.0.5",
-        "supports-color": "^5.3.0"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/@babel/code-frame/node_modules/color-convert": {
-      "version": "1.9.3",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
-      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
-      "dev": true,
-      "dependencies": {
-        "color-name": "1.1.3"
-      }
-    },
-    "node_modules/@babel/code-frame/node_modules/color-name": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
-      "dev": true
-    },
-    "node_modules/@babel/code-frame/node_modules/escape-string-regexp": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.8.0"
-      }
-    },
-    "node_modules/@babel/code-frame/node_modules/has-flag": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
-      "dev": true,
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/@babel/code-frame/node_modules/supports-color": {
-      "version": "5.5.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
-      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
-      "dev": true,
-      "dependencies": {
-        "has-flag": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
     "node_modules/@babel/compat-data": {
-      "version": "7.23.5",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
-      "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==",
+      "version": "7.24.4",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz",
+      "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/core": {
-      "version": "7.23.9",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz",
-      "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==",
+      "version": "7.24.4",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz",
+      "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==",
       "dev": true,
       "dependencies": {
         "@ampproject/remapping": "^2.2.0",
-        "@babel/code-frame": "^7.23.5",
-        "@babel/generator": "^7.23.6",
+        "@babel/code-frame": "^7.24.2",
+        "@babel/generator": "^7.24.4",
         "@babel/helper-compilation-targets": "^7.23.6",
         "@babel/helper-module-transforms": "^7.23.3",
-        "@babel/helpers": "^7.23.9",
-        "@babel/parser": "^7.23.9",
-        "@babel/template": "^7.23.9",
-        "@babel/traverse": "^7.23.9",
-        "@babel/types": "^7.23.9",
+        "@babel/helpers": "^7.24.4",
+        "@babel/parser": "^7.24.4",
+        "@babel/template": "^7.24.0",
+        "@babel/traverse": "^7.24.1",
+        "@babel/types": "^7.24.0",
         "convert-source-map": "^2.0.0",
         "debug": "^4.1.0",
         "gensync": "^1.0.0-beta.2",
@@ -694,14 +668,14 @@
       }
     },
     "node_modules/@babel/generator": {
-      "version": "7.23.6",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz",
-      "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==",
+      "version": "7.24.4",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz",
+      "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.23.6",
-        "@jridgewell/gen-mapping": "^0.3.2",
-        "@jridgewell/trace-mapping": "^0.3.17",
+        "@babel/types": "^7.24.0",
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.25",
         "jsesc": "^2.5.1"
       },
       "engines": {
@@ -768,12 +742,12 @@
       }
     },
     "node_modules/@babel/helper-module-imports": {
-      "version": "7.22.15",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
-      "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
+      "version": "7.24.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz",
+      "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.22.15"
+        "@babel/types": "^7.24.0"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -799,9 +773,9 @@
       }
     },
     "node_modules/@babel/helper-plugin-utils": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
-      "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==",
+      "version": "7.24.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz",
+      "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
@@ -832,9 +806,9 @@
       }
     },
     "node_modules/@babel/helper-string-parser": {
-      "version": "7.23.4",
-      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
-      "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
+      "version": "7.24.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz",
+      "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
@@ -859,28 +833,29 @@
       }
     },
     "node_modules/@babel/helpers": {
-      "version": "7.23.9",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz",
-      "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==",
+      "version": "7.24.4",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz",
+      "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==",
       "dev": true,
       "dependencies": {
-        "@babel/template": "^7.23.9",
-        "@babel/traverse": "^7.23.9",
-        "@babel/types": "^7.23.9"
+        "@babel/template": "^7.24.0",
+        "@babel/traverse": "^7.24.1",
+        "@babel/types": "^7.24.0"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/highlight": {
-      "version": "7.23.4",
-      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
-      "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
+      "version": "7.24.2",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz",
+      "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==",
       "dev": true,
       "dependencies": {
         "@babel/helper-validator-identifier": "^7.22.20",
         "chalk": "^2.4.2",
-        "js-tokens": "^4.0.0"
+        "js-tokens": "^4.0.0",
+        "picocolors": "^1.0.0"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -958,9 +933,9 @@
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.23.9",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz",
-      "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==",
+      "version": "7.24.4",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz",
+      "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==",
       "dev": true,
       "bin": {
         "parser": "bin/babel-parser.js"
@@ -1030,12 +1005,12 @@
       }
     },
     "node_modules/@babel/plugin-syntax-jsx": {
-      "version": "7.23.3",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz",
-      "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==",
+      "version": "7.24.1",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz",
+      "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==",
       "dev": true,
       "dependencies": {
-        "@babel/helper-plugin-utils": "^7.22.5"
+        "@babel/helper-plugin-utils": "^7.24.0"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -1132,12 +1107,12 @@
       }
     },
     "node_modules/@babel/plugin-syntax-typescript": {
-      "version": "7.23.3",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz",
-      "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==",
+      "version": "7.24.1",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz",
+      "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==",
       "dev": true,
       "dependencies": {
-        "@babel/helper-plugin-utils": "^7.22.5"
+        "@babel/helper-plugin-utils": "^7.24.0"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -1147,9 +1122,9 @@
       }
     },
     "node_modules/@babel/runtime": {
-      "version": "7.23.9",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
-      "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
+      "version": "7.24.4",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz",
+      "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==",
       "dev": true,
       "dependencies": {
         "regenerator-runtime": "^0.14.0"
@@ -1159,33 +1134,33 @@
       }
     },
     "node_modules/@babel/template": {
-      "version": "7.23.9",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz",
-      "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==",
+      "version": "7.24.0",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz",
+      "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==",
       "dev": true,
       "dependencies": {
         "@babel/code-frame": "^7.23.5",
-        "@babel/parser": "^7.23.9",
-        "@babel/types": "^7.23.9"
+        "@babel/parser": "^7.24.0",
+        "@babel/types": "^7.24.0"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/traverse": {
-      "version": "7.23.9",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz",
-      "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==",
+      "version": "7.24.1",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz",
+      "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==",
       "dev": true,
       "dependencies": {
-        "@babel/code-frame": "^7.23.5",
-        "@babel/generator": "^7.23.6",
+        "@babel/code-frame": "^7.24.1",
+        "@babel/generator": "^7.24.1",
         "@babel/helper-environment-visitor": "^7.22.20",
         "@babel/helper-function-name": "^7.23.0",
         "@babel/helper-hoist-variables": "^7.22.5",
         "@babel/helper-split-export-declaration": "^7.22.6",
-        "@babel/parser": "^7.23.9",
-        "@babel/types": "^7.23.9",
+        "@babel/parser": "^7.24.1",
+        "@babel/types": "^7.24.0",
         "debug": "^4.3.1",
         "globals": "^11.1.0"
       },
@@ -1203,9 +1178,9 @@
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.23.9",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz",
-      "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==",
+      "version": "7.24.0",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
+      "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
       "dev": true,
       "dependencies": {
         "@babel/helper-string-parser": "^7.23.4",
@@ -1233,16 +1208,16 @@
       }
     },
     "node_modules/@commitlint/cli": {
-      "version": "18.6.0",
-      "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-18.6.0.tgz",
-      "integrity": "sha512-FiH23cr9QG8VdfbmvJJZmdfHGVMCouOOAzoXZ3Cd7czGC52RbycwNt8YCI7SA69pAl+t30vh8LMaO/N+kcel6w==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-18.6.1.tgz",
+      "integrity": "sha512-5IDE0a+lWGdkOvKH892HHAZgbAjcj1mT5QrfA/SVbLJV/BbBMGyKN0W5mhgjekPJJwEQdVNvhl9PwUacY58Usw==",
       "dev": true,
       "dependencies": {
-        "@commitlint/format": "^18.6.0",
-        "@commitlint/lint": "^18.6.0",
-        "@commitlint/load": "^18.6.0",
-        "@commitlint/read": "^18.6.0",
-        "@commitlint/types": "^18.6.0",
+        "@commitlint/format": "^18.6.1",
+        "@commitlint/lint": "^18.6.1",
+        "@commitlint/load": "^18.6.1",
+        "@commitlint/read": "^18.6.1",
+        "@commitlint/types": "^18.6.1",
         "execa": "^5.0.0",
         "lodash.isfunction": "^3.0.9",
         "resolve-from": "5.0.0",
@@ -1257,11 +1232,12 @@
       }
     },
     "node_modules/@commitlint/config-conventional": {
-      "version": "18.6.0",
-      "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-18.6.0.tgz",
-      "integrity": "sha512-CDCOf2eJz9D/TL44IBks0stM9TmdLCNE2B48owIU3YCadwzts/bobXPScagIgPQF6hhKYMEdj5zpUDlmbwuqwQ==",
+      "version": "18.6.3",
+      "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-18.6.3.tgz",
+      "integrity": "sha512-8ZrRHqF6je+TRaFoJVwszwnOXb/VeYrPmTwPhf0WxpzpGTcYy1p0SPyZ2eRn/sRi/obnWAcobtDAq6+gJQQNhQ==",
       "dev": true,
       "dependencies": {
+        "@commitlint/types": "^18.6.1",
         "conventional-changelog-conventionalcommits": "^7.0.2"
       },
       "engines": {
@@ -1269,47 +1245,25 @@
       }
     },
     "node_modules/@commitlint/config-validator": {
-      "version": "18.6.0",
-      "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-18.6.0.tgz",
-      "integrity": "sha512-Ptfa865arNozlkjxrYG3qt6wT9AlhNUHeuDyKEZiTL/l0ftncFhK/KN0t/EAMV2tec+0Mwxo0FmhbESj/bI+1g==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-18.6.1.tgz",
+      "integrity": "sha512-05uiToBVfPhepcQWE1ZQBR/Io3+tb3gEotZjnI4tTzzPk16NffN6YABgwFQCLmzZefbDcmwWqJWc2XT47q7Znw==",
       "dev": true,
       "dependencies": {
-        "@commitlint/types": "^18.6.0",
+        "@commitlint/types": "^18.6.1",
         "ajv": "^8.11.0"
       },
       "engines": {
         "node": ">=v18"
       }
     },
-    "node_modules/@commitlint/config-validator/node_modules/ajv": {
-      "version": "8.12.0",
-      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
-      "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
-      "dev": true,
-      "dependencies": {
-        "fast-deep-equal": "^3.1.1",
-        "json-schema-traverse": "^1.0.0",
-        "require-from-string": "^2.0.2",
-        "uri-js": "^4.2.2"
-      },
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/epoberezkin"
-      }
-    },
-    "node_modules/@commitlint/config-validator/node_modules/json-schema-traverse": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
-      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
-      "dev": true
-    },
     "node_modules/@commitlint/ensure": {
-      "version": "18.6.0",
-      "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-18.6.0.tgz",
-      "integrity": "sha512-xY07NmOBJ7JuhX3tic021PaeLepZARIQyqpAQoNQZoml1keBFfB6MbA7XlWZv0ebbarUFE4yhKxOPw+WFv7/qw==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-18.6.1.tgz",
+      "integrity": "sha512-BPm6+SspyxQ7ZTsZwXc7TRQL5kh5YWt3euKmEIBZnocMFkJevqs3fbLRb8+8I/cfbVcAo4mxRlpTPfz8zX7SnQ==",
       "dev": true,
       "dependencies": {
-        "@commitlint/types": "^18.6.0",
+        "@commitlint/types": "^18.6.1",
         "lodash.camelcase": "^4.3.0",
         "lodash.kebabcase": "^4.1.1",
         "lodash.snakecase": "^4.1.1",
@@ -1321,21 +1275,21 @@
       }
     },
     "node_modules/@commitlint/execute-rule": {
-      "version": "18.4.4",
-      "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-18.4.4.tgz",
-      "integrity": "sha512-a37Nd3bDQydtg9PCLLWM9ZC+GO7X5i4zJvrggJv5jBhaHsXeQ9ZWdO6ODYR+f0LxBXXNYK3geYXJrCWUCP8JEg==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-18.6.1.tgz",
+      "integrity": "sha512-7s37a+iWyJiGUeMFF6qBlyZciUkF8odSAnHijbD36YDctLhGKoYltdvuJ/AFfRm6cBLRtRk9cCVPdsEFtt/2rg==",
       "dev": true,
       "engines": {
         "node": ">=v18"
       }
     },
     "node_modules/@commitlint/format": {
-      "version": "18.6.0",
-      "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-18.6.0.tgz",
-      "integrity": "sha512-8UNWfs2slPPSQiiVpLGJTnPHv7Jkd5KYxfbNXbmLL583bjom4RrylvyrCVnmZReA8nNad7pPXq6mDH4FNVj6xg==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-18.6.1.tgz",
+      "integrity": "sha512-K8mNcfU/JEFCharj2xVjxGSF+My+FbUHoqR+4GqPGrHNqXOGNio47ziiR4HQUPKtiNs05o8/WyLBoIpMVOP7wg==",
       "dev": true,
       "dependencies": {
-        "@commitlint/types": "^18.6.0",
+        "@commitlint/types": "^18.6.1",
         "chalk": "^4.1.0"
       },
       "engines": {
@@ -1343,43 +1297,43 @@
       }
     },
     "node_modules/@commitlint/is-ignored": {
-      "version": "18.6.0",
-      "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-18.6.0.tgz",
-      "integrity": "sha512-Xjx/ZyyJ4FdLuz0FcOvqiqSFgiO2yYj3QN9XlvyrxqbXTxPVC7QFEXJYBVPulUSN/gR7WXH1Udw+HYYfD17xog==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-18.6.1.tgz",
+      "integrity": "sha512-MOfJjkEJj/wOaPBw5jFjTtfnx72RGwqYIROABudOtJKW7isVjFe9j0t8xhceA02QebtYf4P/zea4HIwnXg8rvA==",
       "dev": true,
       "dependencies": {
-        "@commitlint/types": "^18.6.0",
-        "semver": "7.5.4"
+        "@commitlint/types": "^18.6.1",
+        "semver": "7.6.0"
       },
       "engines": {
         "node": ">=v18"
       }
     },
     "node_modules/@commitlint/lint": {
-      "version": "18.6.0",
-      "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-18.6.0.tgz",
-      "integrity": "sha512-ycbuDWfyykPmslgiHzhz8dL6F0BJYltXLVfc+M49z0c+FNITM0v+r0Vd2+Tdtq06VTc894p2+YSmZhulY8Jn3Q==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-18.6.1.tgz",
+      "integrity": "sha512-8WwIFo3jAuU+h1PkYe5SfnIOzp+TtBHpFr4S8oJWhu44IWKuVx6GOPux3+9H1iHOan/rGBaiacicZkMZuluhfQ==",
       "dev": true,
       "dependencies": {
-        "@commitlint/is-ignored": "^18.6.0",
-        "@commitlint/parse": "^18.6.0",
-        "@commitlint/rules": "^18.6.0",
-        "@commitlint/types": "^18.6.0"
+        "@commitlint/is-ignored": "^18.6.1",
+        "@commitlint/parse": "^18.6.1",
+        "@commitlint/rules": "^18.6.1",
+        "@commitlint/types": "^18.6.1"
       },
       "engines": {
         "node": ">=v18"
       }
     },
     "node_modules/@commitlint/load": {
-      "version": "18.6.0",
-      "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-18.6.0.tgz",
-      "integrity": "sha512-RRssj7TmzT0bowoEKlgwg8uQ7ORXWkw7lYLsZZBMi9aInsJuGNLNWcMxJxRZbwxG3jkCidGUg85WmqJvRjsaDA==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-18.6.1.tgz",
+      "integrity": "sha512-p26x8734tSXUHoAw0ERIiHyW4RaI4Bj99D8YgUlVV9SedLf8hlWAfyIFhHRIhfPngLlCe0QYOdRKYFt8gy56TA==",
       "dev": true,
       "dependencies": {
-        "@commitlint/config-validator": "^18.6.0",
-        "@commitlint/execute-rule": "^18.4.4",
-        "@commitlint/resolve-extends": "^18.6.0",
-        "@commitlint/types": "^18.6.0",
+        "@commitlint/config-validator": "^18.6.1",
+        "@commitlint/execute-rule": "^18.6.1",
+        "@commitlint/resolve-extends": "^18.6.1",
+        "@commitlint/types": "^18.6.1",
         "chalk": "^4.1.0",
         "cosmiconfig": "^8.3.6",
         "cosmiconfig-typescript-loader": "^5.0.0",
@@ -1393,21 +1347,21 @@
       }
     },
     "node_modules/@commitlint/message": {
-      "version": "18.4.4",
-      "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-18.4.4.tgz",
-      "integrity": "sha512-lHF95mMDYgAI1LBXveJUyg4eLaMXyOqJccCK3v55ZOEUsMPrDi8upqDjd/NmzWmESYihaOMBTAnxm+6oD1WoDQ==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-18.6.1.tgz",
+      "integrity": "sha512-VKC10UTMLcpVjMIaHHsY1KwhuTQtdIKPkIdVEwWV+YuzKkzhlI3aNy6oo1eAN6b/D2LTtZkJe2enHmX0corYRw==",
       "dev": true,
       "engines": {
         "node": ">=v18"
       }
     },
     "node_modules/@commitlint/parse": {
-      "version": "18.6.0",
-      "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-18.6.0.tgz",
-      "integrity": "sha512-Y/G++GJpATFw54O0jikc/h2ibyGHgghtPnwsOk3O/aU092ydJ5XEHYcd7xGNQYuLweLzQis2uEwRNk9AVIPbQQ==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-18.6.1.tgz",
+      "integrity": "sha512-eS/3GREtvVJqGZrwAGRwR9Gdno3YcZ6Xvuaa+vUF8j++wsmxrA2En3n0ccfVO2qVOLJC41ni7jSZhQiJpMPGOQ==",
       "dev": true,
       "dependencies": {
-        "@commitlint/types": "^18.6.0",
+        "@commitlint/types": "^18.6.1",
         "conventional-changelog-angular": "^7.0.0",
         "conventional-commits-parser": "^5.0.0"
       },
@@ -1416,13 +1370,13 @@
       }
     },
     "node_modules/@commitlint/read": {
-      "version": "18.6.0",
-      "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-18.6.0.tgz",
-      "integrity": "sha512-w39ji8VfWhPKRquPhRHB3Yd8XIHwaNHgOh28YI1QEmZ59qVpuVUQo6h/NsVb+uoC6LbXZiofTZv2iFR084jKEA==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-18.6.1.tgz",
+      "integrity": "sha512-ia6ODaQFzXrVul07ffSgbZGFajpe8xhnDeLIprLeyfz3ivQU1dIoHp7yz0QIorZ6yuf4nlzg4ZUkluDrGN/J/w==",
       "dev": true,
       "dependencies": {
-        "@commitlint/top-level": "^18.4.4",
-        "@commitlint/types": "^18.6.0",
+        "@commitlint/top-level": "^18.6.1",
+        "@commitlint/types": "^18.6.1",
         "git-raw-commits": "^2.0.11",
         "minimist": "^1.2.6"
       },
@@ -1431,13 +1385,13 @@
       }
     },
     "node_modules/@commitlint/resolve-extends": {
-      "version": "18.6.0",
-      "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-18.6.0.tgz",
-      "integrity": "sha512-k2Xp+Fxeggki2i90vGrbiLDMefPius3zGSTFFlRAPKce/SWLbZtI+uqE9Mne23mHO5lmcSV8z5m6ziiJwGpOcg==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-18.6.1.tgz",
+      "integrity": "sha512-ifRAQtHwK+Gj3Bxj/5chhc4L2LIc3s30lpsyW67yyjsETR6ctHAHRu1FSpt0KqahK5xESqoJ92v6XxoDRtjwEQ==",
       "dev": true,
       "dependencies": {
-        "@commitlint/config-validator": "^18.6.0",
-        "@commitlint/types": "^18.6.0",
+        "@commitlint/config-validator": "^18.6.1",
+        "@commitlint/types": "^18.6.1",
         "import-fresh": "^3.0.0",
         "lodash.mergewith": "^4.6.2",
         "resolve-from": "^5.0.0",
@@ -1448,15 +1402,15 @@
       }
     },
     "node_modules/@commitlint/rules": {
-      "version": "18.6.0",
-      "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-18.6.0.tgz",
-      "integrity": "sha512-pTalvCEvuCWrBWZA/YqO/3B3nZnY3Ncc+TmQsRajBdC1tkQIm5Iovdo4Ec7f2Dw1tVvpYMUUNAgcWqsY0WckWg==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-18.6.1.tgz",
+      "integrity": "sha512-kguM6HxZDtz60v/zQYOe0voAtTdGybWXefA1iidjWYmyUUspO1zBPQEmJZ05/plIAqCVyNUTAiRPWIBKLCrGew==",
       "dev": true,
       "dependencies": {
-        "@commitlint/ensure": "^18.6.0",
-        "@commitlint/message": "^18.4.4",
-        "@commitlint/to-lines": "^18.4.4",
-        "@commitlint/types": "^18.6.0",
+        "@commitlint/ensure": "^18.6.1",
+        "@commitlint/message": "^18.6.1",
+        "@commitlint/to-lines": "^18.6.1",
+        "@commitlint/types": "^18.6.1",
         "execa": "^5.0.0"
       },
       "engines": {
@@ -1464,18 +1418,18 @@
       }
     },
     "node_modules/@commitlint/to-lines": {
-      "version": "18.4.4",
-      "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-18.4.4.tgz",
-      "integrity": "sha512-mwe2Roa59NCz/krniAdCygFabg7+fQCkIhXqBHw00XQ8Y7lw4poZLLxeGI3p3bLpcEOXdqIDrEGLwHmG5lBdwQ==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-18.6.1.tgz",
+      "integrity": "sha512-Gl+orGBxYSNphx1+83GYeNy5N0dQsHBQ9PJMriaLQDB51UQHCVLBT/HBdOx5VaYksivSf5Os55TLePbRLlW50Q==",
       "dev": true,
       "engines": {
         "node": ">=v18"
       }
     },
     "node_modules/@commitlint/top-level": {
-      "version": "18.4.4",
-      "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-18.4.4.tgz",
-      "integrity": "sha512-PBwW1drgeavl9CadB7IPRUk6rkUP/O8jEkxjlC+ofuh3pw0bzJdAT+Kw7M1Yc9KtTb9xTaqUB8uvRtaybHa/tQ==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-18.6.1.tgz",
+      "integrity": "sha512-HyiHQZUTf0+r0goTCDs/bbVv/LiiQ7AVtz6KIar+8ZrseB9+YJAIo8HQ2IC2QT1y3N1lbW6OqVEsTHjbT6hGSw==",
       "dev": true,
       "dependencies": {
         "find-up": "^5.0.0"
@@ -1485,9 +1439,9 @@
       }
     },
     "node_modules/@commitlint/types": {
-      "version": "18.6.0",
-      "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-18.6.0.tgz",
-      "integrity": "sha512-oavoKLML/eJa2rJeyYSbyGAYzTxQ6voG5oeX3OrxpfrkRWhJfm4ACnhoRf5tgiybx2MZ+EVFqC1Lw3W8/uwpZA==",
+      "version": "18.6.1",
+      "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-18.6.1.tgz",
+      "integrity": "sha512-gwRLBLra/Dozj2OywopeuHj2ac26gjGkz2cZ+86cTJOdtWfiRRr4+e77ZDAGc6MDWxaWheI+mAV5TLWWRwqrFg==",
       "dev": true,
       "dependencies": {
         "chalk": "^4.1.0"
@@ -1497,16 +1451,16 @@
       }
     },
     "node_modules/@cspell/cspell-bundled-dicts": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-8.3.2.tgz",
-      "integrity": "sha512-3ubOgz1/MDixJbq//0rQ2omB3cSdhVJDviERZeiREGz4HOq84aaK1Fqbw5SjNZHvhpoq+AYXm6kJbIAH8YhKgg==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-8.7.0.tgz",
+      "integrity": "sha512-B5YQI7Dd9m0JHTmHgs7PiyP4BWXzl8ixpK+HGOwhxzh7GyfFt1Eo/gxMxBDX/9SaewEzeb2OjRpRKEFtEsto3A==",
       "dev": true,
       "dependencies": {
         "@cspell/dict-ada": "^4.0.2",
         "@cspell/dict-aws": "^4.0.1",
         "@cspell/dict-bash": "^4.1.3",
-        "@cspell/dict-companies": "^3.0.29",
-        "@cspell/dict-cpp": "^5.0.10",
+        "@cspell/dict-companies": "^3.0.31",
+        "@cspell/dict-cpp": "^5.1.3",
         "@cspell/dict-cryptocurrencies": "^5.0.0",
         "@cspell/dict-csharp": "^4.0.2",
         "@cspell/dict-css": "^4.0.12",
@@ -1515,39 +1469,42 @@
         "@cspell/dict-docker": "^1.1.7",
         "@cspell/dict-dotnet": "^5.0.0",
         "@cspell/dict-elixir": "^4.0.3",
-        "@cspell/dict-en_us": "^4.3.13",
+        "@cspell/dict-en_us": "^4.3.17",
         "@cspell/dict-en-common-misspellings": "^2.0.0",
         "@cspell/dict-en-gb": "1.1.33",
         "@cspell/dict-filetypes": "^3.0.3",
         "@cspell/dict-fonts": "^4.0.0",
         "@cspell/dict-fsharp": "^1.0.1",
         "@cspell/dict-fullstack": "^3.1.5",
-        "@cspell/dict-gaming-terms": "^1.0.4",
+        "@cspell/dict-gaming-terms": "^1.0.5",
         "@cspell/dict-git": "^3.0.0",
         "@cspell/dict-golang": "^6.0.5",
         "@cspell/dict-haskell": "^4.0.1",
         "@cspell/dict-html": "^4.0.5",
         "@cspell/dict-html-symbol-entities": "^4.0.0",
         "@cspell/dict-java": "^5.0.6",
+        "@cspell/dict-julia": "^1.0.1",
         "@cspell/dict-k8s": "^1.0.2",
         "@cspell/dict-latex": "^4.0.0",
         "@cspell/dict-lorem-ipsum": "^4.0.0",
         "@cspell/dict-lua": "^4.0.3",
         "@cspell/dict-makefile": "^1.0.0",
+        "@cspell/dict-monkeyc": "^1.0.6",
         "@cspell/dict-node": "^4.0.3",
-        "@cspell/dict-npm": "^5.0.14",
-        "@cspell/dict-php": "^4.0.5",
+        "@cspell/dict-npm": "^5.0.15",
+        "@cspell/dict-php": "^4.0.6",
         "@cspell/dict-powershell": "^5.0.3",
-        "@cspell/dict-public-licenses": "^2.0.5",
+        "@cspell/dict-public-licenses": "^2.0.6",
         "@cspell/dict-python": "^4.1.11",
         "@cspell/dict-r": "^2.0.1",
         "@cspell/dict-ruby": "^5.0.2",
-        "@cspell/dict-rust": "^4.0.1",
+        "@cspell/dict-rust": "^4.0.2",
         "@cspell/dict-scala": "^5.0.0",
-        "@cspell/dict-software-terms": "^3.3.15",
+        "@cspell/dict-software-terms": "^3.3.18",
         "@cspell/dict-sql": "^2.1.3",
         "@cspell/dict-svelte": "^1.0.2",
         "@cspell/dict-swift": "^2.0.1",
+        "@cspell/dict-terraform": "^1.0.0",
         "@cspell/dict-typescript": "^3.1.2",
         "@cspell/dict-vue": "^3.0.0"
       },
@@ -1556,30 +1513,30 @@
       }
     },
     "node_modules/@cspell/cspell-json-reporter": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-8.3.2.tgz",
-      "integrity": "sha512-gHSz4jXMJPcxx+lOGfXhHuoyenAWQ8PVA/atHFrWYKo1LzKTbpkEkrsDnlX8QNJubc3EMH63Uy+lOIaFDVyHiQ==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-8.7.0.tgz",
+      "integrity": "sha512-LTQPEvXvCqnc+ok9WXpSISZyt4/nGse9fVEM430g0BpGzKpt3RMx49B8uasvvnanzCuikaW9+wFLmwgvraERhA==",
       "dev": true,
       "dependencies": {
-        "@cspell/cspell-types": "8.3.2"
+        "@cspell/cspell-types": "8.7.0"
       },
       "engines": {
         "node": ">=18"
       }
     },
     "node_modules/@cspell/cspell-pipe": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-8.3.2.tgz",
-      "integrity": "sha512-GZmDwvQGOjQi3IjD4k9xXeVTDANczksOsgVKb3v2QZk9mR4Qj8c6Uarjd4AgSiIhu/wBliJfzr5rWFJu4X2VfQ==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-8.7.0.tgz",
+      "integrity": "sha512-ePqddIQ4arqPQgOkC146SkZxvZb9/jL7xIM5Igy2n3tiWTC5ijrX/mbHpPZ1VGcFck+1M0cJUuyhuJk+vMj3rg==",
       "dev": true,
       "engines": {
         "node": ">=18"
       }
     },
     "node_modules/@cspell/cspell-resolver": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-8.3.2.tgz",
-      "integrity": "sha512-w2Tmb95bzdEz9L4W5qvsP5raZbyEzKL7N2ksU/+yh8NEJcTuExmAl/nMnb3aIk7m2b+kPHnMOcJuwfUMLmyv4A==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-8.7.0.tgz",
+      "integrity": "sha512-grZwDFYqcBYQDaz4AkUtdyqc4UUH2J3/7yWVkBbYDPE+FQHa9ofFXzXxyjs56GJlPfi9ULpe5/Wz6uVLg8rQkQ==",
       "dev": true,
       "dependencies": {
         "global-directory": "^4.0.1"
@@ -1589,18 +1546,18 @@
       }
     },
     "node_modules/@cspell/cspell-service-bus": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-8.3.2.tgz",
-      "integrity": "sha512-skTHNyVi74//W/O+f4IauDhm6twA9S2whkylonsIzPxEl4Pn3y2ZEMXNki/MWUwZfDIzKKSxlcREH61g7zCvhg==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-8.7.0.tgz",
+      "integrity": "sha512-KW48iu0nTDzbedixc7iB7K7mlAZQ7QeMLuM/akxigOlvtOdVJrRa9Pfn44lwejts1ANb/IXil3GH8YylkVi76Q==",
       "dev": true,
       "engines": {
         "node": ">=18"
       }
     },
     "node_modules/@cspell/cspell-types": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-8.3.2.tgz",
-      "integrity": "sha512-qS/gWd9ItOrN6ZX5pwC9lJjnBoyiAyhxYq0GUXuV892LQvwrBmECGk6KhsA1lPW7JJS7o57YTAS1jmXnmXMEpg==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-8.7.0.tgz",
+      "integrity": "sha512-Rb+LCE5I9JEb/LE8nSViVSF8z1CWv/z4mPBIG37VMa7aUx2gAQa6gJekNfpY9YZiMzx4Tv3gDujN80ytks4pGA==",
       "dev": true,
       "engines": {
         "node": ">=18"
@@ -1631,9 +1588,9 @@
       "dev": true
     },
     "node_modules/@cspell/dict-cpp": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-5.1.1.tgz",
-      "integrity": "sha512-Qy9fNsR/5RcQ6G85gDKFjvzh0AdgAilLQeSXPtqY21Fx1kCjUqdVVJYMmHUREgcxH6ptAxtn5knTWU4PIhQtOw==",
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-5.1.3.tgz",
+      "integrity": "sha512-sqnriXRAInZH9W75C+APBh6dtben9filPqVbIsiRMUXGg+s02ekz0z6LbS7kXeJ5mD2qXoMLBrv13qH2eIwutQ==",
       "dev": true
     },
     "node_modules/@cspell/dict-cryptocurrencies": {
@@ -1691,9 +1648,9 @@
       "dev": true
     },
     "node_modules/@cspell/dict-en_us": {
-      "version": "4.3.14",
-      "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.3.14.tgz",
-      "integrity": "sha512-Od7vPVNN4td0Fild5BcCPikx+lBJ2L809zWeO3lThYHqtZXqsbaBNzfv9qlB1bXW199Ru461vu02CrklU1oD+Q==",
+      "version": "4.3.17",
+      "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.3.17.tgz",
+      "integrity": "sha512-CS0Tb2f2YwQZ4VZ6+WLAO5uOzb0iO/iYSRl34kX4enq6quXxLYzwdfGAwv85wSYHPdga8tGiZFP+p8GPsi2JEg==",
       "dev": true
     },
     "node_modules/@cspell/dict-en-common-misspellings": {
@@ -1774,6 +1731,12 @@
       "integrity": "sha512-kdE4AHHHrixyZ5p6zyms1SLoYpaJarPxrz8Tveo6gddszBVVwIUZ+JkQE1bWNLK740GWzIXdkznpUfw1hP9nXw==",
       "dev": true
     },
+    "node_modules/@cspell/dict-julia": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@cspell/dict-julia/-/dict-julia-1.0.1.tgz",
+      "integrity": "sha512-4JsCLCRhhLMLiaHpmR7zHFjj1qOauzDI5ZzCNQS31TUMfsOo26jAKDfo0jljFAKgw5M2fEG7sKr8IlPpQAYrmQ==",
+      "dev": true
+    },
     "node_modules/@cspell/dict-k8s": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/@cspell/dict-k8s/-/dict-k8s-1.0.2.tgz",
@@ -1804,6 +1767,12 @@
       "integrity": "sha512-3W9tHPcSbJa6s0bcqWo6VisEDTSN5zOtDbnPabF7rbyjRpNo0uHXHRJQF8gAbFzoTzBBhgkTmrfSiuyQm7vBUQ==",
       "dev": true
     },
+    "node_modules/@cspell/dict-monkeyc": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.6.tgz",
+      "integrity": "sha512-oO8ZDu/FtZ55aq9Mb67HtaCnsLn59xvhO/t2mLLTHAp667hJFxpp7bCtr2zOrR1NELzFXmKln/2lw/PvxMSvrA==",
+      "dev": true
+    },
     "node_modules/@cspell/dict-node": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-4.0.3.tgz",
@@ -1817,9 +1786,9 @@
       "dev": true
     },
     "node_modules/@cspell/dict-php": {
-      "version": "4.0.5",
-      "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.0.5.tgz",
-      "integrity": "sha512-9r8ao7Z/mH9Z8pSB7yLtyvcCJWw+/MnQpj7xGVYzIV7V2ZWDRjXZAMgteHMJ37m8oYz64q5d4tiipD300QSetQ==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.0.6.tgz",
+      "integrity": "sha512-ySAXisf7twoVFZqBV2o/DKiCLIDTHNqfnj0EfH9OoOUR7HL3rb6zJkm0viLUFDO2G/8SyIi6YrN/6KX+Scjjjg==",
       "dev": true
     },
     "node_modules/@cspell/dict-powershell": {
@@ -1829,9 +1798,9 @@
       "dev": true
     },
     "node_modules/@cspell/dict-public-licenses": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.5.tgz",
-      "integrity": "sha512-91HK4dSRri/HqzAypHgduRMarJAleOX5NugoI8SjDLPzWYkwZ1ftuCXSk+fy8DLc3wK7iOaFcZAvbjmnLhVs4A==",
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.6.tgz",
+      "integrity": "sha512-bHqpSpJvLCUcWxj1ov/Ki8WjmESpYwRpQlqfdchekOTc93Huhvjm/RXVN1R4fVf4Hspyem1QVkCGqAmjJMj6sw==",
       "dev": true
     },
     "node_modules/@cspell/dict-python": {
@@ -1868,9 +1837,9 @@
       "dev": true
     },
     "node_modules/@cspell/dict-software-terms": {
-      "version": "3.3.16",
-      "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-3.3.16.tgz",
-      "integrity": "sha512-ixorEP80LGxAU+ODVSn/CYIDjV0XAlZ2VrBu7CT+PwUFJ7h8o3JX1ywKB4qnt0hHru3JjWFtBoBThmZdrXnREQ==",
+      "version": "3.3.18",
+      "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-3.3.18.tgz",
+      "integrity": "sha512-LJZGGMGqS8KzgXJrSMs3T+6GoqHG9z8Bc+rqLzLzbtoR3FbsMasE9U8oP2PmS3q7jJLFjQkzmg508DrcuZuo2g==",
       "dev": true
     },
     "node_modules/@cspell/dict-sql": {
@@ -1891,6 +1860,12 @@
       "integrity": "sha512-gxrCMUOndOk7xZFmXNtkCEeroZRnS2VbeaIPiymGRHj5H+qfTAzAKxtv7jJbVA3YYvEzWcVE2oKDP4wcbhIERw==",
       "dev": true
     },
+    "node_modules/@cspell/dict-terraform": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@cspell/dict-terraform/-/dict-terraform-1.0.0.tgz",
+      "integrity": "sha512-Ak+vy4HP/bOgzf06BAMC30+ZvL9mzv21xLM2XtfnBLTDJGdxlk/nK0U6QT8VfFLqJ0ZZSpyOxGsUebWDCTr/zQ==",
+      "dev": true
+    },
     "node_modules/@cspell/dict-typescript": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.1.2.tgz",
@@ -1904,9 +1879,9 @@
       "dev": true
     },
     "node_modules/@cspell/dynamic-import": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-8.3.2.tgz",
-      "integrity": "sha512-4t0xM5luA3yQhar2xWvYK4wQSDB2r0u8XkpzzJqd57MnJXd7uIAxI0awGUrDXukadRaCo0tDIlMUBemH48SNVg==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-8.7.0.tgz",
+      "integrity": "sha512-xlEPdiHVDu+4xYkvwjL9MgklxOi9XB+Pr1H9s3Ww9WEq+q6BA3xOHxLIU/k8mhqFTMZGFZRCsdy/EwMu6SyRhQ==",
       "dev": true,
       "dependencies": {
         "import-meta-resolve": "^4.0.0"
@@ -1916,9 +1891,9 @@
       }
     },
     "node_modules/@cspell/strong-weak-map": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-8.3.2.tgz",
-      "integrity": "sha512-Mte/2000ap278kRYOUhiGWI7MNr1+A7WSWJmlcdP4CAH5SO20sZI3/cyZLjJJEyapdhK5vaP1L5J9sUcVDHd3A==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-8.7.0.tgz",
+      "integrity": "sha512-0bo0WwDr2lzGoCP7vbpWbDpPyuOrHKK+218txnUpx6Pn1EDBLfcDQsiZED5B6zlpwgbGi6y3vc0rWtJbjKvwzg==",
       "dev": true,
       "engines": {
         "node": ">=18"
@@ -1928,7 +1903,7 @@
       "version": "0.8.1",
       "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
       "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
-      "devOptional": true,
+      "dev": true,
       "dependencies": {
         "@jridgewell/trace-mapping": "0.3.9"
       },
@@ -1940,7 +1915,7 @@
       "version": "0.3.9",
       "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
       "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
-      "devOptional": true,
+      "dev": true,
       "dependencies": {
         "@jridgewell/resolve-uri": "^3.0.3",
         "@jridgewell/sourcemap-codec": "^1.4.10"
@@ -1993,6 +1968,22 @@
         "url": "https://opencollective.com/eslint"
       }
     },
+    "node_modules/@eslint/eslintrc/node_modules/ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
     "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -2003,6 +1994,12 @@
         "concat-map": "0.0.1"
       }
     },
+    "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
     "node_modules/@eslint/eslintrc/node_modules/minimatch": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2016,18 +2013,18 @@
       }
     },
     "node_modules/@eslint/js": {
-      "version": "8.56.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
-      "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
+      "version": "8.57.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
+      "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
       }
     },
     "node_modules/@faker-js/faker": {
-      "version": "8.4.0",
-      "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.0.tgz",
-      "integrity": "sha512-htW87352wzUCdX1jyUQocUcmAaFqcR/w082EC8iP/gtkF0K+aKcBp0hR5Arb7dzR8tQ1TrhE9DNa5EbJELm84w==",
+      "version": "8.4.1",
+      "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz",
+      "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==",
       "funding": [
         {
           "type": "opencollective",
@@ -2089,9 +2086,9 @@
       }
     },
     "node_modules/@humanwhocodes/object-schema": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
-      "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+      "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
       "dev": true
     },
     "node_modules/@isaacs/cliui": {
@@ -2643,57 +2640,57 @@
       }
     },
     "node_modules/@jridgewell/gen-mapping": {
-      "version": "0.3.3",
-      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
-      "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+      "version": "0.3.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+      "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
       "dev": true,
       "dependencies": {
-        "@jridgewell/set-array": "^1.0.1",
+        "@jridgewell/set-array": "^1.2.1",
         "@jridgewell/sourcemap-codec": "^1.4.10",
-        "@jridgewell/trace-mapping": "^0.3.9"
+        "@jridgewell/trace-mapping": "^0.3.24"
       },
       "engines": {
         "node": ">=6.0.0"
       }
     },
     "node_modules/@jridgewell/resolve-uri": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
-      "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
-      "devOptional": true,
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "dev": true,
       "engines": {
         "node": ">=6.0.0"
       }
     },
     "node_modules/@jridgewell/set-array": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
-      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+      "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
       "dev": true,
       "engines": {
         "node": ">=6.0.0"
       }
     },
     "node_modules/@jridgewell/source-map": {
-      "version": "0.3.5",
-      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
-      "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
+      "version": "0.3.6",
+      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
+      "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
       "dev": true,
       "dependencies": {
-        "@jridgewell/gen-mapping": "^0.3.0",
-        "@jridgewell/trace-mapping": "^0.3.9"
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.25"
       }
     },
     "node_modules/@jridgewell/sourcemap-codec": {
       "version": "1.4.15",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
       "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
-      "devOptional": true
+      "dev": true
     },
     "node_modules/@jridgewell/trace-mapping": {
-      "version": "0.3.22",
-      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
-      "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
+      "version": "0.3.25",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+      "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
       "dev": true,
       "dependencies": {
         "@jridgewell/resolve-uri": "^3.1.0",
@@ -2701,17 +2698,17 @@
       }
     },
     "node_modules/@js-joda/core": {
-      "version": "5.6.1",
-      "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.6.1.tgz",
-      "integrity": "sha512-Xla/d7ZMMR6+zRd6lTio0wRZECfcfFJP7GGe9A9L4tDOlD5CX4YcZ4YZle9w58bBYzssojVapI84RraKWDQZRg=="
+      "version": "5.6.2",
+      "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.6.2.tgz",
+      "integrity": "sha512-ow4R+7C24xeTjiMTTZ4k6lvxj7MRBqvqLCQjThQff3RjOmIMokMP20LNYVFhGafJtUx/Xo2Qp4qU8eNoTVH0SA=="
     },
     "node_modules/@ljharb/through": {
-      "version": "2.3.12",
-      "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.12.tgz",
-      "integrity": "sha512-ajo/heTlG3QgC8EGP6APIejksVAYt4ayz4tqoP3MolFELzcH1x1fzwEYRJTPO0IELutZ5HQ0c26/GqAYy79u3g==",
+      "version": "2.3.13",
+      "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz",
+      "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==",
       "dev": true,
       "dependencies": {
-        "call-bind": "^1.0.5"
+        "call-bind": "^1.0.7"
       },
       "engines": {
         "node": ">= 0.4"
@@ -2725,29 +2722,33 @@
         "node": ">=8"
       }
     },
+    "node_modules/@microsoft/tsdoc": {
+      "version": "0.14.2",
+      "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz",
+      "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug=="
+    },
     "node_modules/@nestjs/axios": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.1.tgz",
-      "integrity": "sha512-VlOZhAGDmOoFdsmewn8AyClAdGpKXQQaY1+3PGB+g6ceurGIdTxZgRX3VXc1T6Zs60PedWjg3A82TDOB05mrzQ==",
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.2.tgz",
+      "integrity": "sha512-Z6GuOUdNQjP7FX+OuV2Ybyamse+/e0BFdTWBX5JxpBDKA+YkdLynDgG6HTF04zy6e9zPa19UX0WA2VDoehwhXQ==",
       "peerDependencies": {
         "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0",
         "axios": "^1.3.1",
-        "reflect-metadata": "^0.1.12",
         "rxjs": "^6.0.0 || ^7.0.0"
       }
     },
     "node_modules/@nestjs/cli": {
-      "version": "10.3.0",
-      "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.3.0.tgz",
-      "integrity": "sha512-37h+wSDItY0NE/x3a/M9yb2cXzfsD4qoE26rHgFn592XXLelDN12wdnfn7dTIaiRZT7WOCdQ+BYP9mQikR4AsA==",
+      "version": "10.3.2",
+      "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.3.2.tgz",
+      "integrity": "sha512-aWmD1GLluWrbuC4a1Iz/XBk5p74Uj6nIVZj6Ov03JbTfgtWqGFLtXuMetvzMiHxfrHehx/myt2iKAPRhKdZvTg==",
       "dev": true,
       "dependencies": {
-        "@angular-devkit/core": "17.0.9",
-        "@angular-devkit/schematics": "17.0.9",
-        "@angular-devkit/schematics-cli": "17.0.9",
+        "@angular-devkit/core": "17.1.2",
+        "@angular-devkit/schematics": "17.1.2",
+        "@angular-devkit/schematics-cli": "17.1.2",
         "@nestjs/schematics": "^10.0.1",
         "chalk": "4.1.2",
-        "chokidar": "3.5.3",
+        "chokidar": "3.6.0",
         "cli-table3": "0.6.3",
         "commander": "4.1.1",
         "fork-ts-checker-webpack-plugin": "9.0.2",
@@ -2762,7 +2763,7 @@
         "tsconfig-paths": "4.2.0",
         "tsconfig-paths-webpack-plugin": "4.1.0",
         "typescript": "5.3.3",
-        "webpack": "5.89.0",
+        "webpack": "5.90.1",
         "webpack-node-externals": "3.0.0"
       },
       "bin": {
@@ -2772,7 +2773,7 @@
         "node": ">= 16.14"
       },
       "peerDependencies": {
-        "@swc/cli": "^0.1.62",
+        "@swc/cli": "^0.1.62 || ^0.3.0",
         "@swc/core": "^1.3.62"
       },
       "peerDependenciesMeta": {
@@ -2784,10 +2785,23 @@
         }
       }
     },
+    "node_modules/@nestjs/cli/node_modules/typescript": {
+      "version": "5.3.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
+      "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
+      "dev": true,
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
     "node_modules/@nestjs/common": {
-      "version": "10.3.1",
-      "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.1.tgz",
-      "integrity": "sha512-YuxeIlVemVQCuXMkNbBpNlmwZgp/Cu6dwCOjki63mhyYHEFX48GNNA4zZn5MFRjF4h7VSceABsScROuzsxs9LA==",
+      "version": "10.3.7",
+      "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.7.tgz",
+      "integrity": "sha512-gKFtFzcJznrwsRYjtNZoPAvSOPYdNgxbTYoAyLTpoy393cIKgLmJTHu6ReH8/qIB9AaZLdGaFLkx98W/tFWFUw==",
       "dependencies": {
         "iterare": "1.2.1",
         "tslib": "2.6.2",
@@ -2800,7 +2814,7 @@
       "peerDependencies": {
         "class-transformer": "*",
         "class-validator": "*",
-        "reflect-metadata": "^0.1.12",
+        "reflect-metadata": "^0.1.12 || ^0.2.0",
         "rxjs": "^7.1.0"
       },
       "peerDependenciesMeta": {
@@ -2813,35 +2827,24 @@
       }
     },
     "node_modules/@nestjs/config": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.1.1.tgz",
-      "integrity": "sha512-qu5QlNiJdqQtOsnB6lx4JCXPQ96jkKUsOGd+JXfXwqJqZcOSAq6heNFg0opW4pq4J/VZoNwoo87TNnx9wthnqQ==",
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.2.tgz",
+      "integrity": "sha512-vGICPOui5vE6kPz1iwQ7oCnp3qWgqxldPmBQ9onkVoKlBtyc83KJCr7CjuVtf4OdovMAVcux1d8Q6jglU2ZphA==",
       "dependencies": {
-        "dotenv": "16.3.1",
+        "dotenv": "16.4.5",
         "dotenv-expand": "10.0.0",
         "lodash": "4.17.21",
-        "uuid": "9.0.0"
+        "uuid": "9.0.1"
       },
       "peerDependencies": {
         "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
-        "reflect-metadata": "^0.1.13"
-      }
-    },
-    "node_modules/@nestjs/config/node_modules/dotenv": {
-      "version": "16.3.1",
-      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
-      "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/motdotla/dotenv?sponsor=1"
+        "rxjs": "^7.1.0"
       }
     },
     "node_modules/@nestjs/core": {
-      "version": "10.3.1",
-      "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.1.tgz",
-      "integrity": "sha512-mh6FwTKh2R3CmLRuB50BF5q/lzc+Mz+7qAlEvpgCiTSIfSXzbQ47vWpfgLirwkL3SlCvtFS8onxOeI69RpxvXA==",
+      "version": "10.3.7",
+      "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.7.tgz",
+      "integrity": "sha512-hsdlnfiQ3kgqHL5k7js3CU0PV7hBJVi+LfFMgCkoagRxNMf67z0GFGeOV2jk5d65ssB19qdYsDa1MGVuEaoUpg==",
       "hasInstallScript": true,
       "dependencies": {
         "@nuxtjs/opencollective": "0.3.2",
@@ -2860,7 +2863,7 @@
         "@nestjs/microservices": "^10.0.0",
         "@nestjs/platform-express": "^10.0.0",
         "@nestjs/websockets": "^10.0.0",
-        "reflect-metadata": "^0.1.12",
+        "reflect-metadata": "^0.1.12 || ^0.2.0",
         "rxjs": "^7.1.0"
       },
       "peerDependenciesMeta": {
@@ -2876,14 +2879,14 @@
       }
     },
     "node_modules/@nestjs/mapped-types": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.4.tgz",
-      "integrity": "sha512-xl+gUSp0B+ln1VSNoUftlglk8dfpUes3DHGxKZ5knuBxS5g2H/8p9/DSBOYWUfO5f4u9s6ffBPZ71WO+tbe5SA==",
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz",
+      "integrity": "sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==",
       "peerDependencies": {
         "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
         "class-transformer": "^0.4.0 || ^0.5.0",
         "class-validator": "^0.13.0 || ^0.14.0",
-        "reflect-metadata": "^0.1.12"
+        "reflect-metadata": "^0.1.12 || ^0.2.0"
       },
       "peerDependenciesMeta": {
         "class-transformer": {
@@ -2904,13 +2907,13 @@
       }
     },
     "node_modules/@nestjs/platform-express": {
-      "version": "10.3.1",
-      "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.1.tgz",
-      "integrity": "sha512-Rj21quI5h4Lry7q9an+nO4ADQiQUy9A6XK74o5aTUHo3Ysm25ujqh2NgU4XbT3M2oXU9qzhE59OfhkQ7ZUvTAg==",
+      "version": "10.3.7",
+      "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.7.tgz",
+      "integrity": "sha512-noNJ+PyIxQJLCKfuXz0tcQtlVAynfLIuKy62g70lEZ86UrIqSrZFqvWs/rFUgkbT6J8H7Rmv11hASOnX+7M2rA==",
       "dependencies": {
         "body-parser": "1.20.2",
         "cors": "2.8.5",
-        "express": "4.18.2",
+        "express": "4.19.2",
         "multer": "1.4.4-lts.1",
         "tslib": "2.6.2"
       },
@@ -2924,39 +2927,46 @@
       }
     },
     "node_modules/@nestjs/schematics": {
-      "version": "10.1.0",
-      "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.1.0.tgz",
-      "integrity": "sha512-HQWvD3F7O0Sv3qHS2jineWxPLmBTLlyjT6VdSw2EAIXulitmV+ErxB3TCVQQORlNkl5p5cwRYWyBaOblDbNFIQ==",
+      "version": "10.1.1",
+      "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.1.1.tgz",
+      "integrity": "sha512-o4lfCnEeIkfJhGBbLZxTuVWcGuqDCFwg5OrvpgRUBM7vI/vONvKKiB5riVNpO+JqXoH0I42NNeDb0m4V5RREig==",
       "dev": true,
       "dependencies": {
-        "@angular-devkit/core": "17.0.9",
-        "@angular-devkit/schematics": "17.0.9",
+        "@angular-devkit/core": "17.1.2",
+        "@angular-devkit/schematics": "17.1.2",
         "comment-json": "4.2.3",
-        "jsonc-parser": "3.2.0",
+        "jsonc-parser": "3.2.1",
         "pluralize": "8.0.0"
       },
       "peerDependencies": {
         "typescript": ">=4.8.2"
       }
     },
+    "node_modules/@nestjs/schematics/node_modules/jsonc-parser": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz",
+      "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==",
+      "dev": true
+    },
     "node_modules/@nestjs/swagger": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.2.0.tgz",
-      "integrity": "sha512-W7WPq561/79w27ZEgViXS7c5hqPwT7QXhsLsSeu2jeBROUhMM825QKDFKbMmtb643IW5dznJ4PjherlZZgtMvg==",
+      "version": "7.3.1",
+      "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.3.1.tgz",
+      "integrity": "sha512-LUC4mr+5oAleEC/a2j8pNRh1S5xhKXJ1Gal5ZdRjt9XebQgbngXCdW7JTA9WOEcwGtFZN9EnKYdquzH971LZfw==",
       "dependencies": {
-        "@nestjs/mapped-types": "2.0.4",
+        "@microsoft/tsdoc": "^0.14.2",
+        "@nestjs/mapped-types": "2.0.5",
         "js-yaml": "4.1.0",
         "lodash": "4.17.21",
         "path-to-regexp": "3.2.0",
-        "swagger-ui-dist": "5.11.0"
+        "swagger-ui-dist": "5.11.2"
       },
       "peerDependencies": {
-        "@fastify/static": "^6.0.0",
+        "@fastify/static": "^6.0.0 || ^7.0.0",
         "@nestjs/common": "^9.0.0 || ^10.0.0",
         "@nestjs/core": "^9.0.0 || ^10.0.0",
         "class-transformer": "*",
         "class-validator": "*",
-        "reflect-metadata": "^0.1.12"
+        "reflect-metadata": "^0.1.12 || ^0.2.0"
       },
       "peerDependenciesMeta": {
         "@fastify/static": {
@@ -2971,9 +2981,9 @@
       }
     },
     "node_modules/@nestjs/terminus": {
-      "version": "10.2.1",
-      "resolved": "https://registry.npmjs.org/@nestjs/terminus/-/terminus-10.2.1.tgz",
-      "integrity": "sha512-23abPhotIP4+hrCZ8YkLEOmZ3m7eUYh1QOwdyrNkU9eMz/nc5LpVzy7jFbsNUuvlnT4MPV/7KXfyQTruQkTouw==",
+      "version": "10.2.3",
+      "resolved": "https://registry.npmjs.org/@nestjs/terminus/-/terminus-10.2.3.tgz",
+      "integrity": "sha512-iX7gXtAooePcyQqFt57aDke5MzgdkBeYgF5YsFNNFwOiAFdIQEhfv3PR0G+HlH9F6D7nBCDZt9U87Pks/qHijg==",
       "dependencies": {
         "boxen": "5.1.2",
         "check-disk-space": "3.4.0"
@@ -2992,7 +3002,7 @@
         "@nestjs/typeorm": "^9.0.0 || ^10.0.0",
         "@prisma/client": "*",
         "mongoose": "*",
-        "reflect-metadata": "0.1.x",
+        "reflect-metadata": "0.1.x || 0.2.x",
         "rxjs": "7.x",
         "sequelize": "*",
         "typeorm": "*"
@@ -3040,9 +3050,9 @@
       }
     },
     "node_modules/@nestjs/testing": {
-      "version": "10.3.1",
-      "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.1.tgz",
-      "integrity": "sha512-74aSAugWT31jSPnStyRWDXgjHXWO3GYaUfAZ2T7Dml88UGkGy95iwaWgYy7aYM8/xVFKcDYkfL5FAYqZYce/yg==",
+      "version": "10.3.7",
+      "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.7.tgz",
+      "integrity": "sha512-PmwZXyoCC/m3F3IFgpgD+SNN6cDPQa/vi3YQxFruvfX3cuHq+P6ZFvBB7hwaKKsLlhA0so42LsMm41oFBkdouw==",
       "dev": true,
       "dependencies": {
         "tslib": "2.6.2"
@@ -3067,32 +3077,20 @@
       }
     },
     "node_modules/@nestjs/typeorm": {
-      "version": "10.0.1",
-      "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-10.0.1.tgz",
-      "integrity": "sha512-YVFYL7D25VAVp5/G+KLXIgsRfYomA+VaFZBpm2rtwrrBOmkXNrxr7kuI2bBBO/Xy4kKBDe6wbvIVVFeEA7/ngA==",
+      "version": "10.0.2",
+      "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-10.0.2.tgz",
+      "integrity": "sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ==",
       "dependencies": {
         "uuid": "9.0.1"
       },
       "peerDependencies": {
         "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
         "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0",
-        "reflect-metadata": "^0.1.13",
+        "reflect-metadata": "^0.1.13 || ^0.2.0",
         "rxjs": "^7.2.0",
         "typeorm": "^0.3.0"
       }
     },
-    "node_modules/@nestjs/typeorm/node_modules/uuid": {
-      "version": "9.0.1",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
-      "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
-      "funding": [
-        "https://github.com/sponsors/broofa",
-        "https://github.com/sponsors/ctavan"
-      ],
-      "bin": {
-        "uuid": "dist/bin/uuid"
-      }
-    },
     "node_modules/@nodelib/fs.scandir": {
       "version": "2.1.5",
       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -3197,42 +3195,34 @@
       "resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-0.5.0.tgz",
       "integrity": "sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ=="
     },
-    "node_modules/@tootallnate/once": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
-      "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
-      "engines": {
-        "node": ">= 10"
-      }
-    },
     "node_modules/@tsconfig/node10": {
-      "version": "1.0.9",
-      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
-      "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
-      "devOptional": true
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
+      "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
+      "dev": true
     },
     "node_modules/@tsconfig/node12": {
       "version": "1.0.11",
       "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
       "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
-      "devOptional": true
+      "dev": true
     },
     "node_modules/@tsconfig/node14": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
       "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
-      "devOptional": true
+      "dev": true
     },
     "node_modules/@tsconfig/node16": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
       "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
-      "devOptional": true
+      "dev": true
     },
     "node_modules/@tsconfig/node21": {
-      "version": "21.0.1",
-      "resolved": "https://registry.npmjs.org/@tsconfig/node21/-/node21-21.0.1.tgz",
-      "integrity": "sha512-2Khg79N+z2Qkb9SjLzOi8cz2PSa/oUpHIeQm1YWzmWXkoFcPXFZSHgs+Z8iPCDjIoXFqMNYntiTXxfLYQMcRhw==",
+      "version": "21.0.3",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node21/-/node21-21.0.3.tgz",
+      "integrity": "sha512-qTX4pGNfnRTutaiRPx+c4GFL1DB1u6GHkwfoYSNn/KelE9m86Mkn3RpFBhhxDh6yHeqaVuAx0tz+n7LrjBUEpw==",
       "dev": true
     },
     "node_modules/@types/babel__core": {
@@ -3317,9 +3307,9 @@
       "dev": true
     },
     "node_modules/@types/eslint": {
-      "version": "8.56.2",
-      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz",
-      "integrity": "sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==",
+      "version": "8.56.7",
+      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.7.tgz",
+      "integrity": "sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA==",
       "dev": true,
       "dependencies": {
         "@types/estree": "*",
@@ -3355,9 +3345,9 @@
       }
     },
     "node_modules/@types/express-serve-static-core": {
-      "version": "4.17.42",
-      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.42.tgz",
-      "integrity": "sha512-ckM3jm2bf/MfB3+spLPWYPUH573plBFwpOhqQ2WottxYV85j1HQFlxmnTq57X1yHY9awZPig06hL/cLMgNWHIQ==",
+      "version": "4.19.0",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz",
+      "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==",
       "dev": true,
       "dependencies": {
         "@types/node": "*",
@@ -3406,9 +3396,9 @@
       }
     },
     "node_modules/@types/jest": {
-      "version": "29.5.11",
-      "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.11.tgz",
-      "integrity": "sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==",
+      "version": "29.5.12",
+      "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz",
+      "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==",
       "dev": true,
       "dependencies": {
         "expect": "^29.0.0",
@@ -3428,9 +3418,9 @@
       "dev": true
     },
     "node_modules/@types/lodash": {
-      "version": "4.14.202",
-      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz",
-      "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==",
+      "version": "4.17.0",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
+      "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==",
       "dev": true
     },
     "node_modules/@types/methods": {
@@ -3452,10 +3442,9 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "20.11.10",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.10.tgz",
-      "integrity": "sha512-rZEfe/hJSGYmdfX9tvcPMYeYPW2sNl50nsw4jZmRcaG0HIAb0WYEpsB05GOb53vjqpyE9GUhlDQ4jLSoB5q9kg==",
-      "devOptional": true,
+      "version": "20.12.7",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
+      "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
       "dependencies": {
         "undici-types": "~5.26.4"
       }
@@ -3467,9 +3456,9 @@
       "dev": true
     },
     "node_modules/@types/qs": {
-      "version": "6.9.11",
-      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz",
-      "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==",
+      "version": "6.9.14",
+      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz",
+      "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==",
       "dev": true
     },
     "node_modules/@types/range-parser": {
@@ -3478,10 +3467,19 @@
       "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
       "dev": true
     },
+    "node_modules/@types/readable-stream": {
+      "version": "4.0.11",
+      "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.11.tgz",
+      "integrity": "sha512-R3eUMUTTKoIoaz7UpYLxvZCrOmCRPRbAmoDDHKcimTEySltaJhF8hLzj4+EzyDifiX5eK6oDQGSfmNnXjxZzYQ==",
+      "dependencies": {
+        "@types/node": "*",
+        "safe-buffer": "~5.1.1"
+      }
+    },
     "node_modules/@types/semver": {
-      "version": "7.5.6",
-      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
-      "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
+      "version": "7.5.8",
+      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
+      "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
       "dev": true
     },
     "node_modules/@types/send": {
@@ -3495,14 +3493,14 @@
       }
     },
     "node_modules/@types/serve-static": {
-      "version": "1.15.5",
-      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz",
-      "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==",
+      "version": "1.15.7",
+      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
+      "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
       "dev": true,
       "dependencies": {
         "@types/http-errors": "*",
-        "@types/mime": "*",
-        "@types/node": "*"
+        "@types/node": "*",
+        "@types/send": "*"
       }
     },
     "node_modules/@types/stack-utils": {
@@ -3512,9 +3510,9 @@
       "dev": true
     },
     "node_modules/@types/superagent": {
-      "version": "8.1.3",
-      "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.3.tgz",
-      "integrity": "sha512-R/CfN6w2XsixLb1Ii8INfn+BT9sGPvw74OavfkW4SwY+jeUcAwLZv2+bXLJkndnimxjEBm0RPHgcjW9pLCa8cw==",
+      "version": "8.1.6",
+      "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.6.tgz",
+      "integrity": "sha512-yzBOv+6meEHSzV2NThYYOA6RtqvPr3Hbob9ZLp3i07SH27CrYVfm8CrF7ydTmidtelsFiKx2I4gZAiAOamGgvQ==",
       "dev": true,
       "dependencies": {
         "@types/cookiejar": "^2.1.5",
@@ -3533,9 +3531,9 @@
       }
     },
     "node_modules/@types/validator": {
-      "version": "13.11.8",
-      "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.8.tgz",
-      "integrity": "sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ=="
+      "version": "13.11.9",
+      "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.9.tgz",
+      "integrity": "sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw=="
     },
     "node_modules/@types/yargs": {
       "version": "17.0.32",
@@ -3553,16 +3551,16 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "6.19.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.1.tgz",
-      "integrity": "sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg==",
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
+      "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.5.1",
-        "@typescript-eslint/scope-manager": "6.19.1",
-        "@typescript-eslint/type-utils": "6.19.1",
-        "@typescript-eslint/utils": "6.19.1",
-        "@typescript-eslint/visitor-keys": "6.19.1",
+        "@typescript-eslint/scope-manager": "6.21.0",
+        "@typescript-eslint/type-utils": "6.21.0",
+        "@typescript-eslint/utils": "6.21.0",
+        "@typescript-eslint/visitor-keys": "6.21.0",
         "debug": "^4.3.4",
         "graphemer": "^1.4.0",
         "ignore": "^5.2.4",
@@ -3588,15 +3586,15 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "6.19.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.1.tgz",
-      "integrity": "sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==",
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
+      "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "6.19.1",
-        "@typescript-eslint/types": "6.19.1",
-        "@typescript-eslint/typescript-estree": "6.19.1",
-        "@typescript-eslint/visitor-keys": "6.19.1",
+        "@typescript-eslint/scope-manager": "6.21.0",
+        "@typescript-eslint/types": "6.21.0",
+        "@typescript-eslint/typescript-estree": "6.21.0",
+        "@typescript-eslint/visitor-keys": "6.21.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -3616,13 +3614,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "6.19.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.1.tgz",
-      "integrity": "sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w==",
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
+      "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "6.19.1",
-        "@typescript-eslint/visitor-keys": "6.19.1"
+        "@typescript-eslint/types": "6.21.0",
+        "@typescript-eslint/visitor-keys": "6.21.0"
       },
       "engines": {
         "node": "^16.0.0 || >=18.0.0"
@@ -3633,13 +3631,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "6.19.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.1.tgz",
-      "integrity": "sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg==",
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz",
+      "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "6.19.1",
-        "@typescript-eslint/utils": "6.19.1",
+        "@typescript-eslint/typescript-estree": "6.21.0",
+        "@typescript-eslint/utils": "6.21.0",
         "debug": "^4.3.4",
         "ts-api-utils": "^1.0.1"
       },
@@ -3660,9 +3658,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "6.19.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.1.tgz",
-      "integrity": "sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==",
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
+      "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
       "dev": true,
       "engines": {
         "node": "^16.0.0 || >=18.0.0"
@@ -3673,13 +3671,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "6.19.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.1.tgz",
-      "integrity": "sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==",
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
+      "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "6.19.1",
-        "@typescript-eslint/visitor-keys": "6.19.1",
+        "@typescript-eslint/types": "6.21.0",
+        "@typescript-eslint/visitor-keys": "6.21.0",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -3701,17 +3699,17 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "6.19.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.1.tgz",
-      "integrity": "sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w==",
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
+      "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
         "@types/json-schema": "^7.0.12",
         "@types/semver": "^7.5.0",
-        "@typescript-eslint/scope-manager": "6.19.1",
-        "@typescript-eslint/types": "6.19.1",
-        "@typescript-eslint/typescript-estree": "6.19.1",
+        "@typescript-eslint/scope-manager": "6.21.0",
+        "@typescript-eslint/types": "6.21.0",
+        "@typescript-eslint/typescript-estree": "6.21.0",
         "semver": "^7.5.4"
       },
       "engines": {
@@ -3726,12 +3724,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "6.19.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.1.tgz",
-      "integrity": "sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==",
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
+      "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "6.19.1",
+        "@typescript-eslint/types": "6.21.0",
         "eslint-visitor-keys": "^3.4.1"
       },
       "engines": {
@@ -3749,9 +3747,9 @@
       "dev": true
     },
     "node_modules/@webassemblyjs/ast": {
-      "version": "1.11.6",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
-      "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==",
+      "version": "1.12.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
+      "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==",
       "dev": true,
       "dependencies": {
         "@webassemblyjs/helper-numbers": "1.11.6",
@@ -3771,9 +3769,9 @@
       "dev": true
     },
     "node_modules/@webassemblyjs/helper-buffer": {
-      "version": "1.11.6",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz",
-      "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==",
+      "version": "1.12.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz",
+      "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==",
       "dev": true
     },
     "node_modules/@webassemblyjs/helper-numbers": {
@@ -3794,15 +3792,15 @@
       "dev": true
     },
     "node_modules/@webassemblyjs/helper-wasm-section": {
-      "version": "1.11.6",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz",
-      "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==",
+      "version": "1.12.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz",
+      "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==",
       "dev": true,
       "dependencies": {
-        "@webassemblyjs/ast": "1.11.6",
-        "@webassemblyjs/helper-buffer": "1.11.6",
+        "@webassemblyjs/ast": "1.12.1",
+        "@webassemblyjs/helper-buffer": "1.12.1",
         "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
-        "@webassemblyjs/wasm-gen": "1.11.6"
+        "@webassemblyjs/wasm-gen": "1.12.1"
       }
     },
     "node_modules/@webassemblyjs/ieee754": {
@@ -3830,28 +3828,28 @@
       "dev": true
     },
     "node_modules/@webassemblyjs/wasm-edit": {
-      "version": "1.11.6",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz",
-      "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==",
+      "version": "1.12.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz",
+      "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==",
       "dev": true,
       "dependencies": {
-        "@webassemblyjs/ast": "1.11.6",
-        "@webassemblyjs/helper-buffer": "1.11.6",
+        "@webassemblyjs/ast": "1.12.1",
+        "@webassemblyjs/helper-buffer": "1.12.1",
         "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
-        "@webassemblyjs/helper-wasm-section": "1.11.6",
-        "@webassemblyjs/wasm-gen": "1.11.6",
-        "@webassemblyjs/wasm-opt": "1.11.6",
-        "@webassemblyjs/wasm-parser": "1.11.6",
-        "@webassemblyjs/wast-printer": "1.11.6"
+        "@webassemblyjs/helper-wasm-section": "1.12.1",
+        "@webassemblyjs/wasm-gen": "1.12.1",
+        "@webassemblyjs/wasm-opt": "1.12.1",
+        "@webassemblyjs/wasm-parser": "1.12.1",
+        "@webassemblyjs/wast-printer": "1.12.1"
       }
     },
     "node_modules/@webassemblyjs/wasm-gen": {
-      "version": "1.11.6",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz",
-      "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==",
+      "version": "1.12.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz",
+      "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==",
       "dev": true,
       "dependencies": {
-        "@webassemblyjs/ast": "1.11.6",
+        "@webassemblyjs/ast": "1.12.1",
         "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
         "@webassemblyjs/ieee754": "1.11.6",
         "@webassemblyjs/leb128": "1.11.6",
@@ -3859,24 +3857,24 @@
       }
     },
     "node_modules/@webassemblyjs/wasm-opt": {
-      "version": "1.11.6",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz",
-      "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==",
+      "version": "1.12.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz",
+      "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==",
       "dev": true,
       "dependencies": {
-        "@webassemblyjs/ast": "1.11.6",
-        "@webassemblyjs/helper-buffer": "1.11.6",
-        "@webassemblyjs/wasm-gen": "1.11.6",
-        "@webassemblyjs/wasm-parser": "1.11.6"
+        "@webassemblyjs/ast": "1.12.1",
+        "@webassemblyjs/helper-buffer": "1.12.1",
+        "@webassemblyjs/wasm-gen": "1.12.1",
+        "@webassemblyjs/wasm-parser": "1.12.1"
       }
     },
     "node_modules/@webassemblyjs/wasm-parser": {
-      "version": "1.11.6",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz",
-      "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==",
+      "version": "1.12.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz",
+      "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==",
       "dev": true,
       "dependencies": {
-        "@webassemblyjs/ast": "1.11.6",
+        "@webassemblyjs/ast": "1.12.1",
         "@webassemblyjs/helper-api-error": "1.11.6",
         "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
         "@webassemblyjs/ieee754": "1.11.6",
@@ -3885,12 +3883,12 @@
       }
     },
     "node_modules/@webassemblyjs/wast-printer": {
-      "version": "1.11.6",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz",
-      "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==",
+      "version": "1.12.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz",
+      "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==",
       "dev": true,
       "dependencies": {
-        "@webassemblyjs/ast": "1.11.6",
+        "@webassemblyjs/ast": "1.12.1",
         "@xtuc/long": "4.2.2"
       }
     },
@@ -3933,7 +3931,7 @@
       "version": "8.11.3",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
       "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
-      "devOptional": true,
+      "dev": true,
       "bin": {
         "acorn": "bin/acorn"
       },
@@ -3963,31 +3961,31 @@
       "version": "8.3.2",
       "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
       "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
-      "devOptional": true,
+      "dev": true,
       "engines": {
         "node": ">=0.4.0"
       }
     },
     "node_modules/agent-base": {
-      "version": "6.0.2",
-      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
-      "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
+      "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
       "dependencies": {
-        "debug": "4"
+        "debug": "^4.3.4"
       },
       "engines": {
-        "node": ">= 6.0.0"
+        "node": ">= 14"
       }
     },
     "node_modules/ajv": {
-      "version": "6.12.6",
-      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
-      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "version": "8.12.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+      "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
       "dev": true,
       "dependencies": {
         "fast-deep-equal": "^3.1.1",
-        "fast-json-stable-stringify": "^2.0.0",
-        "json-schema-traverse": "^0.4.1",
+        "json-schema-traverse": "^1.0.0",
+        "require-from-string": "^2.0.2",
         "uri-js": "^4.2.2"
       },
       "funding": {
@@ -4012,28 +4010,6 @@
         }
       }
     },
-    "node_modules/ajv-formats/node_modules/ajv": {
-      "version": "8.12.0",
-      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
-      "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
-      "dev": true,
-      "dependencies": {
-        "fast-deep-equal": "^3.1.1",
-        "json-schema-traverse": "^1.0.0",
-        "require-from-string": "^2.0.2",
-        "uri-js": "^4.2.2"
-      },
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/epoberezkin"
-      }
-    },
-    "node_modules/ajv-formats/node_modules/json-schema-traverse": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
-      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
-      "dev": true
-    },
     "node_modules/ajv-keywords": {
       "version": "3.5.2",
       "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -4159,7 +4135,7 @@
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
       "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
-      "devOptional": true
+      "dev": true
     },
     "node_modules/argparse": {
       "version": "2.0.1",
@@ -4176,12 +4152,15 @@
       }
     },
     "node_modules/array-buffer-byte-length": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
-      "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz",
+      "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==",
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "is-array-buffer": "^3.0.1"
+        "call-bind": "^1.0.5",
+        "is-array-buffer": "^3.0.4"
+      },
+      "engines": {
+        "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
@@ -4199,15 +4178,16 @@
       "dev": true
     },
     "node_modules/array-includes": {
-      "version": "3.1.7",
-      "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz",
-      "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==",
+      "version": "3.1.8",
+      "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz",
+      "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==",
       "dev": true,
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.2.0",
-        "es-abstract": "^1.22.1",
-        "get-intrinsic": "^1.2.1",
+        "call-bind": "^1.0.7",
+        "define-properties": "^1.2.1",
+        "es-abstract": "^1.23.2",
+        "es-object-atoms": "^1.0.0",
+        "get-intrinsic": "^1.2.4",
         "is-string": "^1.0.7"
       },
       "engines": {
@@ -4232,17 +4212,38 @@
         "node": ">=8"
       }
     },
+    "node_modules/array.prototype.findlast": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz",
+      "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
+      "dev": true,
+      "dependencies": {
+        "call-bind": "^1.0.7",
+        "define-properties": "^1.2.1",
+        "es-abstract": "^1.23.2",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.0.0",
+        "es-shim-unscopables": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/array.prototype.findlastindex": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz",
-      "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==",
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz",
+      "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==",
       "dev": true,
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.2.0",
-        "es-abstract": "^1.22.1",
-        "es-shim-unscopables": "^1.0.0",
-        "get-intrinsic": "^1.2.1"
+        "call-bind": "^1.0.7",
+        "define-properties": "^1.2.1",
+        "es-abstract": "^1.23.2",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.0.0",
+        "es-shim-unscopables": "^1.0.2"
       },
       "engines": {
         "node": ">= 0.4"
@@ -4287,30 +4288,43 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/array.prototype.tosorted": {
+    "node_modules/array.prototype.toreversed": {
       "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz",
-      "integrity": "sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==",
+      "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz",
+      "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==",
       "dev": true,
       "dependencies": {
         "call-bind": "^1.0.2",
         "define-properties": "^1.2.0",
         "es-abstract": "^1.22.1",
-        "es-shim-unscopables": "^1.0.0",
-        "get-intrinsic": "^1.2.1"
+        "es-shim-unscopables": "^1.0.0"
+      }
+    },
+    "node_modules/array.prototype.tosorted": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz",
+      "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==",
+      "dev": true,
+      "dependencies": {
+        "call-bind": "^1.0.5",
+        "define-properties": "^1.2.1",
+        "es-abstract": "^1.22.3",
+        "es-errors": "^1.1.0",
+        "es-shim-unscopables": "^1.0.2"
       }
     },
     "node_modules/arraybuffer.prototype.slice": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz",
-      "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==",
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz",
+      "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==",
       "dependencies": {
-        "array-buffer-byte-length": "^1.0.0",
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.2.0",
-        "es-abstract": "^1.22.1",
-        "get-intrinsic": "^1.2.1",
-        "is-array-buffer": "^3.0.2",
+        "array-buffer-byte-length": "^1.0.1",
+        "call-bind": "^1.0.5",
+        "define-properties": "^1.2.1",
+        "es-abstract": "^1.22.3",
+        "es-errors": "^1.2.1",
+        "get-intrinsic": "^1.2.3",
+        "is-array-buffer": "^3.0.4",
         "is-shared-array-buffer": "^1.0.2"
       },
       "engines": {
@@ -4341,15 +4355,6 @@
       "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
       "dev": true
     },
-    "node_modules/asynciterator.prototype": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz",
-      "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==",
-      "dev": true,
-      "dependencies": {
-        "has-symbols": "^1.0.3"
-      }
-    },
     "node_modules/asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -4364,9 +4369,12 @@
       }
     },
     "node_modules/available-typed-arrays": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
-      "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+      "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
+      "dependencies": {
+        "possible-typed-array-names": "^1.0.0"
+      },
       "engines": {
         "node": ">= 0.4"
       },
@@ -4384,11 +4392,11 @@
       }
     },
     "node_modules/axios": {
-      "version": "1.6.7",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
-      "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
+      "version": "1.6.8",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
+      "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
       "dependencies": {
-        "follow-redirects": "^1.15.4",
+        "follow-redirects": "^1.15.6",
         "form-data": "^4.0.0",
         "proxy-from-env": "^1.1.0"
       }
@@ -4554,12 +4562,15 @@
       }
     },
     "node_modules/binary-extensions": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
-      "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+      "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
       "dev": true,
       "engines": {
         "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
       }
     },
     "node_modules/bl": {
@@ -4664,9 +4675,9 @@
       }
     },
     "node_modules/browserslist": {
-      "version": "4.22.3",
-      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz",
-      "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==",
+      "version": "4.23.0",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
+      "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
       "dev": true,
       "funding": [
         {
@@ -4683,8 +4694,8 @@
         }
       ],
       "dependencies": {
-        "caniuse-lite": "^1.0.30001580",
-        "electron-to-chromium": "^1.4.648",
+        "caniuse-lite": "^1.0.30001587",
+        "electron-to-chromium": "^1.4.668",
         "node-releases": "^2.0.14",
         "update-browserslist-db": "^1.0.13"
       },
@@ -4770,13 +4781,18 @@
       }
     },
     "node_modules/call-bind": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
-      "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+      "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
       "dependencies": {
+        "es-define-property": "^1.0.0",
+        "es-errors": "^1.3.0",
         "function-bind": "^1.1.2",
-        "get-intrinsic": "^1.2.1",
-        "set-function-length": "^1.1.1"
+        "get-intrinsic": "^1.2.4",
+        "set-function-length": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
@@ -4829,9 +4845,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001580",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz",
-      "integrity": "sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA==",
+      "version": "1.0.30001608",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001608.tgz",
+      "integrity": "sha512-cjUJTQkk9fQlJR2s4HMuPMvTiRggl0rAVMtthQuyOlDWuqHXqN8azLq+pi8B2TjwKJ32diHjUqRIKeFX4z1FoA==",
       "dev": true,
       "funding": [
         {
@@ -4920,16 +4936,10 @@
       }
     },
     "node_modules/chokidar": {
-      "version": "3.5.3",
-      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
-      "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
       "dev": true,
-      "funding": [
-        {
-          "type": "individual",
-          "url": "https://paulmillr.com/funding/"
-        }
-      ],
       "dependencies": {
         "anymatch": "~3.1.2",
         "braces": "~3.0.2",
@@ -4942,6 +4952,9 @@
       "engines": {
         "node": ">= 8.10.0"
       },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      },
       "optionalDependencies": {
         "fsevents": "~2.3.2"
       }
@@ -5513,9 +5526,9 @@
       "dev": true
     },
     "node_modules/cookie": {
-      "version": "0.5.0",
-      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
-      "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+      "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
       "engines": {
         "node": ">= 0.6"
       }
@@ -5616,7 +5629,7 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
       "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
-      "devOptional": true
+      "dev": true
     },
     "node_modules/cross-spawn": {
       "version": "7.0.3",
@@ -5659,27 +5672,27 @@
       }
     },
     "node_modules/cspell": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/cspell/-/cspell-8.3.2.tgz",
-      "integrity": "sha512-V8Ub3RO/a5lwSsltW/ib3Z3G/sczKtSpBBN1JChzbSCfEgaY2mJY8JW0BpkSV+Ug6uJitpXNOOaxa3Xr489i7g==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/cspell/-/cspell-8.7.0.tgz",
+      "integrity": "sha512-77nRPgLl240C6FK8RKVKo34lP15Lzp/6bk+SKYJFwUKKXlcgWXDis+Lw4JolA741/JgHtuxmhW1C8P7dCKjJ3w==",
       "dev": true,
       "dependencies": {
-        "@cspell/cspell-json-reporter": "8.3.2",
-        "@cspell/cspell-pipe": "8.3.2",
-        "@cspell/cspell-types": "8.3.2",
-        "@cspell/dynamic-import": "8.3.2",
+        "@cspell/cspell-json-reporter": "8.7.0",
+        "@cspell/cspell-pipe": "8.7.0",
+        "@cspell/cspell-types": "8.7.0",
+        "@cspell/dynamic-import": "8.7.0",
         "chalk": "^5.3.0",
         "chalk-template": "^1.1.0",
-        "commander": "^11.1.0",
-        "cspell-gitignore": "8.3.2",
-        "cspell-glob": "8.3.2",
-        "cspell-io": "8.3.2",
-        "cspell-lib": "8.3.2",
+        "commander": "^12.0.0",
+        "cspell-gitignore": "8.7.0",
+        "cspell-glob": "8.7.0",
+        "cspell-io": "8.7.0",
+        "cspell-lib": "8.7.0",
         "fast-glob": "^3.3.2",
         "fast-json-stable-stringify": "^2.1.0",
         "file-entry-cache": "^8.0.0",
         "get-stdin": "^9.0.0",
-        "semver": "^7.5.4",
+        "semver": "^7.6.0",
         "strip-ansi": "^7.1.0",
         "vscode-uri": "^3.0.8"
       },
@@ -5695,42 +5708,42 @@
       }
     },
     "node_modules/cspell-config-lib": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-8.3.2.tgz",
-      "integrity": "sha512-Wc98XhBNLwDxnxCzMtgRJALI9a69cu3C5Gf1rGjNTKSFo9JYiQmju0Ur3z25Pkx9Sa86f+2IjvNCf33rUDSoBQ==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-8.7.0.tgz",
+      "integrity": "sha512-depsd01GbLBo71/tfRrL5iECWQLS4CjCxA9C01dVkFAJqVB0s+K9KLKjTlq5aHOhcvo9Z3dHV+bGQCf5/Q7bfw==",
       "dev": true,
       "dependencies": {
-        "@cspell/cspell-types": "8.3.2",
+        "@cspell/cspell-types": "8.7.0",
         "comment-json": "^4.2.3",
-        "yaml": "^2.3.4"
+        "yaml": "^2.4.1"
       },
       "engines": {
         "node": ">=18"
       }
     },
     "node_modules/cspell-dictionary": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-8.3.2.tgz",
-      "integrity": "sha512-xyK95hO2BMPFxIo8zBwGml8035qOxSBdga1BMhwW/p2wDrQP8S4Cdm/54//tCDmKn6uRkFQvyOfWGaX2l8WMEg==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-8.7.0.tgz",
+      "integrity": "sha512-S6IpZSzIMxlOO/33NgCOuP0TPH2mZbw8d5CP44z5jajflloq8l74MeJLkeDzYfCRcm0Rtk0A5drBeMg+Ai34OA==",
       "dev": true,
       "dependencies": {
-        "@cspell/cspell-pipe": "8.3.2",
-        "@cspell/cspell-types": "8.3.2",
-        "cspell-trie-lib": "8.3.2",
+        "@cspell/cspell-pipe": "8.7.0",
+        "@cspell/cspell-types": "8.7.0",
+        "cspell-trie-lib": "8.7.0",
         "fast-equals": "^5.0.1",
-        "gensequence": "^6.0.0"
+        "gensequence": "^7.0.0"
       },
       "engines": {
         "node": ">=18"
       }
     },
     "node_modules/cspell-gitignore": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-8.3.2.tgz",
-      "integrity": "sha512-3Qc9P5BVvl/cg//s2s+zIMGKcoH5v7oOtRgwn4UQry8yiyo19h0tiTKkSR574FMhF5NtcShTnwIwPSIXVBPFHA==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-8.7.0.tgz",
+      "integrity": "sha512-yvUZ86qyopUpDgn+YXP1qTpUe/lp65ZFvpMtw21lWHTFlg1OWKntr349EQU/5ben/K6koxk1FiElCBV7Lr4uFg==",
       "dev": true,
       "dependencies": {
-        "cspell-glob": "8.3.2",
+        "cspell-glob": "8.7.0",
         "find-up-simple": "^1.0.0"
       },
       "bin": {
@@ -5741,9 +5754,9 @@
       }
     },
     "node_modules/cspell-glob": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-8.3.2.tgz",
-      "integrity": "sha512-KtIFxE+3l5dGEofND4/CdZffXP8XN1+XGQKxJ96lIzWsc01mkotfhxTkla6mgvfH039t7BsY/SWv0460KyGslQ==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-8.7.0.tgz",
+      "integrity": "sha512-AMdfx0gvROA/aIL8t8b5Y5NtMgscGZELFj6WhCSZiQSuWRxXUKiLGGLUFjx2y0hgXN9LUYOo6aBjvhnxI/v71g==",
       "dev": true,
       "dependencies": {
         "micromatch": "^4.0.5"
@@ -5753,13 +5766,13 @@
       }
     },
     "node_modules/cspell-grammar": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-8.3.2.tgz",
-      "integrity": "sha512-tYCkOmRzJe1a6/R+8QGSwG7TwTgznLPqsHtepKzLmnS4YX54VXjKRI9zMARxXDzUVfyCSVdW5MyiY/0WTNoy+A==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-8.7.0.tgz",
+      "integrity": "sha512-SGcXc7322wU2WNRi7vtpToWDXTqZHhxqvR+aIXHT2kkxlMSWp3Rvfpshd0ckgY54nZtgw7R/JtKND2jeACRpwQ==",
       "dev": true,
       "dependencies": {
-        "@cspell/cspell-pipe": "8.3.2",
-        "@cspell/cspell-types": "8.3.2"
+        "@cspell/cspell-pipe": "8.7.0",
+        "@cspell/cspell-types": "8.7.0"
       },
       "bin": {
         "cspell-grammar": "bin.mjs"
@@ -5769,40 +5782,40 @@
       }
     },
     "node_modules/cspell-io": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-8.3.2.tgz",
-      "integrity": "sha512-WYpKsyBCQP0SY4gXnhW5fPuxcYchKYKG1PIXVV3ezFU4muSgW6GuLNbGuSfwv/8YNXRgFSN0e3hYH0rdBK2Aow==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-8.7.0.tgz",
+      "integrity": "sha512-o7OltyyvVkRG1gQrIqGpN5pUkHNnv6rvihb7Qu6cJ8jITinLGuWJuEQpgt0eF5yIr624jDbFwSzAxsFox8riQg==",
       "dev": true,
       "dependencies": {
-        "@cspell/cspell-service-bus": "8.3.2"
+        "@cspell/cspell-service-bus": "8.7.0"
       },
       "engines": {
         "node": ">=18"
       }
     },
     "node_modules/cspell-lib": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-8.3.2.tgz",
-      "integrity": "sha512-wTvdaev/TyGB/ln6CVD1QbVs2D7/+QiajQ67S7yj1suLHM6YcNQQb/5sPAM8VPtj0E7PgwgPXf3bq18OtPvnFg==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-8.7.0.tgz",
+      "integrity": "sha512-qDSHZGekwiDmouYRECTQokE+hgAuPqREm+Hb+G3DoIo3ZK5H47TtEUo8fNCw22XsKefcF8X28LiyoZwiYHVpSg==",
       "dev": true,
       "dependencies": {
-        "@cspell/cspell-bundled-dicts": "8.3.2",
-        "@cspell/cspell-pipe": "8.3.2",
-        "@cspell/cspell-resolver": "8.3.2",
-        "@cspell/cspell-types": "8.3.2",
-        "@cspell/dynamic-import": "8.3.2",
-        "@cspell/strong-weak-map": "8.3.2",
+        "@cspell/cspell-bundled-dicts": "8.7.0",
+        "@cspell/cspell-pipe": "8.7.0",
+        "@cspell/cspell-resolver": "8.7.0",
+        "@cspell/cspell-types": "8.7.0",
+        "@cspell/dynamic-import": "8.7.0",
+        "@cspell/strong-weak-map": "8.7.0",
         "clear-module": "^4.1.2",
         "comment-json": "^4.2.3",
         "configstore": "^6.0.0",
-        "cspell-config-lib": "8.3.2",
-        "cspell-dictionary": "8.3.2",
-        "cspell-glob": "8.3.2",
-        "cspell-grammar": "8.3.2",
-        "cspell-io": "8.3.2",
-        "cspell-trie-lib": "8.3.2",
+        "cspell-config-lib": "8.7.0",
+        "cspell-dictionary": "8.7.0",
+        "cspell-glob": "8.7.0",
+        "cspell-grammar": "8.7.0",
+        "cspell-io": "8.7.0",
+        "cspell-trie-lib": "8.7.0",
         "fast-equals": "^5.0.1",
-        "gensequence": "^6.0.0",
+        "gensequence": "^7.0.0",
         "import-fresh": "^3.3.0",
         "resolve-from": "^5.0.0",
         "vscode-languageserver-textdocument": "^1.0.11",
@@ -5813,14 +5826,14 @@
       }
     },
     "node_modules/cspell-trie-lib": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-8.3.2.tgz",
-      "integrity": "sha512-8qh2FqzkLMwzlTlvO/5Z+89fhi30rrfekocpight/BmqKbE2XFJQD7wS2ml24e7q/rdHJLXVpJbY/V5mByucCA==",
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-8.7.0.tgz",
+      "integrity": "sha512-W3Nh2cO7gMV91r+hLqyTMgKlvRl4W5diKs5YiyOxjZumRkMBy42IzcNYtgIIacOxghklv96F5Bd1Vx/zY6ylGA==",
       "dev": true,
       "dependencies": {
-        "@cspell/cspell-pipe": "8.3.2",
-        "@cspell/cspell-types": "8.3.2",
-        "gensequence": "^6.0.0"
+        "@cspell/cspell-pipe": "8.7.0",
+        "@cspell/cspell-types": "8.7.0",
+        "gensequence": "^7.0.0"
       },
       "engines": {
         "node": ">=18"
@@ -5839,12 +5852,12 @@
       }
     },
     "node_modules/cspell/node_modules/commander": {
-      "version": "11.1.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
-      "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+      "version": "12.0.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
+      "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==",
       "dev": true,
       "engines": {
-        "node": ">=16"
+        "node": ">=18"
       }
     },
     "node_modules/damerau-levenshtein": {
@@ -5862,10 +5875,58 @@
         "node": ">=8"
       }
     },
+    "node_modules/data-view-buffer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz",
+      "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==",
+      "dependencies": {
+        "call-bind": "^1.0.6",
+        "es-errors": "^1.3.0",
+        "is-data-view": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/data-view-byte-length": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz",
+      "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==",
+      "dependencies": {
+        "call-bind": "^1.0.7",
+        "es-errors": "^1.3.0",
+        "is-data-view": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/data-view-byte-offset": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz",
+      "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==",
+      "dependencies": {
+        "call-bind": "^1.0.6",
+        "es-errors": "^1.3.0",
+        "is-data-view": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/date-fns": {
-      "version": "3.3.1",
-      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz",
-      "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==",
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
+      "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/kossnocorp"
@@ -5935,9 +5996,9 @@
       }
     },
     "node_modules/dedent": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz",
-      "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==",
+      "version": "1.5.3",
+      "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz",
+      "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==",
       "dev": true,
       "peerDependencies": {
         "babel-plugin-macros": "^3.1.0"
@@ -5976,16 +6037,19 @@
       }
     },
     "node_modules/define-data-property": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
-      "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+      "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
       "dependencies": {
-        "get-intrinsic": "^1.2.1",
-        "gopd": "^1.0.1",
-        "has-property-descriptors": "^1.0.0"
+        "es-define-property": "^1.0.0",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.0.1"
       },
       "engines": {
         "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
       }
     },
     "node_modules/define-lazy-prop": {
@@ -6038,9 +6102,9 @@
       }
     },
     "node_modules/destr": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.2.tgz",
-      "integrity": "sha512-65AlobnZMiCET00KaFFjUefxDX0khFA/E4myqZ7a6Sq1yZtR8+FVIvilVX66vF2uobSumxooYZChiRPCKNqhmg=="
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz",
+      "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ=="
     },
     "node_modules/destroy": {
       "version": "1.2.0",
@@ -6083,7 +6147,7 @@
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
       "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
-      "devOptional": true,
+      "dev": true,
       "engines": {
         "node": ">=0.3.1"
       }
@@ -6134,14 +6198,14 @@
       }
     },
     "node_modules/dotenv": {
-      "version": "16.4.1",
-      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.1.tgz",
-      "integrity": "sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==",
+      "version": "16.4.5",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
+      "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
       "engines": {
         "node": ">=12"
       },
       "funding": {
-        "url": "https://github.com/motdotla/dotenv?sponsor=1"
+        "url": "https://dotenvx.com"
       }
     },
     "node_modules/dotenv-expand": {
@@ -6176,9 +6240,9 @@
       "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.648",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.648.tgz",
-      "integrity": "sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg==",
+      "version": "1.4.733",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.733.tgz",
+      "integrity": "sha512-gUI9nhI2iBGF0OaYYLKOaOtliFMl+Bt1rY7VmEjwxOxqoYLub/D9xmduPEhbw2imE6gYkJKhIE5it+KE2ulVxQ==",
       "dev": true
     },
     "node_modules/emittery": {
@@ -6215,9 +6279,9 @@
       }
     },
     "node_modules/enhanced-resolve": {
-      "version": "5.15.0",
-      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
-      "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
+      "version": "5.16.0",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz",
+      "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==",
       "dev": true,
       "dependencies": {
         "graceful-fs": "^4.2.4",
@@ -6227,6 +6291,17 @@
         "node": ">=10.13.0"
       }
     },
+    "node_modules/envix": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/envix/-/envix-1.5.0.tgz",
+      "integrity": "sha512-IOxTKT+tffjxgvX2O5nq6enbkv6kBQ/QdMy18bZWo0P0rKPvsRp2/EypIPwTvJfnmk3VdOlq/KcRSZCswefM/w==",
+      "dependencies": {
+        "std-env": "^3.7.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
     "node_modules/error-ex": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -6237,49 +6312,56 @@
       }
     },
     "node_modules/es-abstract": {
-      "version": "1.22.3",
-      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz",
-      "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==",
-      "dependencies": {
-        "array-buffer-byte-length": "^1.0.0",
-        "arraybuffer.prototype.slice": "^1.0.2",
-        "available-typed-arrays": "^1.0.5",
-        "call-bind": "^1.0.5",
-        "es-set-tostringtag": "^2.0.1",
+      "version": "1.23.3",
+      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz",
+      "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==",
+      "dependencies": {
+        "array-buffer-byte-length": "^1.0.1",
+        "arraybuffer.prototype.slice": "^1.0.3",
+        "available-typed-arrays": "^1.0.7",
+        "call-bind": "^1.0.7",
+        "data-view-buffer": "^1.0.1",
+        "data-view-byte-length": "^1.0.1",
+        "data-view-byte-offset": "^1.0.0",
+        "es-define-property": "^1.0.0",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.0.0",
+        "es-set-tostringtag": "^2.0.3",
         "es-to-primitive": "^1.2.1",
         "function.prototype.name": "^1.1.6",
-        "get-intrinsic": "^1.2.2",
-        "get-symbol-description": "^1.0.0",
+        "get-intrinsic": "^1.2.4",
+        "get-symbol-description": "^1.0.2",
         "globalthis": "^1.0.3",
         "gopd": "^1.0.1",
-        "has-property-descriptors": "^1.0.0",
-        "has-proto": "^1.0.1",
+        "has-property-descriptors": "^1.0.2",
+        "has-proto": "^1.0.3",
         "has-symbols": "^1.0.3",
-        "hasown": "^2.0.0",
-        "internal-slot": "^1.0.5",
-        "is-array-buffer": "^3.0.2",
+        "hasown": "^2.0.2",
+        "internal-slot": "^1.0.7",
+        "is-array-buffer": "^3.0.4",
         "is-callable": "^1.2.7",
-        "is-negative-zero": "^2.0.2",
+        "is-data-view": "^1.0.1",
+        "is-negative-zero": "^2.0.3",
         "is-regex": "^1.1.4",
-        "is-shared-array-buffer": "^1.0.2",
+        "is-shared-array-buffer": "^1.0.3",
         "is-string": "^1.0.7",
-        "is-typed-array": "^1.1.12",
+        "is-typed-array": "^1.1.13",
         "is-weakref": "^1.0.2",
         "object-inspect": "^1.13.1",
         "object-keys": "^1.1.1",
-        "object.assign": "^4.1.4",
-        "regexp.prototype.flags": "^1.5.1",
-        "safe-array-concat": "^1.0.1",
-        "safe-regex-test": "^1.0.0",
-        "string.prototype.trim": "^1.2.8",
-        "string.prototype.trimend": "^1.0.7",
-        "string.prototype.trimstart": "^1.0.7",
-        "typed-array-buffer": "^1.0.0",
-        "typed-array-byte-length": "^1.0.0",
-        "typed-array-byte-offset": "^1.0.0",
-        "typed-array-length": "^1.0.4",
+        "object.assign": "^4.1.5",
+        "regexp.prototype.flags": "^1.5.2",
+        "safe-array-concat": "^1.1.2",
+        "safe-regex-test": "^1.0.3",
+        "string.prototype.trim": "^1.2.9",
+        "string.prototype.trimend": "^1.0.8",
+        "string.prototype.trimstart": "^1.0.8",
+        "typed-array-buffer": "^1.0.2",
+        "typed-array-byte-length": "^1.0.1",
+        "typed-array-byte-offset": "^1.0.2",
+        "typed-array-length": "^1.0.6",
         "unbox-primitive": "^1.0.2",
-        "which-typed-array": "^1.1.13"
+        "which-typed-array": "^1.1.15"
       },
       "engines": {
         "node": ">= 0.4"
@@ -6289,18 +6371,18 @@
       }
     },
     "node_modules/es-aggregate-error": {
-      "version": "1.0.11",
-      "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.11.tgz",
-      "integrity": "sha512-DCiZiNlMlbvofET/cE55My387NiLvuGToBEZDdK9U2G3svDCjL8WOgO5Il6lO83nQ8qmag/R9nArdpaFQ/m3lA==",
+      "version": "1.0.13",
+      "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.13.tgz",
+      "integrity": "sha512-KkzhUUuD2CUMqEc8JEqsXEMDHzDPE8RCjZeUBitsnB1eNcAJWQPiciKsMXe3Yytj4Flw1XLl46Qcf9OxvZha7A==",
       "dependencies": {
-        "define-data-property": "^1.1.0",
+        "define-data-property": "^1.1.4",
         "define-properties": "^1.2.1",
-        "es-abstract": "^1.22.1",
-        "function-bind": "^1.1.1",
-        "get-intrinsic": "^1.2.1",
+        "es-abstract": "^1.23.2",
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
         "globalthis": "^1.0.3",
-        "has-property-descriptors": "^1.0.0",
-        "set-function-name": "^2.0.1"
+        "has-property-descriptors": "^1.0.2",
+        "set-function-name": "^2.0.2"
       },
       "engines": {
         "node": ">= 0.4"
@@ -6309,42 +6391,75 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/es-define-property": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+      "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+      "dependencies": {
+        "get-intrinsic": "^1.2.4"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/es-iterator-helpers": {
-      "version": "1.0.15",
-      "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz",
-      "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==",
+      "version": "1.0.18",
+      "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz",
+      "integrity": "sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA==",
       "dev": true,
       "dependencies": {
-        "asynciterator.prototype": "^1.0.0",
-        "call-bind": "^1.0.2",
+        "call-bind": "^1.0.7",
         "define-properties": "^1.2.1",
-        "es-abstract": "^1.22.1",
-        "es-set-tostringtag": "^2.0.1",
-        "function-bind": "^1.1.1",
-        "get-intrinsic": "^1.2.1",
+        "es-abstract": "^1.23.0",
+        "es-errors": "^1.3.0",
+        "es-set-tostringtag": "^2.0.3",
+        "function-bind": "^1.1.2",
+        "get-intrinsic": "^1.2.4",
         "globalthis": "^1.0.3",
-        "has-property-descriptors": "^1.0.0",
-        "has-proto": "^1.0.1",
+        "has-property-descriptors": "^1.0.2",
+        "has-proto": "^1.0.3",
         "has-symbols": "^1.0.3",
-        "internal-slot": "^1.0.5",
+        "internal-slot": "^1.0.7",
         "iterator.prototype": "^1.1.2",
-        "safe-array-concat": "^1.0.1"
+        "safe-array-concat": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
       }
     },
     "node_modules/es-module-lexer": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
-      "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==",
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz",
+      "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==",
       "dev": true
     },
+    "node_modules/es-object-atoms": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
+      "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/es-set-tostringtag": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
-      "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==",
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
+      "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
       "dependencies": {
-        "get-intrinsic": "^1.2.2",
-        "has-tostringtag": "^1.0.0",
-        "hasown": "^2.0.0"
+        "get-intrinsic": "^1.2.4",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.1"
       },
       "engines": {
         "node": ">= 0.4"
@@ -6376,9 +6491,9 @@
       }
     },
     "node_modules/escalade": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
-      "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
+      "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
       "engines": {
         "node": ">=6"
       }
@@ -6401,16 +6516,16 @@
       }
     },
     "node_modules/eslint": {
-      "version": "8.56.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
-      "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
+      "version": "8.57.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
+      "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.6.1",
         "@eslint/eslintrc": "^2.1.4",
-        "@eslint/js": "8.56.0",
-        "@humanwhocodes/config-array": "^0.11.13",
+        "@eslint/js": "8.57.0",
+        "@humanwhocodes/config-array": "^0.11.14",
         "@humanwhocodes/module-importer": "^1.0.1",
         "@nodelib/fs.walk": "^1.2.8",
         "@ungap/structured-clone": "^1.2.0",
@@ -6857,9 +6972,9 @@
       }
     },
     "node_modules/eslint-module-utils": {
-      "version": "2.8.0",
-      "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz",
-      "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==",
+      "version": "2.8.1",
+      "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz",
+      "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==",
       "dev": true,
       "dependencies": {
         "debug": "^3.2.7"
@@ -7061,9 +7176,9 @@
       }
     },
     "node_modules/eslint-plugin-jest": {
-      "version": "27.6.3",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.3.tgz",
-      "integrity": "sha512-+YsJFVH6R+tOiO3gCJon5oqn4KWc+mDq2leudk8mrp8RFubLOo9CVyi3cib4L7XMpxExmkmBZQTPDYVBzgpgOA==",
+      "version": "27.9.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz",
+      "integrity": "sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==",
       "dev": true,
       "dependencies": {
         "@typescript-eslint/utils": "^5.10.0"
@@ -7072,7 +7187,7 @@
         "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
       },
       "peerDependencies": {
-        "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0",
+        "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0 || ^7.0.0",
         "eslint": "^7.0.0 || ^8.0.0",
         "jest": "*"
       },
@@ -7365,27 +7480,29 @@
       }
     },
     "node_modules/eslint-plugin-react": {
-      "version": "7.33.2",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz",
-      "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==",
+      "version": "7.34.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz",
+      "integrity": "sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==",
       "dev": true,
       "dependencies": {
-        "array-includes": "^3.1.6",
-        "array.prototype.flatmap": "^1.3.1",
-        "array.prototype.tosorted": "^1.1.1",
+        "array-includes": "^3.1.7",
+        "array.prototype.findlast": "^1.2.4",
+        "array.prototype.flatmap": "^1.3.2",
+        "array.prototype.toreversed": "^1.1.2",
+        "array.prototype.tosorted": "^1.1.3",
         "doctrine": "^2.1.0",
-        "es-iterator-helpers": "^1.0.12",
+        "es-iterator-helpers": "^1.0.17",
         "estraverse": "^5.3.0",
         "jsx-ast-utils": "^2.4.1 || ^3.0.0",
         "minimatch": "^3.1.2",
-        "object.entries": "^1.1.6",
-        "object.fromentries": "^2.0.6",
-        "object.hasown": "^1.1.2",
-        "object.values": "^1.1.6",
+        "object.entries": "^1.1.7",
+        "object.fromentries": "^2.0.7",
+        "object.hasown": "^1.1.3",
+        "object.values": "^1.1.7",
         "prop-types": "^15.8.1",
-        "resolve": "^2.0.0-next.4",
+        "resolve": "^2.0.0-next.5",
         "semver": "^6.3.1",
-        "string.prototype.matchall": "^4.0.8"
+        "string.prototype.matchall": "^4.0.10"
       },
       "engines": {
         "node": ">=4"
@@ -7467,9 +7584,9 @@
       }
     },
     "node_modules/eslint-plugin-security": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-2.1.0.tgz",
-      "integrity": "sha512-ywxclP954bf8d3gr6KOQ/AFc+PRvWuhOxtPOEtiHmVYiZr/mcgQtmSJq6+hTEXC5ylTjHnPPG+PEnzlDiWMXbQ==",
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-2.1.1.tgz",
+      "integrity": "sha512-7cspIGj7WTfR3EhaILzAPcfCo5R9FbeWvbgsPYWivSurTBKW88VQxtP3c4aWMG9Hz/GfJlJVdXEJ3c8LqS+u2w==",
       "dev": true,
       "dependencies": {
         "safe-regex": "^2.1.1"
@@ -7498,9 +7615,9 @@
       }
     },
     "node_modules/eslint-plugin-unused-imports": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.0.0.tgz",
-      "integrity": "sha512-sduiswLJfZHeeBJ+MQaG+xYzSWdRXoSw61DpU13mzWumCkR0ufD0HmO4kdNokjrkluMHpj/7PJeN35pgbhW3kw==",
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.1.0.tgz",
+      "integrity": "sha512-9l1YFCzXKkw1qtAru1RWUtG2EVDZY0a0eChKXcL+EZ5jitG7qxdctu4RnvhOJHv4xfmUf7h+JJPINlVpGhZMrw==",
       "dev": true,
       "dependencies": {
         "eslint-rule-composer": "^0.3.0"
@@ -7509,8 +7626,8 @@
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
       },
       "peerDependencies": {
-        "@typescript-eslint/eslint-plugin": "^6.0.0",
-        "eslint": "^8.0.0"
+        "@typescript-eslint/eslint-plugin": "6 - 7",
+        "eslint": "8"
       },
       "peerDependenciesMeta": {
         "@typescript-eslint/eslint-plugin": {
@@ -7579,6 +7696,22 @@
         "url": "https://opencollective.com/eslint"
       }
     },
+    "node_modules/eslint/node_modules/ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
     "node_modules/eslint/node_modules/ansi-regex": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -7656,6 +7789,12 @@
         "node": ">=10.13.0"
       }
     },
+    "node_modules/eslint/node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
     "node_modules/eslint/node_modules/minimatch": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -7846,16 +7985,16 @@
       }
     },
     "node_modules/express": {
-      "version": "4.18.2",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
-      "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
+      "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
       "dependencies": {
         "accepts": "~1.3.8",
         "array-flatten": "1.1.1",
-        "body-parser": "1.20.1",
+        "body-parser": "1.20.2",
         "content-disposition": "0.5.4",
         "content-type": "~1.0.4",
-        "cookie": "0.5.0",
+        "cookie": "0.6.0",
         "cookie-signature": "1.0.6",
         "debug": "2.6.9",
         "depd": "2.0.0",
@@ -7894,29 +8033,6 @@
         "basic-auth": "^2.0.1"
       }
     },
-    "node_modules/express/node_modules/body-parser": {
-      "version": "1.20.1",
-      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
-      "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
-      "dependencies": {
-        "bytes": "3.1.2",
-        "content-type": "~1.0.4",
-        "debug": "2.6.9",
-        "depd": "2.0.0",
-        "destroy": "1.2.0",
-        "http-errors": "2.0.0",
-        "iconv-lite": "0.4.24",
-        "on-finished": "2.4.1",
-        "qs": "6.11.0",
-        "raw-body": "2.5.1",
-        "type-is": "~1.6.18",
-        "unpipe": "1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.8",
-        "npm": "1.2.8000 || >= 1.4.16"
-      }
-    },
     "node_modules/express/node_modules/debug": {
       "version": "2.6.9",
       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -7935,20 +8051,6 @@
       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
       "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
     },
-    "node_modules/express/node_modules/raw-body": {
-      "version": "2.5.1",
-      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
-      "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
-      "dependencies": {
-        "bytes": "3.1.2",
-        "http-errors": "2.0.0",
-        "iconv-lite": "0.4.24",
-        "unpipe": "1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
     "node_modules/express/node_modules/safe-buffer": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -7983,9 +8085,9 @@
       }
     },
     "node_modules/fast-copy": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.1.tgz",
-      "integrity": "sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA=="
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz",
+      "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ=="
     },
     "node_modules/fast-deep-equal": {
       "version": "3.1.3",
@@ -8036,9 +8138,9 @@
       "dev": true
     },
     "node_modules/fast-redact": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.3.0.tgz",
-      "integrity": "sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==",
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz",
+      "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==",
       "engines": {
         "node": ">=6"
       }
@@ -8049,9 +8151,9 @@
       "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
     },
     "node_modules/fastq": {
-      "version": "1.17.0",
-      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz",
-      "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==",
+      "version": "1.17.1",
+      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+      "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
       "dependencies": {
         "reusify": "^1.0.4"
       }
@@ -8179,47 +8281,28 @@
       }
     },
     "node_modules/flat-cache": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.0.tgz",
-      "integrity": "sha512-EryKbCE/wxpxKniQlyas6PY1I9vwtF3uCBweX+N8KYTCn3Y12RTGtQAJ/bd5pl7kxUAc8v/R3Ake/N17OZiFqA==",
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+      "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
       "dev": true,
       "dependencies": {
         "flatted": "^3.2.9",
-        "keyv": "^4.5.4",
-        "rimraf": "^5.0.5"
+        "keyv": "^4.5.4"
       },
       "engines": {
         "node": ">=16"
       }
     },
-    "node_modules/flat-cache/node_modules/rimraf": {
-      "version": "5.0.5",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz",
-      "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==",
-      "dev": true,
-      "dependencies": {
-        "glob": "^10.3.7"
-      },
-      "bin": {
-        "rimraf": "dist/esm/bin.mjs"
-      },
-      "engines": {
-        "node": ">=14"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
     "node_modules/flatted": {
-      "version": "3.2.9",
-      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz",
-      "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
+      "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
       "dev": true
     },
     "node_modules/follow-redirects": {
-      "version": "1.15.5",
-      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
-      "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
+      "version": "1.15.6",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+      "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
       "funding": [
         {
           "type": "individual",
@@ -8437,12 +8520,12 @@
       }
     },
     "node_modules/gensequence": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-6.0.0.tgz",
-      "integrity": "sha512-8WwuywE9pokJRAcg2QFR/plk3cVPebSUqRPzpGQh3WQ0wIiHAw+HyOQj5IuHyUTQBHpBKFoB2JUMu9zT3vJ16Q==",
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-7.0.0.tgz",
+      "integrity": "sha512-47Frx13aZh01afHJTB3zTtKIlFI6vWY+MYCN9Qpew6i52rfKjnhCF/l1YlC8UmEMvvntZZ6z4PiCcmyuedR2aQ==",
       "dev": true,
       "engines": {
-        "node": ">=16"
+        "node": ">=18"
       }
     },
     "node_modules/gensync": {
@@ -8475,15 +8558,19 @@
       }
     },
     "node_modules/get-intrinsic": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
-      "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+      "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
       "dependencies": {
+        "es-errors": "^1.3.0",
         "function-bind": "^1.1.2",
         "has-proto": "^1.0.1",
         "has-symbols": "^1.0.3",
         "hasown": "^2.0.0"
       },
+      "engines": {
+        "node": ">= 0.4"
+      },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
@@ -8522,12 +8609,13 @@
       }
     },
     "node_modules/get-symbol-description": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
-      "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz",
+      "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==",
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "get-intrinsic": "^1.1.1"
+        "call-bind": "^1.0.5",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.4"
       },
       "engines": {
         "node": ">= 0.4"
@@ -8537,9 +8625,9 @@
       }
     },
     "node_modules/get-tsconfig": {
-      "version": "4.7.2",
-      "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz",
-      "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==",
+      "version": "4.7.3",
+      "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz",
+      "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==",
       "dev": true,
       "dependencies": {
         "resolve-pkg-maps": "^1.0.0"
@@ -8823,20 +8911,20 @@
       }
     },
     "node_modules/has-property-descriptors": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
-      "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==",
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+      "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
       "dependencies": {
-        "get-intrinsic": "^1.2.2"
+        "es-define-property": "^1.0.0"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
     "node_modules/has-proto": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
-      "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+      "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
       "engines": {
         "node": ">= 0.4"
       },
@@ -8856,11 +8944,11 @@
       }
     },
     "node_modules/has-tostringtag": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
-      "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
       "dependencies": {
-        "has-symbols": "^1.0.2"
+        "has-symbols": "^1.0.3"
       },
       "engines": {
         "node": ">= 0.4"
@@ -8870,9 +8958,9 @@
       }
     },
     "node_modules/hasown": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
-      "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
       "dependencies": {
         "function-bind": "^1.1.2"
       },
@@ -8954,28 +9042,27 @@
       }
     },
     "node_modules/http-proxy-agent": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
-      "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+      "version": "7.0.2",
+      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+      "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
       "dependencies": {
-        "@tootallnate/once": "2",
-        "agent-base": "6",
-        "debug": "4"
+        "agent-base": "^7.1.0",
+        "debug": "^4.3.4"
       },
       "engines": {
-        "node": ">= 6"
+        "node": ">= 14"
       }
     },
     "node_modules/https-proxy-agent": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
-      "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+      "version": "7.0.4",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz",
+      "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==",
       "dependencies": {
-        "agent-base": "6",
+        "agent-base": "^7.0.2",
         "debug": "4"
       },
       "engines": {
-        "node": ">= 6"
+        "node": ">= 14"
       }
     },
     "node_modules/human-signals": {
@@ -8988,12 +9075,12 @@
       }
     },
     "node_modules/husky": {
-      "version": "9.0.6",
-      "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.6.tgz",
-      "integrity": "sha512-EEuw/rfTiMjOfuL7pGO/i9otg1u36TXxqjIA6D9qxVjd/UXoDOsLor/BSFf5hTK50shwzCU3aVVwdXDp/lp7RA==",
+      "version": "9.0.11",
+      "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz",
+      "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==",
       "dev": true,
       "bin": {
-        "husky": "bin.js"
+        "husky": "bin.mjs"
       },
       "engines": {
         "node": ">=18"
@@ -9033,9 +9120,9 @@
       ]
     },
     "node_modules/ignore": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
-      "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
+      "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
       "dev": true,
       "engines": {
         "node": ">= 4"
@@ -9211,11 +9298,11 @@
       }
     },
     "node_modules/internal-slot": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz",
-      "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==",
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
+      "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==",
       "dependencies": {
-        "get-intrinsic": "^1.2.2",
+        "es-errors": "^1.3.0",
         "hasown": "^2.0.0",
         "side-channel": "^1.0.4"
       },
@@ -9241,13 +9328,15 @@
       }
     },
     "node_modules/is-array-buffer": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
-      "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
+      "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==",
       "dependencies": {
         "call-bind": "^1.0.2",
-        "get-intrinsic": "^1.2.0",
-        "is-typed-array": "^1.1.10"
+        "get-intrinsic": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
@@ -9335,6 +9424,20 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/is-data-view": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz",
+      "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==",
+      "dependencies": {
+        "is-typed-array": "^1.1.13"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/is-date-object": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
@@ -9440,18 +9543,21 @@
       }
     },
     "node_modules/is-map": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
-      "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==",
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+      "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
       "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
     "node_modules/is-negative-zero": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
-      "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
+      "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
       "engines": {
         "node": ">= 0.4"
       },
@@ -9527,20 +9633,26 @@
       }
     },
     "node_modules/is-set": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz",
-      "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==",
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+      "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
       "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
     "node_modules/is-shared-array-buffer": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
-      "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
+      "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==",
       "dependencies": {
-        "call-bind": "^1.0.2"
+        "call-bind": "^1.0.7"
+      },
+      "engines": {
+        "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
@@ -9599,11 +9711,11 @@
       }
     },
     "node_modules/is-typed-array": {
-      "version": "1.1.12",
-      "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
-      "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==",
+      "version": "1.1.13",
+      "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz",
+      "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==",
       "dependencies": {
-        "which-typed-array": "^1.1.11"
+        "which-typed-array": "^1.1.14"
       },
       "engines": {
         "node": ">= 0.4"
@@ -9631,10 +9743,13 @@
       }
     },
     "node_modules/is-weakmap": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz",
-      "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==",
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+      "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
       "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
@@ -9651,13 +9766,16 @@
       }
     },
     "node_modules/is-weakset": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz",
-      "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==",
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz",
+      "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==",
       "dev": true,
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "get-intrinsic": "^1.1.1"
+        "call-bind": "^1.0.7",
+        "get-intrinsic": "^1.2.4"
+      },
+      "engines": {
+        "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
@@ -9694,14 +9812,14 @@
       }
     },
     "node_modules/istanbul-lib-instrument": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz",
-      "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==",
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz",
+      "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==",
       "dev": true,
       "dependencies": {
-        "@babel/core": "^7.12.3",
-        "@babel/parser": "^7.14.7",
-        "@istanbuljs/schema": "^0.1.2",
+        "@babel/core": "^7.23.9",
+        "@babel/parser": "^7.23.9",
+        "@istanbuljs/schema": "^0.1.3",
         "istanbul-lib-coverage": "^3.2.0",
         "semver": "^7.5.4"
       },
@@ -9747,9 +9865,9 @@
       }
     },
     "node_modules/istanbul-reports": {
-      "version": "3.1.6",
-      "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz",
-      "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==",
+      "version": "3.1.7",
+      "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
+      "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
       "dev": true,
       "dependencies": {
         "html-escaper": "^2.0.0",
@@ -10532,9 +10650,9 @@
       "dev": true
     },
     "node_modules/json-schema-traverse": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
-      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
       "dev": true
     },
     "node_modules/json-stable-stringify-without-jsonify": {
@@ -10745,9 +10863,9 @@
       }
     },
     "node_modules/libphonenumber-js": {
-      "version": "1.10.54",
-      "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.54.tgz",
-      "integrity": "sha512-P+38dUgJsmh0gzoRDoM4F5jLbyfztkU6PY6eSK6S5HwTi/LPvnwXqVCQZlAy1FxZ5c48q25QhxGQ0pq+WQcSlQ=="
+      "version": "1.10.60",
+      "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.60.tgz",
+      "integrity": "sha512-Ctgq2lXUpEJo5j1762NOzl2xo7z7pqmVWYai0p07LvAkQ32tbPv3wb+tcUeHEiXhKU5buM4H9MXsXo6OlM6C2g=="
     },
     "node_modules/lilconfig": {
       "version": "3.0.0",
@@ -10765,9 +10883,9 @@
       "dev": true
     },
     "node_modules/lint-staged": {
-      "version": "15.2.0",
-      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.0.tgz",
-      "integrity": "sha512-TFZzUEV00f+2YLaVPWBWGAMq7So6yQx+GG8YRMDeOEIf95Zn5RyiLMsEiX4KTNl9vq/w+NqRJkLA1kPIo15ufQ==",
+      "version": "15.2.2",
+      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz",
+      "integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==",
       "dev": true,
       "dependencies": {
         "chalk": "5.3.0",
@@ -10775,7 +10893,7 @@
         "debug": "4.3.4",
         "execa": "8.0.1",
         "lilconfig": "3.0.0",
-        "listr2": "8.0.0",
+        "listr2": "8.0.1",
         "micromatch": "4.0.5",
         "pidtree": "0.6.0",
         "string-argv": "0.3.2",
@@ -10881,9 +10999,9 @@
       }
     },
     "node_modules/lint-staged/node_modules/npm-run-path": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz",
-      "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==",
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
+      "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
       "dev": true,
       "dependencies": {
         "path-key": "^4.0.0"
@@ -10946,10 +11064,19 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/lint-staged/node_modules/yaml": {
+      "version": "2.3.4",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
+      "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 14"
+      }
+    },
     "node_modules/listr2": {
-      "version": "8.0.0",
-      "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.0.tgz",
-      "integrity": "sha512-u8cusxAcyqAiQ2RhYvV7kRKNLgUvtObIbhOX2NCXqvp1UU32xIg5CT22ykS2TPKJXZWJwtK3IKLiqAGlGNE+Zg==",
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz",
+      "integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==",
       "dev": true,
       "dependencies": {
         "cli-truncate": "^4.0.0",
@@ -11040,16 +11167,16 @@
       }
     },
     "node_modules/locter": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/locter/-/locter-2.0.2.tgz",
-      "integrity": "sha512-bb/7J2yiGa9UzoFRoTXXQSPBEGirz7wddD996LcrJ6x0MV6TxrwkL6lVNIsFoYboj7gQLjnZLaB6f0F1pBdRRQ==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/locter/-/locter-2.1.0.tgz",
+      "integrity": "sha512-QUPPtb6CQ3hOacDZq2kc6KMzYn9z6r9B2RtFJTBD9nqxmyQJVYnTNZNqY6Z5NcJfwsGEgJLddnfFpofg7EJMDg==",
       "dependencies": {
-        "destr": "^2.0.2",
+        "destr": "^2.0.3",
         "ebec": "^2.3.0",
         "fast-glob": "^3.3.2",
         "flat": "^5.0.2",
         "jiti": "^1.21.0",
-        "yaml": "^2.3.4"
+        "yaml": "^2.4.1"
       }
     },
     "node_modules/lodash": {
@@ -11200,13 +11327,10 @@
       }
     },
     "node_modules/log-update/node_modules/ansi-escapes": {
-      "version": "6.2.0",
-      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz",
-      "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==",
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz",
+      "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==",
       "dev": true,
-      "dependencies": {
-        "type-fest": "^3.0.0"
-      },
       "engines": {
         "node": ">=14.16"
       },
@@ -11311,18 +11435,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/log-update/node_modules/type-fest": {
-      "version": "3.13.1",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
-      "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
-      "dev": true,
-      "engines": {
-        "node": ">=14.16"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/log-update/node_modules/wrap-ansi": {
       "version": "9.0.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
@@ -11400,7 +11512,7 @@
       "version": "1.3.6",
       "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
       "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
-      "devOptional": true
+      "dev": true
     },
     "node_modules/makeerror": {
       "version": "1.0.12",
@@ -11736,9 +11848,9 @@
       }
     },
     "node_modules/nock": {
-      "version": "13.5.1",
-      "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.1.tgz",
-      "integrity": "sha512-+s7b73fzj5KnxbKH4Oaqz07tQ8degcMilU4rrmnKvI//b0JMBU4wEXFQ8zqr+3+L4eWSfU3H/UoIVGUV0tue1Q==",
+      "version": "13.5.4",
+      "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.4.tgz",
+      "integrity": "sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==",
       "dev": true,
       "dependencies": {
         "debug": "^4.1.0",
@@ -11872,28 +11984,29 @@
       }
     },
     "node_modules/object.entries": {
-      "version": "1.1.7",
-      "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz",
-      "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==",
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz",
+      "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==",
       "dev": true,
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.2.0",
-        "es-abstract": "^1.22.1"
+        "call-bind": "^1.0.7",
+        "define-properties": "^1.2.1",
+        "es-object-atoms": "^1.0.0"
       },
       "engines": {
         "node": ">= 0.4"
       }
     },
     "node_modules/object.fromentries": {
-      "version": "2.0.7",
-      "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz",
-      "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==",
+      "version": "2.0.8",
+      "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz",
+      "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
       "dev": true,
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.2.0",
-        "es-abstract": "^1.22.1"
+        "call-bind": "^1.0.7",
+        "define-properties": "^1.2.1",
+        "es-abstract": "^1.23.2",
+        "es-object-atoms": "^1.0.0"
       },
       "engines": {
         "node": ">= 0.4"
@@ -11903,39 +12016,45 @@
       }
     },
     "node_modules/object.groupby": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz",
-      "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==",
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz",
+      "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==",
       "dev": true,
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.2.0",
-        "es-abstract": "^1.22.1",
-        "get-intrinsic": "^1.2.1"
+        "call-bind": "^1.0.7",
+        "define-properties": "^1.2.1",
+        "es-abstract": "^1.23.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
       }
     },
     "node_modules/object.hasown": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz",
-      "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==",
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz",
+      "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==",
       "dev": true,
       "dependencies": {
-        "define-properties": "^1.2.0",
-        "es-abstract": "^1.22.1"
+        "define-properties": "^1.2.1",
+        "es-abstract": "^1.23.2",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
     "node_modules/object.values": {
-      "version": "1.1.7",
-      "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz",
-      "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==",
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz",
+      "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==",
       "dev": true,
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.2.0",
-        "es-abstract": "^1.22.1"
+        "call-bind": "^1.0.7",
+        "define-properties": "^1.2.1",
+        "es-object-atoms": "^1.0.0"
       },
       "engines": {
         "node": ">= 0.4"
@@ -12251,11 +12370,11 @@
       "dev": true
     },
     "node_modules/path-scurry": {
-      "version": "1.10.1",
-      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
-      "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
+      "version": "1.10.2",
+      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
+      "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
       "dependencies": {
-        "lru-cache": "^9.1.1 || ^10.0.0",
+        "lru-cache": "^10.2.0",
         "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
       },
       "engines": {
@@ -12323,14 +12442,14 @@
       }
     },
     "node_modules/pino": {
-      "version": "8.17.2",
-      "resolved": "https://registry.npmjs.org/pino/-/pino-8.17.2.tgz",
-      "integrity": "sha512-LA6qKgeDMLr2ux2y/YiUt47EfgQ+S9LznBWOJdN3q1dx2sv0ziDLUBeVpyVv17TEcGCBuWf0zNtg3M5m1NhhWQ==",
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/pino/-/pino-8.20.0.tgz",
+      "integrity": "sha512-uhIfMj5TVp+WynVASaVEJFTncTUe4dHBq6CWplu/vBgvGHhvBvQfxz+vcOrnnBQdORH3izaGEurLfNlq3YxdFQ==",
       "dependencies": {
         "atomic-sleep": "^1.0.0",
         "fast-redact": "^3.1.1",
         "on-exit-leak-free": "^2.1.0",
-        "pino-abstract-transport": "v1.1.0",
+        "pino-abstract-transport": "^1.1.0",
         "pino-std-serializers": "^6.0.0",
         "process-warning": "^3.0.0",
         "quick-format-unescaped": "^4.0.3",
@@ -12604,6 +12723,14 @@
         "node": ">=4"
       }
     },
+    "node_modules/possible-typed-array-names": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
+      "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/prelude-ls": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -12614,9 +12741,9 @@
       }
     },
     "node_modules/prettier": {
-      "version": "3.2.4",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz",
-      "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==",
+      "version": "3.2.5",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
+      "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
       "dev": true,
       "bin": {
         "prettier": "bin/prettier.cjs"
@@ -12753,14 +12880,15 @@
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
       "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+      "dev": true,
       "engines": {
         "node": ">=6"
       }
     },
     "node_modules/pure-rand": {
-      "version": "6.0.4",
-      "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz",
-      "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==",
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+      "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
       "dev": true,
       "funding": [
         {
@@ -13075,20 +13203,21 @@
       }
     },
     "node_modules/reflect-metadata": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz",
-      "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw=="
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
+      "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="
     },
     "node_modules/reflect.getprototypeof": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz",
-      "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==",
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
+      "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==",
       "dev": true,
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.2.0",
-        "es-abstract": "^1.22.1",
-        "get-intrinsic": "^1.2.1",
+        "call-bind": "^1.0.7",
+        "define-properties": "^1.2.1",
+        "es-abstract": "^1.23.1",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.4",
         "globalthis": "^1.0.3",
         "which-builtin-type": "^1.1.3"
       },
@@ -13115,13 +13244,14 @@
       }
     },
     "node_modules/regexp.prototype.flags": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
-      "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==",
+      "version": "1.5.2",
+      "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
+      "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==",
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.2.0",
-        "set-function-name": "^2.0.0"
+        "call-bind": "^1.0.6",
+        "define-properties": "^1.2.1",
+        "es-errors": "^1.3.0",
+        "set-function-name": "^2.0.1"
       },
       "engines": {
         "node": ">= 0.4"
@@ -13363,12 +13493,12 @@
       }
     },
     "node_modules/safe-array-concat": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz",
-      "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==",
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz",
+      "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==",
       "dependencies": {
-        "call-bind": "^1.0.5",
-        "get-intrinsic": "^1.2.2",
+        "call-bind": "^1.0.7",
+        "get-intrinsic": "^1.2.4",
         "has-symbols": "^1.0.3",
         "isarray": "^2.0.5"
       },
@@ -13399,12 +13529,12 @@
       }
     },
     "node_modules/safe-regex-test": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz",
-      "integrity": "sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==",
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz",
+      "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==",
       "dependencies": {
-        "call-bind": "^1.0.5",
-        "get-intrinsic": "^1.2.2",
+        "call-bind": "^1.0.6",
+        "es-errors": "^1.3.0",
         "is-regex": "^1.1.4"
       },
       "engines": {
@@ -13445,15 +13575,37 @@
         "url": "https://opencollective.com/webpack"
       }
     },
+    "node_modules/schema-utils/node_modules/ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/schema-utils/node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
     "node_modules/secure-json-parse": {
       "version": "2.7.0",
       "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
       "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="
     },
     "node_modules/semver": {
-      "version": "7.5.4",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
-      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+      "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
       "dependencies": {
         "lru-cache": "^6.0.0"
       },
@@ -13545,28 +13697,30 @@
       }
     },
     "node_modules/set-function-length": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz",
-      "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==",
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+      "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
       "dependencies": {
-        "define-data-property": "^1.1.1",
+        "define-data-property": "^1.1.4",
+        "es-errors": "^1.3.0",
         "function-bind": "^1.1.2",
-        "get-intrinsic": "^1.2.2",
+        "get-intrinsic": "^1.2.4",
         "gopd": "^1.0.1",
-        "has-property-descriptors": "^1.0.1"
+        "has-property-descriptors": "^1.0.2"
       },
       "engines": {
         "node": ">= 0.4"
       }
     },
     "node_modules/set-function-name": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz",
-      "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==",
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
+      "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
       "dependencies": {
-        "define-data-property": "^1.0.1",
+        "define-data-property": "^1.1.4",
+        "es-errors": "^1.3.0",
         "functions-have-names": "^1.2.3",
-        "has-property-descriptors": "^1.0.0"
+        "has-property-descriptors": "^1.0.2"
       },
       "engines": {
         "node": ">= 0.4"
@@ -13668,13 +13822,17 @@
       }
     },
     "node_modules/side-channel": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
-      "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+      "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
       "dependencies": {
-        "call-bind": "^1.0.0",
-        "get-intrinsic": "^1.0.2",
-        "object-inspect": "^1.9.0"
+        "call-bind": "^1.0.7",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.4",
+        "object-inspect": "^1.13.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
@@ -13730,14 +13888,14 @@
       }
     },
     "node_modules/smob": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz",
-      "integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ=="
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz",
+      "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig=="
     },
     "node_modules/sonic-boom": {
-      "version": "3.8.0",
-      "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.0.tgz",
-      "integrity": "sha512-ybz6OYOUjoQQCQ/i4LU8kaToD8ACtYP+Cj5qd2AO36bwbdewxWJ3ArmJ2cr6AvxlL2o0PqnCcPGUgkILbfkaCA==",
+      "version": "3.8.1",
+      "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz",
+      "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==",
       "dependencies": {
         "atomic-sleep": "^1.0.0"
       }
@@ -13749,9 +13907,9 @@
       "dev": true
     },
     "node_modules/sort-package-json": {
-      "version": "2.6.0",
-      "resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-2.6.0.tgz",
-      "integrity": "sha512-XSQ+lY9bAYA8ZsoChcEoPlgcSMaheziEp1beox1JVxy1SV4F2jSq9+h2rJ+3mC/Dhu9Ius1DLnInD5AWcsDXZw==",
+      "version": "2.10.0",
+      "resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-2.10.0.tgz",
+      "integrity": "sha512-MYecfvObMwJjjJskhxYfuOADkXp1ZMMnCFC8yhp+9HDsk7HhR336hd7eiBs96lTXfiqmUNI+WQCeCMRBhl251g==",
       "dev": true,
       "dependencies": {
         "detect-indent": "^7.0.1",
@@ -13760,6 +13918,7 @@
         "git-hooks-list": "^3.0.0",
         "globby": "^13.1.2",
         "is-plain-obj": "^4.1.0",
+        "semver": "^7.6.0",
         "sort-object-keys": "^1.1.3"
       },
       "bin": {
@@ -13848,9 +14007,9 @@
       }
     },
     "node_modules/spdx-exceptions": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.4.0.tgz",
-      "integrity": "sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==",
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
+      "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
       "dev": true
     },
     "node_modules/spdx-expression-parse": {
@@ -13864,9 +14023,9 @@
       }
     },
     "node_modules/spdx-license-ids": {
-      "version": "3.0.16",
-      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz",
-      "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==",
+      "version": "3.0.17",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz",
+      "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==",
       "dev": true
     },
     "node_modules/split2": {
@@ -13911,6 +14070,11 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/std-env": {
+      "version": "3.7.0",
+      "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",
+      "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg=="
+    },
     "node_modules/stoppable": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
@@ -14071,33 +14235,40 @@
       }
     },
     "node_modules/string.prototype.matchall": {
-      "version": "4.0.10",
-      "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz",
-      "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==",
+      "version": "4.0.11",
+      "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz",
+      "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==",
       "dev": true,
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.2.0",
-        "es-abstract": "^1.22.1",
-        "get-intrinsic": "^1.2.1",
+        "call-bind": "^1.0.7",
+        "define-properties": "^1.2.1",
+        "es-abstract": "^1.23.2",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.0.0",
+        "get-intrinsic": "^1.2.4",
+        "gopd": "^1.0.1",
         "has-symbols": "^1.0.3",
-        "internal-slot": "^1.0.5",
-        "regexp.prototype.flags": "^1.5.0",
-        "set-function-name": "^2.0.0",
-        "side-channel": "^1.0.4"
+        "internal-slot": "^1.0.7",
+        "regexp.prototype.flags": "^1.5.2",
+        "set-function-name": "^2.0.2",
+        "side-channel": "^1.0.6"
+      },
+      "engines": {
+        "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
     "node_modules/string.prototype.trim": {
-      "version": "1.2.8",
-      "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz",
-      "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==",
+      "version": "1.2.9",
+      "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz",
+      "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==",
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.2.0",
-        "es-abstract": "^1.22.1"
+        "call-bind": "^1.0.7",
+        "define-properties": "^1.2.1",
+        "es-abstract": "^1.23.0",
+        "es-object-atoms": "^1.0.0"
       },
       "engines": {
         "node": ">= 0.4"
@@ -14107,26 +14278,29 @@
       }
     },
     "node_modules/string.prototype.trimend": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz",
-      "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==",
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz",
+      "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==",
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.2.0",
-        "es-abstract": "^1.22.1"
+        "call-bind": "^1.0.7",
+        "define-properties": "^1.2.1",
+        "es-object-atoms": "^1.0.0"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
     "node_modules/string.prototype.trimstart": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz",
-      "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==",
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
+      "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.2.0",
-        "es-abstract": "^1.22.1"
+        "call-bind": "^1.0.7",
+        "define-properties": "^1.2.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
@@ -14277,9 +14451,9 @@
       }
     },
     "node_modules/swagger-ui-dist": {
-      "version": "5.11.0",
-      "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.0.tgz",
-      "integrity": "sha512-j0PIATqQSEFGOLmiJOJZj1X1Jt6bFIur3JpY7+ghliUnfZs0fpWDdHEkn9q7QUlBtKbkn6TepvSxTqnE8l3s0A=="
+      "version": "5.11.2",
+      "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.2.tgz",
+      "integrity": "sha512-jQG0cRgJNMZ7aCoiFofnoojeSaa/+KgWaDlfgs8QN+BXoGMpxeMVY5OEnjq4OlNvF3yjftO8c9GRAgcHlO+u7A=="
     },
     "node_modules/symbol-observable": {
       "version": "4.0.0",
@@ -14324,9 +14498,9 @@
       }
     },
     "node_modules/tedious": {
-      "version": "16.6.1",
-      "resolved": "https://registry.npmjs.org/tedious/-/tedious-16.6.1.tgz",
-      "integrity": "sha512-KKSDB1OPrPk0WbMPug9YqRbPl44zMjdL2hFyzLEidr2IkItzpV0ZbzW8VA47QIS2oyWhCU7ifIEQY12n23IRDA==",
+      "version": "16.7.1",
+      "resolved": "https://registry.npmjs.org/tedious/-/tedious-16.7.1.tgz",
+      "integrity": "sha512-NmedZS0NJiTv3CoYnf1FtjxIDUgVYzEmavrc8q2WHRb+lP4deI9BpQfmNnBZZaWusDbP5FVFZCcvzb3xOlNVlQ==",
       "dependencies": {
         "@azure/identity": "^3.4.1",
         "@azure/keyvault-keys": "^4.4.0",
@@ -14338,7 +14512,6 @@
         "jsbi": "^4.3.0",
         "native-duplexpair": "^1.0.0",
         "node-abort-controller": "^3.1.1",
-        "punycode": "^2.3.0",
         "sprintf-js": "^1.1.2"
       },
       "engines": {
@@ -14346,10 +14519,11 @@
       }
     },
     "node_modules/tedious/node_modules/bl": {
-      "version": "6.0.10",
-      "resolved": "https://registry.npmjs.org/bl/-/bl-6.0.10.tgz",
-      "integrity": "sha512-F14DFhDZfxtVm2FY0k9kG2lWAwzZkO9+jX3Ytuoy/V0E1/5LBuBzzQHXAjqpxXEDIpmTPZZf5GVIGPQcLxFpaA==",
+      "version": "6.0.12",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-6.0.12.tgz",
+      "integrity": "sha512-EnEYHilP93oaOa2MnmNEjAcovPS3JlQZOyzGXi3EyEpPhm9qWvdDp7BmAVEVusGzp8LlwQK56Av+OkDoRjzE0w==",
       "dependencies": {
+        "@types/readable-stream": "^4.0.0",
         "buffer": "^6.0.3",
         "inherits": "^2.0.4",
         "readable-stream": "^4.2.0"
@@ -14432,9 +14606,9 @@
       }
     },
     "node_modules/terser": {
-      "version": "5.27.0",
-      "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz",
-      "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==",
+      "version": "5.30.3",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz",
+      "integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==",
       "dev": true,
       "dependencies": {
         "@jridgewell/source-map": "^0.3.3",
@@ -14718,12 +14892,12 @@
       }
     },
     "node_modules/ts-api-utils": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz",
-      "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
+      "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
       "dev": true,
       "engines": {
-        "node": ">=16.13.0"
+        "node": ">=16"
       },
       "peerDependencies": {
         "typescript": ">=4.2.0"
@@ -14796,7 +14970,7 @@
       "version": "10.9.2",
       "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
       "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
-      "devOptional": true,
+      "dev": true,
       "dependencies": {
         "@cspotcode/source-map-support": "^0.8.0",
         "@tsconfig/node10": "^1.0.7",
@@ -14941,27 +15115,28 @@
       }
     },
     "node_modules/typed-array-buffer": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz",
-      "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==",
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz",
+      "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==",
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "get-intrinsic": "^1.2.1",
-        "is-typed-array": "^1.1.10"
+        "call-bind": "^1.0.7",
+        "es-errors": "^1.3.0",
+        "is-typed-array": "^1.1.13"
       },
       "engines": {
         "node": ">= 0.4"
       }
     },
     "node_modules/typed-array-byte-length": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz",
-      "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz",
+      "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==",
       "dependencies": {
-        "call-bind": "^1.0.2",
+        "call-bind": "^1.0.7",
         "for-each": "^0.3.3",
-        "has-proto": "^1.0.1",
-        "is-typed-array": "^1.1.10"
+        "gopd": "^1.0.1",
+        "has-proto": "^1.0.3",
+        "is-typed-array": "^1.1.13"
       },
       "engines": {
         "node": ">= 0.4"
@@ -14971,15 +15146,16 @@
       }
     },
     "node_modules/typed-array-byte-offset": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz",
-      "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==",
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz",
+      "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==",
       "dependencies": {
-        "available-typed-arrays": "^1.0.5",
-        "call-bind": "^1.0.2",
+        "available-typed-arrays": "^1.0.7",
+        "call-bind": "^1.0.7",
         "for-each": "^0.3.3",
-        "has-proto": "^1.0.1",
-        "is-typed-array": "^1.1.10"
+        "gopd": "^1.0.1",
+        "has-proto": "^1.0.3",
+        "is-typed-array": "^1.1.13"
       },
       "engines": {
         "node": ">= 0.4"
@@ -14989,13 +15165,19 @@
       }
     },
     "node_modules/typed-array-length": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
-      "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz",
+      "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==",
       "dependencies": {
-        "call-bind": "^1.0.2",
+        "call-bind": "^1.0.7",
         "for-each": "^0.3.3",
-        "is-typed-array": "^1.1.9"
+        "gopd": "^1.0.1",
+        "has-proto": "^1.0.3",
+        "is-typed-array": "^1.1.13",
+        "possible-typed-array-names": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
@@ -15121,17 +15303,18 @@
       }
     },
     "node_modules/typeorm-extension": {
-      "version": "3.4.0",
-      "resolved": "https://registry.npmjs.org/typeorm-extension/-/typeorm-extension-3.4.0.tgz",
-      "integrity": "sha512-eQsOZmea8tSF8GULPIbc0HPuYRUTLvGBnP8B/Y9Hyk5q0JFj09+PGKeG7YL1jm9hOsVqZLF0Lab7p0vKdaR+Cg==",
+      "version": "3.5.1",
+      "resolved": "https://registry.npmjs.org/typeorm-extension/-/typeorm-extension-3.5.1.tgz",
+      "integrity": "sha512-gykF1eBattSIt+F0+134c+j+AFODYCb/6uIjmFCNzAoc63UW5j7d25JnzDah10viWnDi99BY03UUIxRcQWcR+w==",
       "dependencies": {
-        "@faker-js/faker": "^8.3.1",
+        "@faker-js/faker": "^8.4.1",
         "consola": "^3.2.3",
-        "locter": "^2.0.2",
+        "envix": "^1.5.0",
+        "locter": "^2.1.0",
         "pascal-case": "^3.1.2",
         "rapiq": "^0.9.0",
-        "reflect-metadata": "^0.2.1",
-        "smob": "^1.4.1",
+        "reflect-metadata": "^0.2.2",
+        "smob": "^1.5.0",
         "yargs": "^17.7.2"
       },
       "bin": {
@@ -15191,9 +15374,9 @@
       }
     },
     "node_modules/typescript": {
-      "version": "5.3.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
-      "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
+      "version": "5.4.5",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
+      "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
@@ -15230,8 +15413,7 @@
     "node_modules/undici-types": {
       "version": "5.26.5",
       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
-      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
-      "devOptional": true
+      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
     },
     "node_modules/unique-string": {
       "version": "3.0.0",
@@ -15318,9 +15500,13 @@
       }
     },
     "node_modules/uuid": {
-      "version": "9.0.0",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
-      "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+      "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
       "bin": {
         "uuid": "dist/bin/uuid"
       }
@@ -15329,7 +15515,7 @@
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
       "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
-      "devOptional": true
+      "dev": true
     },
     "node_modules/v8-to-istanbul": {
       "version": "9.2.0",
@@ -15393,9 +15579,9 @@
       }
     },
     "node_modules/watchpack": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
-      "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz",
+      "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==",
       "dev": true,
       "dependencies": {
         "glob-to-regexp": "^0.4.1",
@@ -15420,19 +15606,19 @@
       "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
     },
     "node_modules/webpack": {
-      "version": "5.89.0",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz",
-      "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==",
+      "version": "5.90.1",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz",
+      "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==",
       "dev": true,
       "dependencies": {
         "@types/eslint-scope": "^3.7.3",
-        "@types/estree": "^1.0.0",
+        "@types/estree": "^1.0.5",
         "@webassemblyjs/ast": "^1.11.5",
         "@webassemblyjs/wasm-edit": "^1.11.5",
         "@webassemblyjs/wasm-parser": "^1.11.5",
         "acorn": "^8.7.1",
         "acorn-import-assertions": "^1.9.0",
-        "browserslist": "^4.14.5",
+        "browserslist": "^4.21.10",
         "chrome-trace-event": "^1.0.2",
         "enhanced-resolve": "^5.15.0",
         "es-module-lexer": "^1.2.1",
@@ -15446,7 +15632,7 @@
         "neo-async": "^2.6.2",
         "schema-utils": "^3.2.0",
         "tapable": "^2.1.1",
-        "terser-webpack-plugin": "^5.3.7",
+        "terser-webpack-plugin": "^5.3.10",
         "watchpack": "^2.4.0",
         "webpack-sources": "^3.2.3"
       },
@@ -15577,30 +15763,33 @@
       "dev": true
     },
     "node_modules/which-collection": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz",
-      "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==",
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+      "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
       "dev": true,
       "dependencies": {
-        "is-map": "^2.0.1",
-        "is-set": "^2.0.1",
-        "is-weakmap": "^2.0.1",
-        "is-weakset": "^2.0.1"
+        "is-map": "^2.0.3",
+        "is-set": "^2.0.3",
+        "is-weakmap": "^2.0.2",
+        "is-weakset": "^2.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
     "node_modules/which-typed-array": {
-      "version": "1.1.13",
-      "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz",
-      "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==",
+      "version": "1.1.15",
+      "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz",
+      "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==",
       "dependencies": {
-        "available-typed-arrays": "^1.0.5",
-        "call-bind": "^1.0.4",
+        "available-typed-arrays": "^1.0.7",
+        "call-bind": "^1.0.7",
         "for-each": "^0.3.3",
         "gopd": "^1.0.1",
-        "has-tostringtag": "^1.0.0"
+        "has-tostringtag": "^1.0.2"
       },
       "engines": {
         "node": ">= 0.4"
@@ -15743,9 +15932,12 @@
       "dev": true
     },
     "node_modules/yaml": {
-      "version": "2.3.4",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
-      "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz",
+      "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==",
+      "bin": {
+        "yaml": "bin.mjs"
+      },
       "engines": {
         "node": ">= 14"
       }
@@ -15779,7 +15971,7 @@
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
       "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
-      "devOptional": true,
+      "dev": true,
       "engines": {
         "node": ">=6"
       }
diff --git a/package.json b/package.json
index 731bb763..44d7fe8a 100644
--- a/package.json
+++ b/package.json
@@ -28,20 +28,20 @@
     ]
   },
   "dependencies": {
-    "@nestjs/axios": "^3.0.1",
-    "@nestjs/common": "^10.3.1",
-    "@nestjs/config": "^3.1.1",
-    "@nestjs/core": "^10.3.1",
+    "@nestjs/axios": "^3.0.2",
+    "@nestjs/common": "^10.3.7",
+    "@nestjs/config": "^3.2.2",
+    "@nestjs/core": "^10.3.7",
     "@nestjs/passport": "^10.0.3",
-    "@nestjs/platform-express": "^10.3.1",
-    "@nestjs/swagger": "^7.2.0",
-    "@nestjs/terminus": "^10.2.1",
-    "@nestjs/typeorm": "^10.0.1",
-    "axios": "^1.6.7",
+    "@nestjs/platform-express": "^10.3.7",
+    "@nestjs/swagger": "^7.3.1",
+    "@nestjs/terminus": "^10.2.3",
+    "@nestjs/typeorm": "^10.0.2",
+    "axios": "^1.6.8",
     "class-transformer": "^0.5.1",
     "class-validator": "^0.14.1",
     "compression": "^1.7.4",
-    "date-fns": "^3.3.1",
+    "date-fns": "^3.6.0",
     "dotenv": "^16.4.1",
     "express-basic-auth": "^1.2.1",
     "lodash": "^4.17.21",
@@ -51,33 +51,33 @@
     "passport-headerapikey": "^1.2.2",
     "pino-http": "^9.0.0",
     "pino-pretty": "^10.3.1",
-    "reflect-metadata": "^0.2.1",
+    "reflect-metadata": "^0.2.2",
     "rxjs": "^7.8.1",
     "tsconfig-paths": "^4.2.0",
     "tslib": "^2.6.2",
     "typeorm": "^0.3.20",
-    "typeorm-extension": "^3.4.0",
-    "typescript": "^5.3.3"
+    "typeorm-extension": "^3.5.1",
+    "typescript": "^5.4.5"
   },
   "devDependencies": {
-    "@commitlint/cli": "^18.6.0",
-    "@commitlint/config-conventional": "^18.6.0",
-    "@nestjs/cli": "^10.3.0",
-    "@nestjs/schematics": "^10.1.0",
-    "@nestjs/testing": "^10.3.1",
-    "@tsconfig/node21": "^21.0.1",
+    "@commitlint/cli": "^18.6.1",
+    "@commitlint/config-conventional": "^18.6.3",
+    "@nestjs/cli": "^10.3.2",
+    "@nestjs/schematics": "^10.1.1",
+    "@nestjs/testing": "^10.3.7",
+    "@tsconfig/node21": "^21.0.3",
     "@types/chance": "^1.1.6",
     "@types/compression": "^1.7.5",
     "@types/express": "^4.17.21",
-    "@types/jest": "^29.5.11",
-    "@types/lodash": "^4.14.202",
-    "@types/node": "^20.11.10",
+    "@types/jest": "^29.5.12",
+    "@types/lodash": "^4.17.0",
+    "@types/node": "^20.12.7",
     "@types/supertest": "^6.0.2",
-    "@typescript-eslint/eslint-plugin": "^6.19.1",
-    "@typescript-eslint/parser": "^6.19.1",
+    "@typescript-eslint/eslint-plugin": "^6.21.0",
+    "@typescript-eslint/parser": "^6.21.0",
     "chance": "^1.1.11",
-    "cspell": "^8.3.2",
-    "eslint": "^8.56.0",
+    "cspell": "^8.7.0",
+    "eslint": "^8.57.0",
     "eslint-config-airbnb-base": "^15.0.0",
     "eslint-config-airbnb-typescript": "^17.1.0",
     "eslint-config-airbnb-typescript-prettier": "^5.0.0",
@@ -86,22 +86,22 @@
     "eslint-plugin-deprecation": "^2.0.0",
     "eslint-plugin-eslint-comments": "^3.2.0",
     "eslint-plugin-import": "^2.29.1",
-    "eslint-plugin-jest": "^27.6.3",
+    "eslint-plugin-jest": "^27.9.0",
     "eslint-plugin-jest-formatting": "^3.1.0",
     "eslint-plugin-node": "^11.1.0",
     "eslint-plugin-optimize-regex": "^1.2.1",
     "eslint-plugin-prettier": "^5.1.3",
-    "eslint-plugin-security": "^2.1.0",
+    "eslint-plugin-security": "^2.1.1",
     "eslint-plugin-simple-import-sort": "^10.0.0",
     "eslint-plugin-switch-case": "^1.1.2",
-    "eslint-plugin-unused-imports": "^3.0.0",
-    "husky": "^9.0.6",
+    "eslint-plugin-unused-imports": "^3.1.0",
+    "husky": "^9.0.11",
     "jest": "29.7.0",
     "jest-when": "^3.6.0",
-    "lint-staged": "^15.2.0",
-    "nock": "^13.5.1",
-    "prettier": "^3.2.4",
-    "sort-package-json": "^2.6.0",
+    "lint-staged": "^15.2.2",
+    "nock": "^13.5.4",
+    "prettier": "^3.2.5",
+    "sort-package-json": "^2.10.0",
     "source-map-support": "^0.5.21",
     "supertest": "^6.3.4",
     "ts-jest": "29.1.2",

From c8cb1bc98c72f3258f8bc9d498effdfe561ff45b Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 11 Apr 2024 13:53:36 +0100
Subject: [PATCH 11/56] feat(DTFS2-7052): api-tests for
 geospatial/get-address-by-postcode

---
 ...ordnance-survey-auth-error-response.dto.ts |   8 +
 .../ordnance-survey.service.ts                |   1 -
 src/modules/geospatial/geospatial.service.ts  |   4 +
 .../get-docs-yaml.api-test.ts.snap            | 146 +++++++--
 .../get-address-by-postcode.api-test.ts       | 303 ++++++++----------
 test/support/api.ts                           |   2 +-
 test/support/environment-variables.ts         |   9 +
 .../get-geospatial-addresses-generator.ts     |  98 +++++-
 .../generator/random-value-generator.ts       |   5 +
 9 files changed, 359 insertions(+), 217 deletions(-)
 create mode 100644 src/helper-modules/ordnance-survey/dto/ordnance-survey-auth-error-response.dto.ts

diff --git a/src/helper-modules/ordnance-survey/dto/ordnance-survey-auth-error-response.dto.ts b/src/helper-modules/ordnance-survey/dto/ordnance-survey-auth-error-response.dto.ts
new file mode 100644
index 00000000..36c25260
--- /dev/null
+++ b/src/helper-modules/ordnance-survey/dto/ordnance-survey-auth-error-response.dto.ts
@@ -0,0 +1,8 @@
+export type OrdnanceSurveyAuthErrorResponse = {
+  fault: {
+    faultstring: string;
+    detail: {
+      errorcode: string;
+    };
+  };
+};
diff --git a/src/helper-modules/ordnance-survey/ordnance-survey.service.ts b/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
index 4dbd137c..a079baab 100644
--- a/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
+++ b/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
@@ -22,7 +22,6 @@ export class OrdnanceSurveyService {
 
   async getAddressesByPostcode(postcode): Promise<GetAddressOrdnanceSurveyResponse> {
     const path = `/search/places/v1/postcode?postcode=${encodeURIComponent(postcode)}&lr=${GEOSPATIAL.DEFAULT.RESULT_LANGUAGE}&key=${encodeURIComponent(this.key)}`;
-
     const { data } = await this.httpClient.get<GetAddressOrdnanceSurveyResponse>({
       path,
       headers: { 'Content-Type': 'application/json' },
diff --git a/src/modules/geospatial/geospatial.service.ts b/src/modules/geospatial/geospatial.service.ts
index 0c1b999e..e1cf93df 100644
--- a/src/modules/geospatial/geospatial.service.ts
+++ b/src/modules/geospatial/geospatial.service.ts
@@ -13,6 +13,10 @@ export class GeospatialService {
     const addresses = [];
     const response: GetAddressOrdnanceSurveyResponse = await this.ordnanceSurveyService.getAddressesByPostcode(postcode);
 
+    if (!response?.results) {
+      return [];
+    }
+
     response.results.forEach((item) => {
       // Ordnance survey sends duplicated results with the welsh version too via 'CY'
       const item_data = item[Object.keys(item)[0]];
diff --git a/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap b/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
index df16b21b..77af87e5 100644
--- a/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
+++ b/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
@@ -17,7 +17,7 @@ paths:
                 type: array
                 items:
                   $ref: '#/components/schemas/CurrencyEntity'
-      tags: &ref_0
+      tags:
         - currencies
   /api/v1/currencies/exchange:
     get:
@@ -58,7 +58,8 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/GetCurrencyExchangeDto'
-      tags: *ref_0
+      tags:
+        - currencies
   /api/v1/currencies/{isoCode}:
     get:
       operationId: CurrenciesController_findOne
@@ -80,7 +81,8 @@ paths:
                 type: array
                 items:
                   $ref: '#/components/schemas/CurrencyEntity'
-      tags: *ref_0
+      tags:
+        - currencies
   /api/v1/customers:
     get:
       operationId: CustomersController_getCustomers
@@ -194,38 +196,43 @@ paths:
                     example: ok
                   info:
                     type: object
-                    example: &ref_1
-                      database: &ref_2
+                    example:
+                      database:
                         status: up
                     additionalProperties:
                       type: object
+                      required:
+                        - status
                       properties:
                         status:
                           type: string
-                      additionalProperties:
-                        type: string
+                      additionalProperties: true
                     nullable: true
                   error:
                     type: object
                     example: {}
                     additionalProperties:
                       type: object
+                      required:
+                        - status
                       properties:
                         status:
                           type: string
-                      additionalProperties:
-                        type: string
+                      additionalProperties: true
                     nullable: true
                   details:
                     type: object
-                    example: *ref_1
+                    example:
+                      database:
+                        status: up
                     additionalProperties:
                       type: object
+                      required:
+                        - status
                       properties:
                         status:
                           type: string
-                      additionalProperties:
-                        type: string
+                      additionalProperties: true
         '503':
           description: The Health Check is not successful
           content:
@@ -238,41 +245,49 @@ paths:
                     example: error
                   info:
                     type: object
-                    example: *ref_1
+                    example:
+                      database:
+                        status: up
                     additionalProperties:
                       type: object
+                      required:
+                        - status
                       properties:
                         status:
                           type: string
-                      additionalProperties:
-                        type: string
+                      additionalProperties: true
                     nullable: true
                   error:
                     type: object
                     example:
-                      redis: &ref_3
+                      redis:
                         status: down
                         message: Could not connect
                     additionalProperties:
                       type: object
+                      required:
+                        - status
                       properties:
                         status:
                           type: string
-                      additionalProperties:
-                        type: string
+                      additionalProperties: true
                     nullable: true
                   details:
                     type: object
                     example:
-                      database: *ref_2
-                      redis: *ref_3
+                      database:
+                        status: up
+                      redis:
+                        status: down
+                        message: Could not connect
                     additionalProperties:
                       type: object
+                      required:
+                        - status
                       properties:
                         status:
                           type: string
-                      additionalProperties:
-                        type: string
+                      additionalProperties: true
       tags:
         - healthcheck
   /api/v1/interest-rates:
@@ -342,7 +357,7 @@ paths:
       responses:
         '201':
           description: ''
-      tags: &ref_4
+      tags:
         - numbers
     get:
       operationId: NumbersController_findOne
@@ -373,7 +388,8 @@ paths:
                 type: array
                 items:
                   $ref: '#/components/schemas/UkefId'
-      tags: *ref_4
+      tags:
+        - numbers
   /api/v1/premium/schedule:
     post:
       operationId: PremiumSchedulesController_create
@@ -390,7 +406,7 @@ paths:
       responses:
         '201':
           description: ''
-      tags: &ref_5
+      tags:
         - premium-schedules
   /api/v1/premium/segments/{facilityId}:
     get:
@@ -415,7 +431,8 @@ paths:
                 type: array
                 items:
                   $ref: '#/components/schemas/PremiumScheduleEntity'
-      tags: *ref_5
+      tags:
+        - premium-schedules
   /api/v1/sector-industries:
     get:
       operationId: SectorIndustriesController_find
@@ -469,6 +486,35 @@ paths:
                   $ref: '#/components/schemas/YieldRateEntity'
       tags:
         - yield-rates
+  /api/v1/geospatial/addresses/postcode:
+    get:
+      operationId: GeospatialController_getGeospatial
+      summary: >-
+        A search based on a property's postcode. Will accept a full postcode
+        consisting of the area, district, sector and unit e.g. SO16 0AS.
+      parameters:
+        - name: postcode
+          required: true
+          in: query
+          example: SW1A 2AQ
+          description: Postcode to search for
+          schema:
+            type: string
+      responses:
+        '200':
+          description: >-
+            Returns addresses from Ordanance survey Delivery Point Address (DPA)
+            system.
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/GetAddressesResponseItem'
+        '404':
+          description: Customer not found.
+      tags:
+        - geospatial
 info:
   title: MDM API Specification
   description: MDM API documentation
@@ -850,8 +896,8 @@ components:
           type: number
           example: 1
           description: >-
-            Payment frequency. It can be: 0 -> Null (At maturity), 1 -> Monthly, 2 -> Quarterly, 3->
-            Semi-annually or 4 -> Annually
+            Payment frequency. It can be: 0 -> Null (At maturity), 1 -> Monthly,
+            2 -> Quarterly, 3-> Semi-annually or 4 -> Annually
         guaranteeCommencementDate:
           format: date-time
           type: string
@@ -1087,6 +1133,50 @@ components:
         - updated
         - effectiveTo
         - effectiveFrom
+    GetAddressesResponseItem:
+      type: object
+      properties:
+        organisationName:
+          type: string
+          description: Organisation name if available
+          example: CHURCHILL MUSEUM & CABINET WAR ROOMS
+        addressLine1:
+          type: string
+          description: Address line 1
+          example: CLIVE STEPS  KING CHARLES STREET
+        addressLine2:
+          type: string
+          description: Address line 2
+          example: null
+        addressLine3:
+          type: string
+          description: Address line 3
+          example: null
+        locality:
+          type: string
+          description: Locality, Town
+          example: LONDON
+        postalCode:
+          type: string
+          description: Postcode
+          example: SW1A 2AQ
+        country:
+          type: string
+          description: Country of address record
+          example: England
+          enum:
+            - England
+            - Scotland
+            - Wales
+            - Northern Ireland
+      required:
+        - organisationName
+        - addressLine1
+        - addressLine2
+        - addressLine3
+        - locality
+        - postalCode
+        - country
 security:
   - ApiKeyHeader: []
 "
diff --git a/test/geospatial/get-address-by-postcode.api-test.ts b/test/geospatial/get-address-by-postcode.api-test.ts
index a1522d29..770ef319 100644
--- a/test/geospatial/get-address-by-postcode.api-test.ts
+++ b/test/geospatial/get-address-by-postcode.api-test.ts
@@ -1,8 +1,7 @@
-// import { CUSTOMERS, ENUMS } from '@ukef/constants';
-// import { IncorrectAuthArg, withClientAuthenticationTests } from '@ukef-test/common-tests/client-authentication-api-tests';
+import { GEOSPATIAL } from '@ukef/constants';
+import { IncorrectAuthArg, withClientAuthenticationTests } from '@ukef-test/common-tests/client-authentication-api-tests';
 import { Api } from '@ukef-test/support/api';
-// import { ENVIRONMENT_VARIABLES, TIME_EXCEEDING_INFORMATICA_TIMEOUT } from '@ukef-test/support/environment-variables';
-import { ENVIRONMENT_VARIABLES } from '@ukef-test/support/environment-variables';
+import { ENVIRONMENT_VARIABLES, TIME_EXCEEDING_ORDNANCE_SURVEY_TIMEOUT } from '@ukef-test/support/environment-variables';
 import { GetGeospatialAddressesGenerator } from '@ukef-test/support/generator/get-geospatial-addresses-generator';
 import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator';
 import nock from 'nock';
@@ -12,13 +11,21 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
 
   let api: Api;
 
-  const { ordnanceSurveyPath, mdmPath, getAddressesByPostcodeResponse, getAddressOrdnanceSurveyResponse } = new GetGeospatialAddressesGenerator(
-    valueGenerator,
-  ).generate({
+  const {
+    ordnanceSurveyPath,
+    mdmPath,
+    getAddressByPostcodeResponse,
+    getAddressOrdnanceSurveyResponse,
+    getAddressOrdnanceSurveyEmptyResponse,
+    getAddressessOrdnanceSurveyResponse,
+    ordnanceSurveyAuthErrorResponse,
+  } = new GetGeospatialAddressesGenerator(valueGenerator).generate({
+    postcode: GEOSPATIAL.EXAMPLES.POSTCODE,
+    key: ENVIRONMENT_VARIABLES.ORDNANCE_SURVEY_KEY,
     numberToGenerate: 2,
   });
 
-  // const getMdmUrl = (postcode: string) => `/api/v1/geospatial/addresses/postcode?postcode=${encodeURIComponent(postcode)}`;
+  const getMdmUrl = (postcode: string) => `/api/v1/geospatial/addresses/postcode?postcode=${encodeURIComponent(postcode)}`;
 
   beforeAll(async () => {
     api = await Api.create();
@@ -33,181 +40,121 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
     nock.cleanAll();
   });
 
-  // withClientAuthenticationTests({
-  //   givenTheRequestWouldOtherwiseSucceed: () => {
-  //     requestToGetCustomers(mdmPath[0]).reply(200, getAddressesByPostcodeResponse[0]);
-  //   },
-  //   makeRequestWithoutAuth: (incorrectAuth?: IncorrectAuthArg) => api.getWithoutAuth(mdmPath[0], incorrectAuth?.headerName, incorrectAuth?.headerValue),
-  // });
+  // MDM auth tests
+  withClientAuthenticationTests({
+    givenTheRequestWouldOtherwiseSucceed: () => {
+      requestToGetAddressesByPostcode(mdmPath[0]).reply(200, getAddressOrdnanceSurveyResponse[0]);
+    },
+    makeRequestWithoutAuth: (incorrectAuth?: IncorrectAuthArg) => api.getWithoutAuth(mdmPath[0], incorrectAuth?.headerName, incorrectAuth?.headerValue),
+  });
+
+  it('returns a 200 response with the address if it is returned by Ordnance Survey API', async () => {
+    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(200, getAddressOrdnanceSurveyResponse[0]);
+
+    const { status, body } = await api.get(mdmPath[0]);
+
+    expect(status).toBe(200);
+    expect(body).toStrictEqual(getAddressByPostcodeResponse[0]);
+  });
+
+  it('returns a 200 response with the addresses if they are returned by Ordnance Survey API', async () => {
+    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(200, getAddressessOrdnanceSurveyResponse);
+
+    const { status, body } = await api.get(mdmPath[0]);
 
-  it.only('returns a 200 response with the customers if they are returned by Informatica', async () => {
-    requestToGetCustomers(ordnanceSurveyPath[0]).reply(200, getAddressOrdnanceSurveyResponse[0]);
+    expect(status).toBe(200);
+    expect(body).toStrictEqual([getAddressByPostcodeResponse[0][0], getAddressByPostcodeResponse[1][0]]);
+  });
+
+  it('returns a empty 200 response if Ordnance Survey API returns a 200 without results', async () => {
+    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(200, getAddressOrdnanceSurveyEmptyResponse[0]);
 
     const { status, body } = await api.get(mdmPath[0]);
 
     expect(status).toBe(200);
-    expect(body).toStrictEqual(getAddressesByPostcodeResponse[0]);
+    expect(body).toStrictEqual([]);
+  });
+
+  it('returns a 500 response if Ordnance Survey API returns a status code 401', async () => {
+    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(401);
+
+    const { status, body } = await api.get(mdmPath[0]);
+
+    expect(status).toBe(500);
+    expect(body).toStrictEqual({
+      statusCode: 500,
+      message: 'Internal server error',
+    });
+  });
+
+  it('returns a 500 response if Ordnance Survey API returns a status code 404', async () => {
+    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(404);
+
+    const { status, body } = await api.get(mdmPath[0]);
+
+    expect(status).toBe(500);
+    expect(body).toStrictEqual({
+      statusCode: 500,
+      message: 'Internal server error',
+    });
+  });
+
+  it('returns a 500 response if Ordnance Survey API times out', async () => {
+    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).delay(TIME_EXCEEDING_ORDNANCE_SURVEY_TIMEOUT).reply(200, getAddressOrdnanceSurveyResponse[0]);
+
+    const { status, body } = await api.get(mdmPath[0]);
+
+    expect(status).toBe(500);
+    expect(body).toStrictEqual({
+      statusCode: 500,
+      message: 'Internal server error',
+    });
+  });
+
+  it('returns a 500 response if Ordnance Survey API returns error', async () => {
+    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(401, ordnanceSurveyAuthErrorResponse);
+
+    const { status, body } = await api.get(mdmPath[0]);
+
+    expect(status).toBe(500);
+    expect(body).toStrictEqual({
+      statusCode: 500,
+      message: 'Internal server error',
+    });
+  });
+
+  it.each([
+    {
+      postcode: valueGenerator.string({ length: 4 }),
+      expectedError: 'postcode must be longer than or equal to 5 characters',
+    },
+    {
+      postcode: valueGenerator.string({ length: 9 }),
+      expectedError: 'postcode must be shorter than or equal to 8 characters',
+    },
+    {
+      postcode: valueGenerator.stringOfNumericCharacters({ length: 5 }),
+      expectedError: 'postcode must match /^[A-Za-z]{1,2}[\\dRr][\\dA-Za-z]?\\s?\\d[ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$/ regular expression',
+    },
+    {
+      postcode: 'AA1 1CL',
+      expectedError: 'postcode must match /^[A-Za-z]{1,2}[\\dRr][\\dA-Za-z]?\\s?\\d[ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$/ regular expression',
+    },
+    {
+      postcode: 'SW1  2AQ',
+      expectedError: 'postcode must match /^[A-Za-z]{1,2}[\\dRr][\\dA-Za-z]?\\s?\\d[ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$/ regular expression',
+    },
+  ])('returns a 400 response with error array if postcode is "$postcode"', async ({ postcode, expectedError }) => {
+    const { status, body } = await api.get(getMdmUrl(postcode));
+
+    expect(status).toBe(400);
+    expect(body).toMatchObject({
+      error: 'Bad Request',
+      message: expect.arrayContaining([expectedError]),
+      statusCode: 400,
+    });
   });
 
-  // it.each([
-  //   {
-  //     query: { name: CUSTOMERS.EXAMPLES.NAME, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.YES },
-  //   },
-  //   {
-  //     query: { companyReg: CUSTOMERS.EXAMPLES.COMPANYREG, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.YES },
-  //   },
-  //   {
-  //     query: { partyUrn: CUSTOMERS.EXAMPLES.PARTYURN, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.YES },
-  //   },
-  //   {
-  //     query: { name: CUSTOMERS.EXAMPLES.NAME, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.NO },
-  //   },
-  //   {
-  //     query: { companyReg: CUSTOMERS.EXAMPLES.COMPANYREG, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.NO },
-  //   },
-  //   {
-  //     query: { partyUrn: CUSTOMERS.EXAMPLES.PARTYURN, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.NO },
-  //   },
-  //   {
-  //     query: { name: CUSTOMERS.EXAMPLES.NAME, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.LEGACY_ONLY },
-  //   },
-  //   {
-  //     query: { companyReg: CUSTOMERS.EXAMPLES.COMPANYREG, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.LEGACY_ONLY },
-  //   },
-  //   {
-  //     query: { partyUrn: CUSTOMERS.EXAMPLES.PARTYURN, fallbackToLegacyData: ENUMS.FALLBACK_TO_LEGACY_DATA.LEGACY_ONLY },
-  //   },
-  //   {
-  //     query: { name: CUSTOMERS.EXAMPLES.NAME },
-  //   },
-  //   {
-  //     query: { companyReg: CUSTOMERS.EXAMPLES.COMPANYREG },
-  //   },
-  //   {
-  //     query: { partyUrn: CUSTOMERS.EXAMPLES.PARTYURN },
-  //   },
-  // ])('returns a 200 response with the customers if query is "$query"', async ({ query }) => {
-  //   const { mdmPath, informaticaPath, getCustomersResponse } = new GetCustomersGenerator(valueGenerator).generate({
-  //     numberToGenerate: 1,
-  //     query,
-  //   });
-  //   requestToGetCustomers(informaticaPath).reply(200, getCustomersResponse[0]);
-
-  //   const { status, body } = await api.get(mdmPath);
-
-  //   expect(status).toBe(200);
-  //   expect(body).toStrictEqual(getCustomersResponse[0]);
-  // });
-
-  // it('returns a 404 response if Informatica returns a 404 response with the string "null"', async () => {
-  //   requestToGetCustomers(informaticaPath).reply(404, [
-  //     {
-  //       errorCode: '404',
-  //       errorDateTime: '2023-06-30T13:41:33Z',
-  //       errorMessage: 'Company registration not found',
-  //       errorDescription: 'Party details request for the requested company registration not found.',
-  //     },
-  //   ]);
-
-  //   const { status, body } = await api.get(mdmPath);
-
-  //   expect(status).toBe(404);
-  //   expect(body).toStrictEqual({
-  //     statusCode: 404,
-  //     message: 'Customer not found.',
-  //   });
-  // });
-
-  // it('returns a 500 response if Informatica returns a status code that is NOT 200', async () => {
-  //   requestToGetCustomers(informaticaPath).reply(401);
-
-  //   const { status, body } = await api.get(mdmPath);
-
-  //   expect(status).toBe(500);
-  //   expect(body).toStrictEqual({
-  //     statusCode: 500,
-  //     message: 'Internal server error',
-  //   });
-  // });
-
-  // it('returns a 500 response if getting the facility investors from ACBS times out', async () => {
-  //   requestToGetCustomers(informaticaPath).delay(TIME_EXCEEDING_INFORMATICA_TIMEOUT).reply(200, getCustomersResponse[0]);
-
-  //   const { status, body } = await api.get(mdmPath);
-
-  //   expect(status).toBe(500);
-  //   expect(body).toStrictEqual({
-  //     statusCode: 500,
-  //     message: 'Internal server error',
-  //   });
-  // });
-
-  // it.each([
-  //   {
-  //     query: { name: valueGenerator.string({ length: 1 }) },
-  //     expectedError: 'name must be longer than or equal to 2 characters',
-  //   },
-  //   {
-  //     query: { name: valueGenerator.string({ length: 256 }) },
-  //     expectedError: 'name must be shorter than or equal to 255 characters',
-  //   },
-  //   {
-  //     query: { name: valueGenerator.word(), extraParameter: valueGenerator.word() },
-  //     expectedError: 'property extraParameter should not exist',
-  //   },
-  //   {
-  //     query: { companyReg: valueGenerator.string({ length: 7 }) },
-  //     expectedError: 'companyReg must be longer than or equal to 8 characters',
-  //   },
-  //   {
-  //     query: { companyReg: valueGenerator.string({ length: 11 }) },
-  //     expectedError: 'companyReg must be shorter than or equal to 10 characters',
-  //   },
-  //   {
-  //     query: { partyUrn: valueGenerator.stringOfNumericCharacters({ length: 7 }) },
-  //     expectedError: 'partyUrn must match /^\\d{8}$/ regular expression',
-  //   },
-  //   {
-  //     query: { partyUrn: valueGenerator.stringOfNumericCharacters({ length: 9 }) },
-  //     expectedError: 'partyUrn must match /^\\d{8}$/ regular expression',
-  //   },
-  //   {
-  //     query: { partyUrn: valueGenerator.word() },
-  //     expectedError: 'partyUrn must match /^\\d{8}$/ regular expression',
-  //   },
-  // ])('returns a 400 response with error array if query is "$query"', async ({ query, expectedError }) => {
-  //   const { status, body } = await api.get(getMdmUrl(query));
-
-  //   expect(status).toBe(400);
-  //   expect(body).toMatchObject({
-  //     error: 'Bad Request',
-  //     message: expect.arrayContaining([expectedError]),
-  //     statusCode: 400,
-  //   });
-  // });
-
-  // it.each([
-  //   {
-  //     query: {},
-  //     expectedError: 'One and just one search parameter is required',
-  //   },
-  //   {
-  //     query: { name: valueGenerator.word(), companyReg: valueGenerator.string({ length: 8 }) },
-  //     expectedError: 'One and just one search parameter is required',
-  //   },
-  // ])('returns a 400 response with error string if query is "$query"', async ({ query, expectedError }) => {
-  //   const { status, body } = await api.get(getMdmUrl(query));
-
-  //   expect(status).toBe(400);
-  //   expect(body).toMatchObject({
-  //     error: 'Bad Request',
-  //     message: expectedError,
-  //     statusCode: 400,
-  //   });
-  // });
-
-  const basicAuth = Buffer.from(`${ENVIRONMENT_VARIABLES.APIM_INFORMATICA_USERNAME}:${ENVIRONMENT_VARIABLES.APIM_INFORMATICA_PASSWORD}`).toString('base64');
-
-  const requestToGetCustomers = (informaticaPath: string): nock.Interceptor =>
-    nock(ENVIRONMENT_VARIABLES.APIM_INFORMATICA_URL).get(informaticaPath).matchHeader('authorization', `Basic ${basicAuth}`);
+  const requestToGetAddressesByPostcode = (ordnanceSurveyPath: string): nock.Interceptor =>
+    nock(ENVIRONMENT_VARIABLES.ORDNANCE_SURVEY_URL).get(ordnanceSurveyPath);
 });
diff --git a/test/support/api.ts b/test/support/api.ts
index 48e84d9b..dc03f634 100644
--- a/test/support/api.ts
+++ b/test/support/api.ts
@@ -29,7 +29,7 @@ export class Api {
     return this.app.destroy();
   }
 
-  private request(): request.SuperTest<request.Test> {
+  private request(): any {
     return request(this.app.getHttpServer());
   }
 
diff --git a/test/support/environment-variables.ts b/test/support/environment-variables.ts
index 1c9a8d29..5f57fa07 100644
--- a/test/support/environment-variables.ts
+++ b/test/support/environment-variables.ts
@@ -17,6 +17,12 @@ export const ENVIRONMENT_VARIABLES = Object.freeze({
   APIM_INFORMATICA_MAX_REDIRECTS: 0,
   APIM_INFORMATICA_TIMEOUT: 1000,
 
+  ORDNANCE_SURVEY_URL: valueGenerator.httpsUrl(),
+  ORDNANCE_SURVEY_KEY: valueGenerator.word(),
+
+  ORDNANCE_SURVEY_MAX_REDIRECTS: 0,
+  ORDNANCE_SURVEY_TIMEOUT: 5000,
+
   API_KEY: valueGenerator.string(),
 });
 
@@ -26,6 +32,9 @@ export const getEnvironmentVariablesForProcessEnv = (): NodeJS.ProcessEnv => ({
   SINGLE_LINE_LOG_FORMAT: ENVIRONMENT_VARIABLES.SINGLE_LINE_LOG_FORMAT.toString(),
   APIM_INFORMATICA_MAX_REDIRECTS: ENVIRONMENT_VARIABLES.APIM_INFORMATICA_MAX_REDIRECTS.toString(),
   APIM_INFORMATICA_TIMEOUT: ENVIRONMENT_VARIABLES.APIM_INFORMATICA_TIMEOUT.toString(),
+  ORDNANCE_SURVEY_MAX_REDIRECTS: ENVIRONMENT_VARIABLES.ORDNANCE_SURVEY_MAX_REDIRECTS.toString(),
+  ORDNANCE_SURVEY_TIMEOUT: ENVIRONMENT_VARIABLES.ORDNANCE_SURVEY_TIMEOUT.toString(),
 });
 
 export const TIME_EXCEEDING_INFORMATICA_TIMEOUT = ENVIRONMENT_VARIABLES.APIM_INFORMATICA_TIMEOUT + 500;
+export const TIME_EXCEEDING_ORDNANCE_SURVEY_TIMEOUT = ENVIRONMENT_VARIABLES.ORDNANCE_SURVEY_TIMEOUT + 500;
diff --git a/test/support/generator/get-geospatial-addresses-generator.ts b/test/support/generator/get-geospatial-addresses-generator.ts
index e24fab8a..efabd55c 100644
--- a/test/support/generator/get-geospatial-addresses-generator.ts
+++ b/test/support/generator/get-geospatial-addresses-generator.ts
@@ -1,5 +1,6 @@
 import { ENUMS, GEOSPATIAL } from '@ukef/constants';
 import { GetAddressOrdnanceSurveyResponse } from '@ukef/helper-modules/ordnance-survey/dto/get-addresses-ordnance-survey-response.dto';
+import { OrdnanceSurveyAuthErrorResponse } from '@ukef/helper-modules/ordnance-survey/dto/ordnance-survey-auth-error-response.dto';
 import { GetAddressByPostcodeQueryDto } from '@ukef/modules/geospatial/dto/get-address-by-postcode-query.dto';
 import { GetAddressesResponse } from '@ukef/modules/geospatial/dto/get-addresses-response.dto';
 
@@ -20,17 +21,12 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
       DEPENDENT_LOCALITY: this.valueGenerator.word(),
       POST_TOWN: this.valueGenerator.word(),
       POSTCODE: this.valueGenerator.postcode(),
-      COUNTRY_CODE: this.valueGenerator.enumValue(ENUMS.GEOSPATIAL_COUNTRIES),
+      COUNTRY_CODE: this.valueGenerator.enumKey(ENUMS.GEOSPATIAL_COUNTRIES),
     };
   }
 
   protected transformRawValuesToGeneratedValues(values: AddressValues[], { postcode, key }: GenerateOptions): GenerateResult {
     const useKey = key || 'test';
-    // let request: GetAddressByPostcodeQueryDto = ;
-    // let ordnanceSurveyRequest: GetCustomersInformaticaQueryDto[];
-    // let ordnanceSurveyPath: 'test',
-    // let mdmPath: 'test',
-    // let getAddressesByPostcodeResponse: GetAddressesResponse[];
 
     const request: GetAddressByPostcodeQueryDto[] = values.map((v) => ({ postcode: postcode || v.POSTCODE }) as GetAddressByPostcodeQueryDto);
 
@@ -44,7 +40,7 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
       return `/api/v1/geospatial/addresses/postcode?postcode=${usePostcode}`;
     });
 
-    const getAddressesByPostcodeResponse: GetAddressesResponse[] = values.map((v) => [
+    const getAddressByPostcodeResponse: GetAddressesResponse[] = values.map((v) => [
       {
         organisationName: v.ORGANISATION_NAME,
         addressLine1: `${v.BUILDING_NAME} ${v.BUILDING_NUMBER} ${v.THOROUGHFARE_NAME}`,
@@ -111,13 +107,94 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
       ],
     }));
 
+    const getAddressessOrdnanceSurveyResponse: GetAddressOrdnanceSurveyResponse = {
+      header: {
+        uri: 'test',
+        query: 'test',
+        offset: 0,
+        totalresults: 0,
+        format: 'test',
+        dataset: 'test',
+        lr: 'test',
+        maxresults: 100,
+        epoch: 'test',
+        lastupdate: 'test',
+        output_srs: 'test',
+      },
+      results: values.map((v) => ({
+        DPA: {
+          UPRN: 'test',
+          UDPRN: 'test',
+          ADDRESS: 'test',
+          BUILDING_NAME: v.BUILDING_NAME,
+          BUILDING_NUMBER: v.BUILDING_NUMBER,
+          ORGANISATION_NAME: v.ORGANISATION_NAME,
+          DEPENDENT_LOCALITY: v.DEPENDENT_LOCALITY,
+          THOROUGHFARE_NAME: v.THOROUGHFARE_NAME,
+          POST_TOWN: v.POST_TOWN,
+          POSTCODE: v.POSTCODE,
+          RPC: 'test',
+          X_COORDINATE: 12345,
+          Y_COORDINATE: 12345,
+          STATUS: 'test',
+          LOGICAL_STATUS_CODE: 'test',
+          CLASSIFICATION_CODE: 'test',
+          CLASSIFICATION_CODE_DESCRIPTION: 'test',
+          LOCAL_CUSTODIAN_CODE: 12345,
+          LOCAL_CUSTODIAN_CODE_DESCRIPTION: 'test',
+          COUNTRY_CODE: v.COUNTRY_CODE,
+          COUNTRY_CODE_DESCRIPTION: 'test',
+          POSTAL_ADDRESS_CODE: 'test',
+          POSTAL_ADDRESS_CODE_DESCRIPTION: 'test',
+          BLPU_STATE_CODE: 'test',
+          BLPU_STATE_CODE_DESCRIPTION: 'test',
+          TOPOGRAPHY_LAYER_TOID: 'test',
+          LAST_UPDATE_DATE: 'test',
+          ENTRY_DATE: 'test',
+          BLPU_STATE_DATE: 'test',
+          LANGUAGE: 'test',
+          MATCH: 12345,
+          MATCH_DESCRIPTION: 'test',
+          DELIVERY_POINT_SUFFIX: 'test',
+        },
+      })),
+    };
+
+    const getAddressOrdnanceSurveyEmptyResponse: GetAddressOrdnanceSurveyResponse[] = values.map(() => ({
+      header: {
+        uri: 'test',
+        query: 'test',
+        offset: 0,
+        totalresults: 0,
+        format: 'test',
+        dataset: 'test',
+        lr: 'test',
+        maxresults: 100,
+        epoch: 'test',
+        lastupdate: 'test',
+        output_srs: 'test',
+      },
+    }));
+
+    const ordnanceSurveyAuthErrorResponse = {
+      fault: {
+        faultstring: 'Invalid ApiKey',
+        detail: {
+          errorcode: 'oauth.v2.InvalidApiKey',
+        },
+      },
+    };
+
     return {
       request,
       // ordnanceSurveyRequest,
       ordnanceSurveyPath,
       mdmPath,
-      getAddressesByPostcodeResponse,
+      getAddressByPostcodeResponse,
       getAddressOrdnanceSurveyResponse,
+      getAddressOrdnanceSurveyEmptyResponse,
+      getAddressessOrdnanceSurveyResponse,
+      ordnanceSurveyAuthErrorResponse,
     };
   }
 }
@@ -143,6 +220,9 @@ interface GenerateResult {
   //ordnanceSurveyRequest: GetCustomersInformaticaQueryDto[];
   ordnanceSurveyPath: string[];
   mdmPath: string[];
-  getAddressesByPostcodeResponse: GetAddressesResponse[];
+  getAddressByPostcodeResponse: GetAddressesResponse[];
   getAddressOrdnanceSurveyResponse: GetAddressOrdnanceSurveyResponse[];
+  getAddressessOrdnanceSurveyResponse: GetAddressOrdnanceSurveyResponse;
+  getAddressOrdnanceSurveyEmptyResponse: GetAddressOrdnanceSurveyResponse[];
+  ordnanceSurveyAuthErrorResponse: OrdnanceSurveyAuthErrorResponse;
 }
diff --git a/test/support/generator/random-value-generator.ts b/test/support/generator/random-value-generator.ts
index 612f9d59..929fce0e 100644
--- a/test/support/generator/random-value-generator.ts
+++ b/test/support/generator/random-value-generator.ts
@@ -75,4 +75,9 @@ export class RandomValueGenerator {
     const possibleValues = Object.values(theEnum);
     return possibleValues[this.integer({ min: 0, max: possibleValues.length - 1 })] as T;
   }
+
+  enumKey<T = string>(theEnum: Enum): T {
+    const possibleValues = Object.keys(theEnum);
+    return possibleValues[this.integer({ min: 0, max: possibleValues.length - 1 })] as T;
+  }
 }

From c91144daa23e806764e5cd80382d1726d4380339 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 11 Apr 2024 15:21:50 +0100
Subject: [PATCH 12/56] feat(DTFS2-7052): refactor api tests to match TFS and
 solve type issues

---
 test/api.ts                                   | 44 ---------------
 test/app.api-test.ts                          | 19 +++----
 test/createApp.ts                             | 30 ----------
 test/currencies/currencies.api-test.ts        | 43 +++++++-------
 .../exposure-period.api-test.ts               | 49 ++++++++--------
 .../interest-rates/interest-rates.api-test.ts | 19 +++----
 test/markets/markets.api-test.ts              | 56 +++++++++----------
 test/numbers/numbers.api-test.ts              | 55 +++++++++---------
 .../premium-schedules.api-test.ts             | 47 +++++++---------
 .../sector-industries.api-test.ts             | 33 +++++------
 test/support/api.ts                           |  4 ++
 test/yield-rates/yield-rates.api-test.ts      | 47 +++++++---------
 12 files changed, 166 insertions(+), 280 deletions(-)
 delete mode 100644 test/api.ts
 delete mode 100644 test/createApp.ts

diff --git a/test/api.ts b/test/api.ts
deleted file mode 100644
index 68d549d9..00000000
--- a/test/api.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { INestApplication } from '@nestjs/common';
-import request from 'supertest';
-
-export class Api {
-  app: INestApplication;
-
-  public constructor(app: INestApplication) {
-    this.app = app;
-  }
-
-  get(url: string) {
-    return request(this.app).get(url);
-  }
-
-  post(data: string | object) {
-    return { to: (url: string) => request(this.app).post(url).send(data) };
-  }
-
-  postEach(list: any) {
-    return {
-      to: async (url: string) => {
-        const results = [];
-
-        for (const data of list) {
-          const result = await request(this.app).post(url).send(data);
-
-          results.push(result);
-        }
-
-        return results;
-      },
-    };
-  }
-
-  put(data: string | object) {
-    return { to: (url: string) => request(this.app).put(url).send(data) };
-  }
-
-  remove(data: string | object) {
-    return {
-      to: (url: string) => request(this.app).delete(url).send(data),
-    };
-  }
-}
diff --git a/test/app.api-test.ts b/test/app.api-test.ts
index 6acfd80b..0853661c 100644
--- a/test/app.api-test.ts
+++ b/test/app.api-test.ts
@@ -1,24 +1,19 @@
-import { INestApplication } from '@nestjs/common';
-
-import { Api } from './api';
-import { CreateApp } from './createApp';
+import { Api } from '@ukef-test/support/api';
 
 describe('AppController (e2e)', () => {
-  let app: INestApplication;
   let api;
 
   beforeAll(async () => {
-    app = await new CreateApp().init();
-    api = new Api(app.getHttpServer());
+    api = await Api.create();
+  });
+
+  afterAll(async () => {
+    await api.destroy();
   });
 
   it(`GET /ready`, async () => {
-    const { status } = await api.get('/ready');
+    const { status } = await api.get('/api/v1/ready');
 
     expect(status).toBe(200);
   });
-
-  afterAll(async () => {
-    await app.close();
-  });
 });
diff --git a/test/createApp.ts b/test/createApp.ts
deleted file mode 100644
index aee3bcee..00000000
--- a/test/createApp.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { ValidationPipe } from '@nestjs/common';
-import { Test, TestingModule } from '@nestjs/testing';
-
-import { MainModule } from '../src/main.module';
-
-export class CreateApp {
-  // NestJs app initialisation happens in /src/main.ts, here is some duplication of same process.
-  // TODO: maybe it is possible to reuse initialisation in /src/main.ts as similar approach is done in DTFS projects.
-  async init() {
-    const moduleFixture: TestingModule = await Test.createTestingModule({
-      imports: [MainModule],
-    }).compile();
-
-    const app = moduleFixture.createNestApplication();
-
-    // Validation pipeline is require to check validations.
-    app.useGlobalPipes(
-      new ValidationPipe({
-        whitelist: true,
-        transform: true,
-        forbidNonWhitelisted: true,
-        transformOptions: {
-          enableImplicitConversion: true,
-        },
-      }),
-    );
-    await app.init();
-    return app;
-  }
-}
diff --git a/test/currencies/currencies.api-test.ts b/test/currencies/currencies.api-test.ts
index 595d9d51..3e6fdf9e 100644
--- a/test/currencies/currencies.api-test.ts
+++ b/test/currencies/currencies.api-test.ts
@@ -1,10 +1,6 @@
-import { INestApplication } from '@nestjs/common';
-
-import { Api } from '../api';
-import { CreateApp } from '../createApp';
+import { Api } from '@ukef-test/support/api';
 
 describe('Currencies', () => {
-  let app: INestApplication;
   let api: Api;
 
   const expectedResult = expect.arrayContaining([
@@ -25,31 +21,34 @@ describe('Currencies', () => {
   ]);
 
   beforeAll(async () => {
-    app = await new CreateApp().init();
-    api = new Api(app.getHttpServer());
+    api = await Api.create();
+  });
+
+  afterAll(async () => {
+    await api.destroy();
   });
 
   describe('/currencies/exchange', () => {
     it('should return 200 on GET `/currencies/exchange?source=GBP&target=AED&exchangeRateDate=2021-01-26`', async () => {
-      const { status, body } = await api.get('/currencies/exchange?source=GBP&target=AED&exchangeRateDate=2021-01-26');
+      const { status, body } = await api.get('/api/v1/currencies/exchange?source=GBP&target=AED&exchangeRateDate=2021-01-26');
       expect(status).toBe(200);
       expect(body).toEqual(expectedResult);
     });
 
     it('should return 200 on GET `/currencies/exchange?source=GBP&target=AED`', async () => {
-      const { status, body } = await api.get('/currencies/exchange?source=GBP&target=AED');
+      const { status, body } = await api.get('/api/v1/currencies/exchange?source=GBP&target=AED');
       expect(status).toBe(200);
       expect(body).toEqual(expectedResult);
     });
 
     it('should return 200 on GET `currencies/exchange?source=USD&target=GBP`', async () => {
-      const { status, body } = await api.get('/currencies/exchange?source=USD&target=GBP');
+      const { status, body } = await api.get('/api/v1/currencies/exchange?source=USD&target=GBP');
       expect(status).toBe(200);
       expect(body).toEqual(expectedResult);
     });
 
     it('should return 404 on GET `/currencies/exchange?source=AED&target=GBP`', async () => {
-      const { status, body } = await api.get('/currencies/exchange?source=AED&target=GBP');
+      const { status, body } = await api.get('/api/v1/currencies/exchange?source=AED&target=GBP');
       expect(status).toBe(404);
       expect(body).toEqual({
         statusCode: 404,
@@ -59,7 +58,7 @@ describe('Currencies', () => {
     });
 
     it('should return 400 on GET `/currencies/exchange?source=GBP&target=abc`', async () => {
-      const { status, body } = await api.get('/currencies/exchange?source=GBP&target=abc');
+      const { status, body } = await api.get('/api/v1/currencies/exchange?source=GBP&target=abc');
       expect(status).toBe(400);
       expect(body).toEqual({
         statusCode: 400,
@@ -69,7 +68,7 @@ describe('Currencies', () => {
     });
 
     it('should return 400 on GET `/currencies/exchange?source=abc&target=AED`', async () => {
-      const { status, body } = await api.get('/currencies/exchange?source=abc&target=AED');
+      const { status, body } = await api.get('/api/v1/currencies/exchange?source=abc&target=AED');
       expect(status).toBe(400);
       expect(body).toEqual({
         statusCode: 400,
@@ -79,7 +78,7 @@ describe('Currencies', () => {
     });
 
     it('should return 400 on GET `/currencies/exchange?source=abc&target=def`', async () => {
-      const { status, body } = await api.get('/currencies/exchange?source=abc&target=def');
+      const { status, body } = await api.get('/api/v1/currencies/exchange?source=abc&target=def');
       expect(status).toBe(400);
       expect(body).toEqual({
         statusCode: 400,
@@ -89,7 +88,7 @@ describe('Currencies', () => {
     });
 
     it('should return 400 on GET `/currencies/exchange`', async () => {
-      const { status, body } = await api.get('/currencies/exchange');
+      const { status, body } = await api.get('/api/v1/currencies/exchange');
       expect(status).toBe(400);
       expect(body).toEqual({
         statusCode: 400,
@@ -99,7 +98,7 @@ describe('Currencies', () => {
     });
 
     it('should return 400 on GET `/currencies/exchange?source=GBP`', async () => {
-      const { status, body } = await api.get('/currencies/exchange?source=GBP');
+      const { status, body } = await api.get('/api/v1/currencies/exchange?source=GBP');
       expect(status).toBe(400);
       expect(body).toEqual({
         statusCode: 400,
@@ -109,7 +108,7 @@ describe('Currencies', () => {
     });
 
     it('should return 400 on GET `/currencies/exchange?target=GBP`', async () => {
-      const { status, body } = await api.get('/currencies/exchange?target=GBP');
+      const { status, body } = await api.get('/api/v1/currencies/exchange?target=GBP');
       expect(status).toBe(400);
       expect(body).toEqual({
         statusCode: 400,
@@ -121,7 +120,7 @@ describe('Currencies', () => {
 
   describe('/currencies', () => {
     it('should return 200 on GET `/currencies`', async () => {
-      const { status, body } = await api.get('/currencies');
+      const { status, body } = await api.get('/api/v1/currencies');
       expect(status).toBe(200);
       expect(body).toEqual(
         expect.arrayContaining([
@@ -142,7 +141,7 @@ describe('Currencies', () => {
 
   describe('/currencies/{isoCode}', () => {
     it('should return 200 on GET `/currencies/GBP`', async () => {
-      const { status, body } = await api.get('/currencies/GBP');
+      const { status, body } = await api.get('/api/v1/currencies/GBP');
       expect(status).toBe(200);
       expect(body).toEqual(
         expect.arrayContaining([
@@ -161,7 +160,7 @@ describe('Currencies', () => {
     });
 
     it('should return 400 on GET `/currencies/abc`', async () => {
-      const { status, body } = await api.get('/currencies/abc');
+      const { status, body } = await api.get('/api/v1/currencies/abc');
       expect(status).toBe(400);
       expect(body).toEqual({
         statusCode: 400,
@@ -170,8 +169,4 @@ describe('Currencies', () => {
       });
     });
   });
-
-  afterAll(async () => {
-    await app.close();
-  });
 });
diff --git a/test/exposure-period/exposure-period.api-test.ts b/test/exposure-period/exposure-period.api-test.ts
index 80095e8e..0781ea76 100644
--- a/test/exposure-period/exposure-period.api-test.ts
+++ b/test/exposure-period/exposure-period.api-test.ts
@@ -1,38 +1,37 @@
-import { INestApplication } from '@nestjs/common';
 import { PRODUCTS } from '@ukef/constants';
-
-import { Api } from '../api';
-import { CreateApp } from '../createApp';
+import { Api } from '@ukef-test/support/api';
 
 describe('Exposure period', () => {
-  let app: INestApplication;
   let api: Api;
 
   beforeAll(async () => {
-    app = await new CreateApp().init();
-    api = new Api(app.getHttpServer());
+    api = await Api.create();
+  });
+
+  afterAll(async () => {
+    await api.destroy();
   });
 
   it(`GET /exposure-period?startdate=2017-07-04&enddate=2018-07-04&productgroup=${PRODUCTS.EW}`, async () => {
-    const { status, body } = await api.get(`/exposure-period?startdate=2017-07-04&enddate=2018-07-04&productgroup=${PRODUCTS.EW}`);
+    const { status, body } = await api.get(`/api/v1/exposure-period?startdate=2017-07-04&enddate=2018-07-04&productgroup=${PRODUCTS.EW}`);
     expect(status).toBe(200);
     expect(body.exposurePeriod).toBe(12);
   });
 
   it(`GET /exposure-period?startdate=2017-07-04&enddate=2018-07-05&productgroup=${PRODUCTS.EW}`, async () => {
-    const { status, body } = await api.get(`/exposure-period?startdate=2017-07-04&enddate=2018-07-05&productgroup=${PRODUCTS.EW}`);
+    const { status, body } = await api.get(`/api/v1/exposure-period?startdate=2017-07-04&enddate=2018-07-05&productgroup=${PRODUCTS.EW}`);
     expect(status).toBe(200);
     expect(body.exposurePeriod).toBe(13);
   });
 
   it(`GET /exposure-period?startdate=2017-07-04&enddate=2018-07-04&productgroup=${PRODUCTS.BS}`, async () => {
-    const { status, body } = await api.get(`/exposure-period?startdate=2017-07-04&enddate=2018-07-04&productgroup=${PRODUCTS.BS}`);
+    const { status, body } = await api.get(`/api/v1/exposure-period?startdate=2017-07-04&enddate=2018-07-04&productgroup=${PRODUCTS.BS}`);
     expect(status).toBe(200);
     expect(body.exposurePeriod).toBe(13);
   });
 
   it(`GET /exposure-period?startdate=2017-07-04&enddate=2018-07-05&productgroup=${PRODUCTS.BS}`, async () => {
-    const { status, body } = await api.get(`/exposure-period?startdate=2017-07-04&enddate=2018-07-05&productgroup=${PRODUCTS.BS}`);
+    const { status, body } = await api.get(`/api/v1/exposure-period?startdate=2017-07-04&enddate=2018-07-05&productgroup=${PRODUCTS.BS}`);
     expect(status).toBe(200);
     expect(body.exposurePeriod).toBe(13);
   });
@@ -50,42 +49,42 @@ describe('Exposure period', () => {
 
   // EW Start is EOM
   it(`GET /exposure-period?startdate=2017-03-31&enddate=2017-04-01&productgroup=${PRODUCTS.EW}`, async () => {
-    const { status, body } = await api.get(`/exposure-period?startdate=2017-03-31&enddate=2017-04-01&productgroup=${PRODUCTS.EW}`);
+    const { status, body } = await api.get(`/api/v1/exposure-period?startdate=2017-03-31&enddate=2017-04-01&productgroup=${PRODUCTS.EW}`);
     expect(status).toBe(200);
     expect(body.exposurePeriod).toBe(1);
   });
 
   // BS Start is EOM
   it(`GET /exposure-period?startdate=2017-03-31&enddate=2017-04-29&productgroup=${PRODUCTS.BS}`, async () => {
-    const { status, body } = await api.get(`/exposure-period?startdate=2017-03-31&enddate=2017-04-29&productgroup=${PRODUCTS.BS}`);
+    const { status, body } = await api.get(`/api/v1/exposure-period?startdate=2017-03-31&enddate=2017-04-29&productgroup=${PRODUCTS.BS}`);
     expect(status).toBe(200);
     expect(body.exposurePeriod).toBe(1);
   });
 
   // EW Start is EOM, end is EOM
   it(`GET /exposure-period?startdate=2017-03-31&enddate=2017-04-30&productgroup=${PRODUCTS.EW}`, async () => {
-    const { status, body } = await api.get(`/exposure-period?startdate=2017-03-31&enddate=2017-04-30&productgroup=${PRODUCTS.EW}`);
+    const { status, body } = await api.get(`/api/v1/exposure-period?startdate=2017-03-31&enddate=2017-04-30&productgroup=${PRODUCTS.EW}`);
     expect(status).toBe(200);
     expect(body.exposurePeriod).toBe(1);
   });
 
   // BS Start is EOM, end is EOM, +1 for exposure
   it(`GET /exposure-period?startdate=2017-03-31&enddate=2017-04-30&productgroup=${PRODUCTS.BS}`, async () => {
-    const { status, body } = await api.get(`/exposure-period?startdate=2017-03-31&enddate=2017-04-30&productgroup=${PRODUCTS.BS}`);
+    const { status, body } = await api.get(`/api/v1/exposure-period?startdate=2017-03-31&enddate=2017-04-30&productgroup=${PRODUCTS.BS}`);
     expect(status).toBe(200);
     expect(body.exposurePeriod).toBe(2);
   });
 
   // EW Start DOM = End DOM
   it(`GET /exposure-period?startdate=2017-03-05&enddate=2017-04-05&productgroup=${PRODUCTS.EW}`, async () => {
-    const { status, body } = await api.get(`/exposure-period?startdate=2017-03-05&enddate=2017-04-05&productgroup=${PRODUCTS.EW}`);
+    const { status, body } = await api.get(`/api/v1/exposure-period?startdate=2017-03-05&enddate=2017-04-05&productgroup=${PRODUCTS.EW}`);
     expect(status).toBe(200);
     expect(body.exposurePeriod).toBe(1);
   });
 
   // BS Start DOM = End DOM, +1 for exposure
   it(`GET /exposure-period?startdate=2017-03-05&enddate=2017-04-05&productgroup=${PRODUCTS.BS}`, async () => {
-    const { status, body } = await api.get(`/exposure-period?startdate=2017-03-05&enddate=2017-04-05&productgroup=${PRODUCTS.BS}`);
+    const { status, body } = await api.get(`/api/v1/exposure-period?startdate=2017-03-05&enddate=2017-04-05&productgroup=${PRODUCTS.BS}`);
     expect(status).toBe(200);
     expect(body.exposurePeriod).toBe(2);
   });
@@ -93,7 +92,7 @@ describe('Exposure period', () => {
   // Input error handling checks
 
   it('GET /exposure-period', async () => {
-    const { status, body } = await api.get(`/exposure-period`);
+    const { status, body } = await api.get(`/api/v1/exposure-period`);
     expect(status).toBe(400);
     expect(body.message).toContain('startdate must be a valid ISO 8601 date string');
     expect(body.message).toContain('enddate must be a valid ISO 8601 date string');
@@ -102,7 +101,7 @@ describe('Exposure period', () => {
   });
 
   it('Should fail Feb 29 and Feb 30 - GET /exposure-period?startdate=2017-02-29&enddate=2017-02-30&productgroup=test', async () => {
-    const { status, body } = await api.get('/exposure-period?startdate=2017-02-29&enddate=2017-02-30&productgroup=test');
+    const { status, body } = await api.get('/api/v1/exposure-period?startdate=2017-02-29&enddate=2017-02-30&productgroup=test');
     expect(status).toBe(400);
     expect(body.message).toContain('startdate must be a valid ISO 8601 date string');
     expect(body.message).toContain('enddate must be a valid ISO 8601 date string');
@@ -110,7 +109,7 @@ describe('Exposure period', () => {
   });
 
   it('GET /exposure-period?startdate=2017-01-32&enddate=2017-02-32&productgroup=test', async () => {
-    const { status, body } = await api.get('/exposure-period?startdate=2017-01-32&enddate=2017-02-32&productgroup=test');
+    const { status, body } = await api.get('/api/v1/exposure-period?startdate=2017-01-32&enddate=2017-02-32&productgroup=test');
     expect(status).toBe(400);
     expect(body.message).toContain('startdate must be a valid ISO 8601 date string');
     expect(body.message).toContain('enddate must be a valid ISO 8601 date string');
@@ -118,7 +117,7 @@ describe('Exposure period', () => {
   });
 
   it('GET /exposure-period?startdate=null&enddate=null&productgroup=null', async () => {
-    const { status, body } = await api.get('/exposure-period?startdate=null&enddate=null&productgroup=null');
+    const { status, body } = await api.get('/api/v1/exposure-period?startdate=null&enddate=null&productgroup=null');
     expect(status).toBe(400);
     expect(body.message).toContain('startdate must be a valid ISO 8601 date string');
     expect(body.message).toContain('enddate must be a valid ISO 8601 date string');
@@ -126,7 +125,7 @@ describe('Exposure period', () => {
   });
 
   it('GET /exposure-period?startdate=undefined&enddate=undefined&productgroup=undefined', async () => {
-    const { status, body } = await api.get('/exposure-period?startdate=undefined&enddate=undefined&productgroup=undefined');
+    const { status, body } = await api.get('/api/v1/exposure-period?startdate=undefined&enddate=undefined&productgroup=undefined');
     expect(status).toBe(400);
     expect(body.message).toContain('startdate must be a valid ISO 8601 date string');
     expect(body.message).toContain('enddate must be a valid ISO 8601 date string');
@@ -134,12 +133,8 @@ describe('Exposure period', () => {
   });
 
   it('GET /exposure-period?startdate=2017-03-05&enddate=2017-04-05&productgroup=new', async () => {
-    const { status, body } = await api.get('/exposure-period?startdate=2017-03-05&enddate=2017-04-05&productgroup=new');
+    const { status, body } = await api.get('/api/v1/exposure-period?startdate=2017-03-05&enddate=2017-04-05&productgroup=new');
     expect(status).toBe(400);
     expect(body.message).toContain('productgroup must be one of the following values: EW, BS');
   });
-
-  afterAll(async () => {
-    await app.close();
-  });
 });
diff --git a/test/interest-rates/interest-rates.api-test.ts b/test/interest-rates/interest-rates.api-test.ts
index 4831efde..2d63091d 100644
--- a/test/interest-rates/interest-rates.api-test.ts
+++ b/test/interest-rates/interest-rates.api-test.ts
@@ -1,19 +1,18 @@
-import { INestApplication } from '@nestjs/common';
-
-import { Api } from '../api';
-import { CreateApp } from '../createApp';
+import { Api } from '@ukef-test/support/api';
 
 describe('Interest rates', () => {
-  let app: INestApplication;
   let api: Api;
 
   beforeAll(async () => {
-    app = await new CreateApp().init();
-    api = new Api(app.getHttpServer());
+    api = await Api.create();
+  });
+
+  afterAll(async () => {
+    await api.destroy();
   });
 
   it(`GET /interest-rates`, async () => {
-    const { status, body } = await api.get('/interest-rates');
+    const { status, body } = await api.get('/api/v1/interest-rates');
 
     expect(status).toBe(200);
     expect(body).toEqual(
@@ -35,8 +34,4 @@ describe('Interest rates', () => {
       ]),
     );
   });
-
-  afterAll(async () => {
-    await app.close();
-  });
 });
diff --git a/test/markets/markets.api-test.ts b/test/markets/markets.api-test.ts
index 3a61a2dd..1443fb20 100644
--- a/test/markets/markets.api-test.ts
+++ b/test/markets/markets.api-test.ts
@@ -1,11 +1,8 @@
-import { INestApplication } from '@nestjs/common';
+import { Api } from '@ukef-test/support/api';
 
-import { Api } from '../api';
-import { CreateApp } from '../createApp';
 import { TEST_CONSTANTS } from '../test-constants';
 
 describe('Markets', () => {
-  let app: INestApplication;
   let api: Api;
 
   const marketSchema = {
@@ -30,19 +27,22 @@ describe('Markets', () => {
   };
 
   beforeAll(async () => {
-    app = await new CreateApp().init();
-    api = new Api(app.getHttpServer());
+    api = await Api.create();
+  });
+
+  afterAll(async () => {
+    await api.destroy();
   });
 
   it(`GET /markets`, async () => {
-    const { status, body } = await api.get('/markets');
+    const { status, body } = await api.get('/api/v1/markets');
 
     expect(status).toBe(200);
     expect(body).toEqual(expect.arrayContaining([expect.objectContaining(marketSchema)]));
   });
 
   it(`GET /markets?active=Y`, async () => {
-    const { status, body } = await api.get('/markets?active=Y');
+    const { status, body } = await api.get('/api/v1/markets?active=Y');
 
     expect(status).toBe(200);
     expect(body).toEqual(
@@ -56,7 +56,7 @@ describe('Markets', () => {
   });
 
   it(`GET /markets?active=N`, async () => {
-    const { status, body } = await api.get('/markets?active=N');
+    const { status, body } = await api.get('/api/v1/markets?active=N');
 
     expect(status).toBe(200);
     expect(body).toEqual(
@@ -70,15 +70,15 @@ describe('Markets', () => {
   });
 
   it(`returns more results from GET /markets?active=Y than GET /markets?active=N`, async () => {
-    const responseActive = await api.get('/markets?active=Y');
-    const responseDisabled = await api.get('/markets?active=N');
+    const responseActive = await api.get('/api/v1/markets?active=Y');
+    const responseDisabled = await api.get('/api/v1/markets?active=N');
 
     // We expect more active markets than disabled
     expect(responseActive.body.length).toBeGreaterThan(responseDisabled.body.length);
   });
 
   it(`GET /markets?active=something-else`, async () => {
-    const { status, body } = await api.get('/markets?active=something-else');
+    const { status, body } = await api.get('/api/v1/markets?active=something-else');
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -86,7 +86,7 @@ describe('Markets', () => {
   });
 
   it(`GET /markets?active=`, async () => {
-    const { status, body } = await api.get('/markets?active=');
+    const { status, body } = await api.get('/api/v1/markets?active=');
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -94,7 +94,7 @@ describe('Markets', () => {
   });
 
   it(`GET /markets?active=null`, async () => {
-    const { status, body } = await api.get('/markets?active=null');
+    const { status, body } = await api.get('/api/v1/markets?active=null');
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -102,7 +102,7 @@ describe('Markets', () => {
   });
 
   it(`GET /markets?active=undefined`, async () => {
-    const { status, body } = await api.get('/markets?active=undefined');
+    const { status, body } = await api.get('/api/v1/markets?active=undefined');
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -112,7 +112,7 @@ describe('Markets', () => {
   // Testing "search" query parameter
 
   it(`GET /markets?search=Aus`, async () => {
-    const { status, body } = await api.get('/markets?search=Aus');
+    const { status, body } = await api.get('/api/v1/markets?search=Aus');
 
     expect(status).toBe(200);
     expect(body).toEqual(expect.arrayContaining([expect.objectContaining(marketSchema)]));
@@ -120,7 +120,7 @@ describe('Markets', () => {
   });
 
   it(`GET /markets?search=AUT`, async () => {
-    const { status, body } = await api.get('/markets?search=AUT');
+    const { status, body } = await api.get('/api/v1/markets?search=AUT');
 
     expect(status).toBe(200);
 
@@ -130,8 +130,8 @@ describe('Markets', () => {
   });
 
   it(`test that query param search is not case sensitive`, async () => {
-    const { status, body } = await api.get('/markets?search=Aus');
-    const lowerCaseResponse = await api.get('/markets?search=aus');
+    const { status, body } = await api.get('/api/v1/markets?search=Aus');
+    const lowerCaseResponse = await api.get('/api/v1/markets?search=aus');
 
     expect(status).toBe(200);
     expect(lowerCaseResponse.status).toBe(200);
@@ -140,55 +140,51 @@ describe('Markets', () => {
   });
 
   it(`GET /markets?active=Y&search=Aus`, async () => {
-    const { status, body } = await api.get('/markets?active=Y&search=Aus');
+    const { status, body } = await api.get('/api/v1/markets?active=Y&search=Aus');
 
     expect(status).toBe(200);
     expect(body).toEqual(expect.arrayContaining([expect.objectContaining(marketSchema)]));
   });
 
   it(`GET /markets?active=N&search=Aus`, async () => {
-    const { status, body } = await api.get('/markets?active=N&search=Aus');
+    const { status, body } = await api.get('/api/v1/markets?active=N&search=Aus');
 
     expect(status).toBe(200);
     expect(body).toEqual([]);
   });
 
   it(`GET /markets?search=undefined`, async () => {
-    const { status, body } = await api.get('/markets?search=undefined');
+    const { status, body } = await api.get('/api/v1/markets?search=undefined');
 
     expect(status).toBe(200);
     expect(body).toEqual([]);
   });
 
   it(`GET /markets?search=null`, async () => {
-    const { status, body } = await api.get('/markets?search=null');
+    const { status, body } = await api.get('/api/v1/markets?search=null');
 
     expect(status).toBe(200);
     expect(body).toEqual([]);
   });
 
   it(`GET /markets?search=`, async () => {
-    const { status, body } = await api.get('/markets?search=');
+    const { status, body } = await api.get('/api/v1/markets?search=');
 
     expect(status).toBe(200);
     expect(body).toEqual(expect.arrayContaining([expect.objectContaining(marketSchema)]));
   });
 
   it(`GET /markets?search=${TEST_CONSTANTS.SPECIAL_CHARACTERS}`, async () => {
-    const { status, body } = await api.get(`/markets?search=${TEST_CONSTANTS.SPECIAL_CHARACTERS}`);
+    const { status, body } = await api.get(`/api/v1/markets?search=${TEST_CONSTANTS.SPECIAL_CHARACTERS}`);
 
     expect(status).toBe(200);
     expect(body).toEqual([]);
   });
 
   it(`GET /markets?search=%20%20%20`, async () => {
-    const { status, body } = await api.get('/markets?search=%20%20%20');
+    const { status, body } = await api.get('/api/v1/markets?search=%20%20%20');
 
     expect(status).toBe(200);
     expect(body).toEqual([]);
   });
-
-  afterAll(async () => {
-    await app.close();
-  });
 });
diff --git a/test/numbers/numbers.api-test.ts b/test/numbers/numbers.api-test.ts
index 25cb95e4..a0128a1b 100644
--- a/test/numbers/numbers.api-test.ts
+++ b/test/numbers/numbers.api-test.ts
@@ -1,19 +1,18 @@
-import { INestApplication } from '@nestjs/common';
-
-import { Api } from '../api';
-import { CreateApp } from '../createApp';
+import { Api } from '@ukef-test/support/api';
 
 describe('Numbers', () => {
-  let app: INestApplication;
   let api: Api;
 
   beforeAll(async () => {
-    app = await new CreateApp().init();
-    api = new Api(app.getHttpServer());
+    api = await Api.create();
+  });
+
+  afterAll(async () => {
+    await api.destroy();
   });
 
   it(`GET /numbers?type=1&ukefId=0010581069`, async () => {
-    const { status, body } = await api.get('/numbers?type=1&ukefId=0010581069');
+    const { status, body } = await api.get('/api/v1/numbers?type=1&ukefId=0010581069');
 
     expect(status).toBe(404);
     expect(body.error).toMatch('Not Found');
@@ -31,25 +30,25 @@ describe('Numbers', () => {
       },
     ];
     // Generate
-    const postResponse = await api.post(postNumbersPayload).to('/numbers');
+    const postResponse = await api.post('/api/v1/numbers', postNumbersPayload);
 
     expect(postResponse.status).toBe(201);
 
     // Test
-    const getResponse = await api.get('/numbers?type=' + postResponse.body[0].type + '&ukefId=' + postResponse.body[0].maskedId);
+    const getResponse = await api.get('/api/v1/numbers?type=' + postResponse.body[0].type + '&ukefId=' + postResponse.body[0].maskedId);
 
     expect(getResponse.status).toBe(200);
     expect(postResponse.body[0]).toEqual(getResponse.body);
   });
 
   it(`GET /numbers?type=2&ukefId=0030581069`, async () => {
-    const { status } = await api.get('/numbers?type=2&ukefId=0030581069');
+    const { status } = await api.get('/api/v1/numbers?type=2&ukefId=0030581069');
 
     expect(status).toBe(404);
   });
 
   it(`GET /numbers?type=a&ukefId=a`, async () => {
-    const { status, body } = await api.get('/numbers?type=a&ukefId=a');
+    const { status, body } = await api.get('/api/v1/numbers?type=a&ukefId=a');
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -58,7 +57,7 @@ describe('Numbers', () => {
   });
 
   it(`GET /numbers?type=null&ukefId=null`, async () => {
-    const { status, body } = await api.get('/numbers?type=null&ukefId=null');
+    const { status, body } = await api.get('/api/v1/numbers?type=null&ukefId=null');
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -67,7 +66,7 @@ describe('Numbers', () => {
   });
 
   it(`GET /numbers?type=undefined&ukefId=undefined`, async () => {
-    const { status, body } = await api.get('/numbers?type=undefined&ukefId=undefined');
+    const { status, body } = await api.get('/api/v1/numbers?type=undefined&ukefId=undefined');
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -76,7 +75,7 @@ describe('Numbers', () => {
   });
 
   it(`GET /numbers?type=a&ukefId=0030581069`, async () => {
-    const { status, body } = await api.get('/numbers?type=a&ukefId=0030581069');
+    const { status, body } = await api.get('/api/v1/numbers?type=a&ukefId=0030581069');
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -84,7 +83,7 @@ describe('Numbers', () => {
   });
 
   it(`GET /numbers?type=3&ukefId=0030581069`, async () => {
-    const { status, body } = await api.get('/numbers?type=3&ukefId=0030581069');
+    const { status, body } = await api.get('/api/v1/numbers?type=3&ukefId=0030581069');
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -99,7 +98,7 @@ describe('Numbers', () => {
         requestingSystem: 'Jest 1 - Deal',
       },
     ];
-    const { status, body } = await api.post(payload).to('/numbers');
+    const { status, body } = await api.post('/api/v1/numbers', payload);
 
     expect(status).toBe(201);
     expect(body).toHaveLength(1);
@@ -119,7 +118,7 @@ describe('Numbers', () => {
         requestingSystem: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mau',
       },
     ];
-    const { status, body } = await api.post(payload).to('/numbers');
+    const { status, body } = await api.post('/api/v1/numbers', payload);
 
     expect(status).toBe(201);
     expect(body).toHaveLength(1);
@@ -139,7 +138,7 @@ describe('Numbers', () => {
         requestingSystem: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris ac magna ipsum',
       },
     ];
-    const { status, body } = await api.post(payload).to('/numbers');
+    const { status, body } = await api.post('/api/v1/numbers', payload);
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -149,7 +148,7 @@ describe('Numbers', () => {
 
   it(`POST /numbers single, missing fields`, async () => {
     const payload = [{}];
-    const { status, body } = await api.post(payload).to('/numbers');
+    const { status, body } = await api.post('/api/v1/numbers', payload);
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -160,7 +159,7 @@ describe('Numbers', () => {
 
   it(`POST /numbers single, empty payload`, async () => {
     const payload = '';
-    const { status, body } = await api.post(payload).to('/numbers');
+    const { status, body } = await api.post('/api/v1/numbers', payload);
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -169,7 +168,7 @@ describe('Numbers', () => {
 
   it(`POST /numbers single, empty array`, async () => {
     const payload = [];
-    const { status, body } = await api.post(payload).to('/numbers');
+    const { status, body } = await api.post('/api/v1/numbers', payload);
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -178,7 +177,7 @@ describe('Numbers', () => {
 
   it(`POST /numbers single, not parsable array`, async () => {
     const payload = '[]';
-    const { status, body } = await api.post(payload).to('/numbers');
+    const { status, body } = await api.post('/api/v1/numbers', payload);
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -187,7 +186,7 @@ describe('Numbers', () => {
 
   it(`POST /numbers single, bad json`, async () => {
     const payload = 'asd';
-    const { status, body } = await api.post(payload).to('/numbers');
+    const { status, body } = await api.post('/api/v1/numbers', payload);
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -217,7 +216,7 @@ describe('Numbers', () => {
         requestingSystem: 'Jest 4 - Covenant',
       },
     ];
-    const { status, body } = await api.post(payload).to('/numbers');
+    const { status, body } = await api.post('/api/v1/numbers', payload);
 
     expect(status).toBe(201);
     expect(body).toHaveLength(4);
@@ -295,7 +294,7 @@ describe('Numbers', () => {
         requestingSystem: 'Jest 4 - Party',
       },
     ];
-    const { status, body } = await api.post(payload).to('/numbers');
+    const { status, body } = await api.post('/api/v1/numbers', payload);
 
     expect(status).toBe(201);
     expect(body).toHaveLength(payload.length);
@@ -314,8 +313,4 @@ describe('Numbers', () => {
       return previousValues;
     }, Object.create(null));
   });
-
-  afterAll(async () => {
-    await app.close();
-  });
 });
diff --git a/test/premium-schedules/premium-schedules.api-test.ts b/test/premium-schedules/premium-schedules.api-test.ts
index a9b75486..d259c96b 100644
--- a/test/premium-schedules/premium-schedules.api-test.ts
+++ b/test/premium-schedules/premium-schedules.api-test.ts
@@ -1,14 +1,10 @@
-import { INestApplication } from '@nestjs/common';
 import { PRODUCTS } from '@ukef/constants';
+import { Api } from '@ukef-test/support/api';
 import Chance from 'chance';
 
-import { Api } from '../api';
-import { CreateApp } from '../createApp';
-
 const chance = new Chance();
 
 describe('Premium schedules', () => {
-  let app: INestApplication;
   let api: Api;
 
   const premiumScheduleSchema = {
@@ -28,12 +24,15 @@ describe('Premium schedules', () => {
   };
 
   beforeAll(async () => {
-    app = await new CreateApp().init();
-    api = new Api(app.getHttpServer());
+    api = await Api.create();
+  });
+
+  afterAll(async () => {
+    await api.destroy();
   });
 
   it('GET /premium/segments/12345678', async () => {
-    const { status, body } = await api.get('/premium/segments/12345678');
+    const { status, body } = await api.get('/api/v1/premium/segments/12345678');
 
     // Not generated yet.
     expect(status).toBe(404);
@@ -60,7 +59,7 @@ describe('Premium schedules', () => {
         maximumLiability: 40000,
       },
     ];
-    const postResponse = await api.post(createSchedules).to('/premium/schedule');
+    const postResponse = await api.post('/api/v1/premium/schedule', createSchedules);
 
     expect(postResponse.status).toBe(201);
     expect(postResponse.body).toHaveLength(1);
@@ -85,12 +84,12 @@ describe('Premium schedules', () => {
       },
     ];
     // Generate
-    const postResponse = await api.post(createSchedules).to('/premium/schedule');
+    const postResponse = await api.post('/api/v1/premium/schedule', createSchedules);
 
     expect(postResponse.status).toBe(201);
 
     // Test
-    const getResponse = await api.get('/premium/segments/' + postResponse.body[0].facilityURN);
+    const getResponse = await api.get('/api/v1/premium/segments/' + postResponse.body[0].facilityURN);
 
     expect(getResponse.status).toBe(200);
     expect(getResponse.body).toHaveLength(16);
@@ -116,7 +115,7 @@ describe('Premium schedules', () => {
         maximumLiability: 40000,
       },
     ];
-    const postResponse = await api.post(createSchedules).to('/premium/schedule');
+    const postResponse = await api.post('/api/v1/premium/schedule', createSchedules);
 
     expect(postResponse.status).toBe(201);
     expect(postResponse.body).toHaveLength(1);
@@ -140,7 +139,7 @@ describe('Premium schedules', () => {
         maximumLiability: 40000,
       },
     ];
-    const postResponse = await api.post(createSchedules).to('/premium/schedule');
+    const postResponse = await api.post('/api/v1/premium/schedule', createSchedules);
 
     expect(postResponse.status).toBe(201);
     expect(postResponse.body).toHaveLength(1);
@@ -164,7 +163,7 @@ describe('Premium schedules', () => {
         maximumLiability: 40000,
       },
     ];
-    const postResponse = await api.post(createSchedules).to('/premium/schedule');
+    const postResponse = await api.post('/api/v1/premium/schedule', createSchedules);
 
     expect(postResponse.status).toBe(201);
     expect(postResponse.body).toHaveLength(1);
@@ -188,7 +187,7 @@ describe('Premium schedules', () => {
         maximumLiability: 40000,
       },
     ];
-    const { status, body } = await api.post(createSchedules).to('/premium/schedule');
+    const { status, body } = await api.post('/api/v1/premium/schedule', createSchedules);
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -213,7 +212,7 @@ describe('Premium schedules', () => {
       },
     ];
 
-    const { status, body } = await api.post(createSchedules).to('/premium/schedule');
+    const { status, body } = await api.post('/api/v1/premium/schedule', createSchedules);
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -223,20 +222,20 @@ describe('Premium schedules', () => {
   });
 
   it('GET /premium/segments/null', async () => {
-    const { status, body } = await api.get('/premium/segments/null');
+    const { status, body } = await api.get('/api/v1/premium/segments/null');
     expect(status).toBe(400);
     expect(body.message).toContain('facilityId must match /^\\d{8,10}$/ regular expression');
   });
 
   it('GET /premium/segments/undefined', async () => {
-    const { status, body } = await api.get('/premium/segments/undefined');
+    const { status, body } = await api.get('/api/v1/premium/segments/undefined');
     expect(status).toBe(400);
     expect(body.message).toContain('facilityId must match /^\\d{8,10}$/ regular expression');
   });
 
   it('POST /premium/schedule, empty array', async () => {
     const payload = [];
-    const { status, body } = await api.post(payload).to('/premium/schedule');
+    const { status, body } = await api.post('/api/v1/premium/schedule', payload);
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -245,7 +244,7 @@ describe('Premium schedules', () => {
 
   it('POST /premium/schedule, not parsable array', async () => {
     const payload = '[]';
-    const { status, body } = await api.post(payload).to('/premium/schedule');
+    const { status, body } = await api.post('/api/v1/premium/schedule', payload);
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -254,7 +253,7 @@ describe('Premium schedules', () => {
 
   it('POST /premium/schedule, bad json', async () => {
     const payload = 'asd';
-    const { status, body } = await api.post(payload).to('/premium/schedule');
+    const { status, body } = await api.post('/api/v1/premium/schedule', payload);
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -263,7 +262,7 @@ describe('Premium schedules', () => {
 
   it('POST /premium/schedule, field validation', async () => {
     const payload = [{}];
-    const { status, body } = await api.post(payload).to('/premium/schedule');
+    const { status, body } = await api.post('/api/v1/premium/schedule', payload);
 
     expect(status).toBe(400);
     expect(body.error).toMatch('Bad Request');
@@ -291,8 +290,4 @@ describe('Premium schedules', () => {
     expect(body.message).toContain('maximumLiability should not be empty');
     expect(body.message).toContain('maximumLiability must be a number conforming to the specified constraints');
   });
-
-  afterAll(async () => {
-    await app.close();
-  });
 });
diff --git a/test/sector-industries/sector-industries.api-test.ts b/test/sector-industries/sector-industries.api-test.ts
index d9b62091..bedf4145 100644
--- a/test/sector-industries/sector-industries.api-test.ts
+++ b/test/sector-industries/sector-industries.api-test.ts
@@ -1,10 +1,6 @@
-import { INestApplication } from '@nestjs/common';
-
-import { Api } from '../api';
-import { CreateApp } from '../createApp';
+import { Api } from '@ukef-test/support/api';
 
 describe('Sector industries', () => {
-  let app: INestApplication;
   let api: Api;
 
   const sectorIndustriesSchema = {
@@ -25,63 +21,62 @@ describe('Sector industries', () => {
   };
 
   beforeAll(async () => {
-    app = await new CreateApp().init();
-    api = new Api(app.getHttpServer());
+    api = await Api.create();
+  });
+
+  afterAll(async () => {
+    await api.destroy();
   });
 
   it(`GET /sector-industries`, async () => {
-    const { status, body } = await api.get('/sector-industries');
+    const { status, body } = await api.get('/api/v1/sector-industries');
     expect(status).toBe(200);
     expect(body).toEqual(expect.arrayContaining([expect.objectContaining(sectorIndustriesSchema)]));
   });
 
   it(`GET /sector-industries?ukefSectorId=1001`, async () => {
-    const { status, body } = await api.get('/sector-industries?ukefSectorId=1001');
+    const { status, body } = await api.get('/api/v1/sector-industries?ukefSectorId=1001');
     expect(status).toBe(200);
     expect(body).toEqual(expect.arrayContaining([expect.objectContaining(sectorIndustriesSchema)]));
     expect(body.length).toBeGreaterThan(1);
   });
 
   it(`GET /sector-industries?ukefIndustryId=01120`, async () => {
-    const { status, body } = await api.get('/sector-industries?ukefIndustryId=01120');
+    const { status, body } = await api.get('/api/v1/sector-industries?ukefIndustryId=01120');
     expect(status).toBe(200);
     expect(body).toEqual(expect.arrayContaining([expect.objectContaining(sectorIndustriesSchema)]));
     expect(body).toHaveLength(1);
   });
 
   it(`GET /sector-industries?ukefSectorId=1001&ukefIndustryId=01120`, async () => {
-    const { status, body } = await api.get('/sector-industries?ukefSectorId=1001&ukefIndustryId=01120');
+    const { status, body } = await api.get('/api/v1/sector-industries?ukefSectorId=1001&ukefIndustryId=01120');
     expect(status).toBe(200);
     expect(body).toHaveLength(1);
   });
 
   it(`GET /sector-industries?ukefSectorId=1234`, async () => {
-    const { status } = await api.get('/sector-industries?ukefSectorId=1234');
+    const { status } = await api.get('/api/v1/sector-industries?ukefSectorId=1234');
     expect(status).toBe(404);
   });
 
   it(`GET /sector-industries?ukefSectorId=a&ukefIndustryId=a`, async () => {
-    const { status, body } = await api.get('/sector-industries?ukefSectorId=a&ukefIndustryId=a');
+    const { status, body } = await api.get('/api/v1/sector-industries?ukefSectorId=a&ukefIndustryId=a');
     expect(status).toBe(400);
     expect(body.message).toContain('ukefSectorId must match /^\\d{4}$/ regular expression');
     expect(body.message).toContain('ukefIndustryId must match /^\\d{5}$/ regular expression');
   });
 
   it(`GET /sector-industries?ukefSectorId=null&ukefIndustryId=null`, async () => {
-    const { status, body } = await api.get('/sector-industries?ukefSectorId=null&ukefIndustryId=null');
+    const { status, body } = await api.get('/api/v1/sector-industries?ukefSectorId=null&ukefIndustryId=null');
     expect(status).toBe(400);
     expect(body.message).toContain('ukefSectorId must match /^\\d{4}$/ regular expression');
     expect(body.message).toContain('ukefIndustryId must match /^\\d{5}$/ regular expression');
   });
 
   it(`GET /sector-industries?ukefSectorId=undefined&ukefIndustryId=undefined`, async () => {
-    const { status, body } = await api.get('/sector-industries?ukefSectorId=undefined&ukefIndustryId=undefined');
+    const { status, body } = await api.get('/api/v1/sector-industries?ukefSectorId=undefined&ukefIndustryId=undefined');
     expect(status).toBe(400);
     expect(body.message).toContain('ukefSectorId must match /^\\d{4}$/ regular expression');
     expect(body.message).toContain('ukefIndustryId must match /^\\d{5}$/ regular expression');
   });
-
-  afterAll(async () => {
-    await app.close();
-  });
 });
diff --git a/test/support/api.ts b/test/support/api.ts
index dc03f634..f86625d2 100644
--- a/test/support/api.ts
+++ b/test/support/api.ts
@@ -16,6 +16,10 @@ export class Api {
     return this.request().get(url).set(this.getValidAuthHeader());
   }
 
+  post(url: string, body: string | object): request.Test {
+    return this.request().post(url).send(body).set(this.getValidAuthHeader());
+  }
+
   getWithoutAuth(url: string, strategy?: string, key?: string): request.Test {
     const query = this.request().get(url);
     return this.setQueryWithAuthStrategyIfPresent(query, strategy, key);
diff --git a/test/yield-rates/yield-rates.api-test.ts b/test/yield-rates/yield-rates.api-test.ts
index 0386c8db..756dbfe8 100644
--- a/test/yield-rates/yield-rates.api-test.ts
+++ b/test/yield-rates/yield-rates.api-test.ts
@@ -1,16 +1,15 @@
-import { INestApplication } from '@nestjs/common';
 import { DATE } from '@ukef/constants';
-
-import { Api } from '../api';
-import { CreateApp } from '../createApp';
+import { Api } from '@ukef-test/support/api';
 
 describe('Interest rates', () => {
-  let app: INestApplication;
-  let api: Api;
+  let api;
 
   beforeAll(async () => {
-    app = await new CreateApp().init();
-    api = new Api(app.getHttpServer());
+    api = await Api.create();
+  });
+
+  afterAll(async () => {
+    await api.destroy();
   });
 
   const yieldRateSchema = {
@@ -31,13 +30,13 @@ describe('Interest rates', () => {
   };
 
   it(`GET /yield-rates`, async () => {
-    const { status, body } = await api.get('/yield-rates');
+    const { status, body } = await api.get('/api/v1/yield-rates');
     expect(status).toBe(200);
     expect(body).toEqual(expect.arrayContaining([expect.objectContaining({ ...yieldRateSchema, effectiveTo: DATE.MAXIMUM_TIMEZONE_LIMIT })]));
   });
 
   it(`GET /yield-rates?searchDate=2023-03-02`, async () => {
-    const { status, body } = await api.get('/yield-rates?searchDate=2023-03-02');
+    const { status, body } = await api.get('/api/v1/yield-rates?searchDate=2023-03-02');
     expect(status).toBe(200);
     expect(body).toEqual(expect.arrayContaining([expect.objectContaining(yieldRateSchema)]));
 
@@ -47,78 +46,74 @@ describe('Interest rates', () => {
 
   // UKEF at the moment has yield rates since 2010-03-15, maybe some old data will be retired in future.
   it(`returns 404 for any date past 2010-03-15 where the yield data does not exist`, async () => {
-    const { status } = await api.get('/yield-rates?searchDate=2010-03-14');
+    const { status } = await api.get('/api/v1/yield-rates?searchDate=2010-03-14');
     expect(status).toBe(404);
   });
 
   // Current yield rates have effective date till 9999-12-31, so 9999-12-30 is max date with results.
   it(`GET /yield-rates?searchDate=9999-12-30`, async () => {
-    const { status, body } = await api.get('/yield-rates?searchDate=9999-12-30');
+    const { status, body } = await api.get('/api/v1/yield-rates?searchDate=9999-12-30');
     expect(status).toBe(200);
     expect(body).toEqual(expect.arrayContaining([expect.objectContaining({ ...yieldRateSchema, effectiveTo: DATE.MAXIMUM_TIMEZONE_LIMIT })]));
   });
 
   // Current yield rates have effective date till 9999-12-31, so no rates for this max date.
   it(`returns 404 for GET /yield-rates?searchDate=9999-12-31`, async () => {
-    const { status } = await api.get('/yield-rates?searchDate=9999-12-31');
+    const { status } = await api.get('/api/v1/yield-rates?searchDate=9999-12-31');
     expect(status).toBe(404);
   });
 
   it(`returns 400 for GET /yield-rates?searchDate=2023-03-02T16:29:04.027Z`, async () => {
-    const { status, body } = await api.get('/yield-rates?searchDate=2023-03-02T16:29:04.027Z');
+    const { status, body } = await api.get('/api/v1/yield-rates?searchDate=2023-03-02T16:29:04.027Z');
     expect(status).toBe(400);
     expect(body.message).toContain('searchDate should use format YYYY-MM-DD');
   });
 
   it(`returns 400 for GET /yield-rates?searchDate=null`, async () => {
-    const { status, body } = await api.get('/yield-rates?searchDate=null');
+    const { status, body } = await api.get('/api/v1/yield-rates?searchDate=null');
     expect(status).toBe(400);
     expect(body.message).toContain('searchDate must be a valid ISO 8601 date string');
   });
 
   it(`returns 400 for GET /yield-rates?searchDate=undefined`, async () => {
-    const { status, body } = await api.get('/yield-rates?searchDate=undefined');
+    const { status, body } = await api.get('/api/v1/yield-rates?searchDate=undefined');
     expect(status).toBe(400);
     expect(body.message).toContain('searchDate must be a valid ISO 8601 date string');
   });
 
   it(`returns 400 for GET /yield-rates?searchDate=ABC`, async () => {
-    const { status, body } = await api.get('/yield-rates?searchDate=ABC');
+    const { status, body } = await api.get('/api/v1/yield-rates?searchDate=ABC');
     expect(status).toBe(400);
     expect(body.message).toContain('searchDate must be a valid ISO 8601 date string');
   });
 
   it(`returns 400 for GET /yield-rates?searchDate=123`, async () => {
-    const { status, body } = await api.get('/yield-rates?searchDate=123');
+    const { status, body } = await api.get('/api/v1/yield-rates?searchDate=123');
     expect(status).toBe(400);
     expect(body.message).toContain('searchDate must be a valid ISO 8601 date string');
   });
 
   it(`returns 400 for GET /yield-rates?searchDate=!"£!"£`, async () => {
-    const { status, body } = await api.get('/yield-rates?searchDate=!"£!"£');
+    const { status, body } = await api.get('/api/v1/yield-rates?searchDate=!"£!"£');
     expect(status).toBe(400);
     expect(body.message).toContain('searchDate must be a valid ISO 8601 date string');
   });
 
   it(`returns 400 for GET /yield-rates?searchDate=A%20£`, async () => {
-    const { status, body } = await api.get('/yield-rates?searchDate=A%20£');
+    const { status, body } = await api.get('/api/v1/yield-rates?searchDate=A%20£');
     expect(status).toBe(400);
     expect(body.message).toContain('searchDate must be a valid ISO 8601 date string');
   });
 
   it(`returns 400 for GET /yield-rates?searchDate=++`, async () => {
-    const { status, body } = await api.get('/yield-rates?searchDate=++');
+    const { status, body } = await api.get('/api/v1/yield-rates?searchDate=++');
     expect(status).toBe(400);
     expect(body.message).toContain('searchDate must be a valid ISO 8601 date string');
   });
 
   it(`returns 400 for GET /yield-rates?searchDate=0000-00-00`, async () => {
-    const { status, body } = await api.get('/yield-rates?searchDate=0000-00-00');
+    const { status, body } = await api.get('/api/v1/yield-rates?searchDate=0000-00-00');
     expect(status).toBe(400);
     expect(body.message).toContain('searchDate must be a valid ISO 8601 date string');
   });
-
-  afterAll(async () => {
-    await app.close();
-  });
 });

From 837a48254d14c6369f1f394aeb2d0646bdeb2ed9 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Mon, 15 Apr 2024 10:25:00 +0100
Subject: [PATCH 13/56] feat(DTFS2-7052): tests for ordnance survey API
 endpoint

---
 .../geospatial/geospatial.controller.test.ts  | 62 +++++++++++++++++
 .../geospatial/geospatial.controller.ts       |  7 +-
 .../geospatial/geospatial.service.test.ts     | 67 +++++++++++++++++++
 src/modules/geospatial/geospatial.service.ts  |  2 +-
 .../get-docs-yaml.api-test.ts.snap            | 11 ++-
 .../get-address-by-postcode.api-test.ts       |  7 +-
 .../get-geospatial-addresses-generator.ts     | 12 ++--
 7 files changed, 150 insertions(+), 18 deletions(-)
 create mode 100644 src/modules/geospatial/geospatial.controller.test.ts
 create mode 100644 src/modules/geospatial/geospatial.service.test.ts

diff --git a/src/modules/geospatial/geospatial.controller.test.ts b/src/modules/geospatial/geospatial.controller.test.ts
new file mode 100644
index 00000000..d60c0a83
--- /dev/null
+++ b/src/modules/geospatial/geospatial.controller.test.ts
@@ -0,0 +1,62 @@
+import { GEOSPATIAL } from '@ukef/constants';
+import { GetGeospatialAddressesGenerator } from '@ukef-test/support/generator/get-geospatial-addresses-generator';
+import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator';
+import { resetAllWhenMocks, when } from 'jest-when';
+
+import { GeospatialController } from './geospatial.controller';
+import { GeospatialService } from './geospatial.service';
+
+describe('GeospatialController', () => {
+  let geospatialServiceGetAddressesByPostcode: jest.Mock;
+
+  let controller: GeospatialController;
+
+  const valueGenerator = new RandomValueGenerator();
+  const { getAddressByPostcodeResponse, getAddressByPostcodeMultipleResponse } = new GetGeospatialAddressesGenerator(valueGenerator).generate({
+    numberToGenerate: 2,
+  });
+
+  beforeEach(() => {
+    resetAllWhenMocks();
+    const geospatialService = new GeospatialService(null);
+    geospatialServiceGetAddressesByPostcode = jest.fn();
+    geospatialService.getAddressesByPostcode = geospatialServiceGetAddressesByPostcode;
+
+    controller = new GeospatialController(geospatialService);
+  });
+
+  it('should be defined', () => {
+    expect(GeospatialController).toBeDefined();
+  });
+
+  describe('getAddressesByPostcode()', () => {
+    const postcode = GEOSPATIAL.EXAMPLES.POSTCODE;
+
+    it('returns address for postcode', async () => {
+      when(geospatialServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressByPostcodeResponse[0]);
+
+      const response = await controller.getAddressesByPostcode({ postcode });
+
+      expect(geospatialServiceGetAddressesByPostcode).toHaveBeenCalled();
+      expect(response).toEqual(getAddressByPostcodeResponse[0]);
+    });
+
+    it('returns multiple addressess for postcode', async () => {
+      when(geospatialServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressByPostcodeMultipleResponse);
+
+      const response = await controller.getAddressesByPostcode({ postcode });
+
+      expect(geospatialServiceGetAddressesByPostcode).toHaveBeenCalled();
+      expect(response).toEqual(getAddressByPostcodeMultipleResponse);
+    });
+
+    it('returns empty response for postcode', async () => {
+      when(geospatialServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce([]);
+
+      const response = await controller.getAddressesByPostcode({ postcode });
+
+      expect(geospatialServiceGetAddressesByPostcode).toHaveBeenCalled();
+      expect(response).toEqual([]);
+    });
+  });
+});
diff --git a/src/modules/geospatial/geospatial.controller.ts b/src/modules/geospatial/geospatial.controller.ts
index 4d1580b7..50486d98 100644
--- a/src/modules/geospatial/geospatial.controller.ts
+++ b/src/modules/geospatial/geospatial.controller.ts
@@ -12,17 +12,18 @@ export class GeospatialController {
 
   @Get('addresses/postcode')
   @ApiOperation({
-    summary: "A search based on a property's postcode. Will accept a full postcode consisting of the area, district, sector and unit e.g. SO16 0AS.",
+    summary:
+      "A search based on a property's postcode. Will accept a full valid postcode. Returns addresses from Ordanance survey Delivery Point Address (DPA) system.",
   })
   @ApiResponse({
     status: 200,
-    description: 'Returns addresses from Ordanance survey Delivery Point Address (DPA) system.',
+    description: 'Returns simplified addresses that are ready to show to users.',
     type: [GetAddressesResponseItem],
   })
   @ApiNotFoundResponse({
     description: 'Customer not found.',
   })
-  getGeospatial(@Query() query: GetAddressByPostcodeQueryDto): Promise<GetAddressesResponse> {
+  getAddressesByPostcode(@Query() query: GetAddressByPostcodeQueryDto): Promise<GetAddressesResponse> {
     return this.geospatialService.getAddressesByPostcode(query.postcode);
   }
 }
diff --git a/src/modules/geospatial/geospatial.service.test.ts b/src/modules/geospatial/geospatial.service.test.ts
new file mode 100644
index 00000000..ec3165dd
--- /dev/null
+++ b/src/modules/geospatial/geospatial.service.test.ts
@@ -0,0 +1,67 @@
+import { ConfigService } from '@nestjs/config';
+import { OrdnanceSurveyService } from '@ukef/helper-modules/ordnance-survey/ordnance-survey.service';
+import { GetGeospatialAddressesGenerator } from '@ukef-test/support/generator/get-geospatial-addresses-generator';
+import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator';
+import { resetAllWhenMocks, when } from 'jest-when';
+
+import { GeospatialService } from './geospatial.service';
+
+jest.mock('@ukef/modules/informatica/informatica.service');
+
+describe('CustomerService', () => {
+  const valueGenerator = new RandomValueGenerator();
+
+  let service: GeospatialService;
+  let configServiceGet: jest.Mock;
+  let informaticaServiceGetAddressesByPostcode: jest.Mock;
+
+  beforeEach(() => {
+    const configService = new ConfigService();
+    configServiceGet = jest.fn().mockReturnValue({ key: valueGenerator.word() });
+    configService.get = configServiceGet;
+
+    informaticaServiceGetAddressesByPostcode = jest.fn();
+    const ordnanceSurveyService = new OrdnanceSurveyService(null, configService);
+    ordnanceSurveyService.getAddressesByPostcode = informaticaServiceGetAddressesByPostcode;
+    resetAllWhenMocks();
+
+    service = new GeospatialService(ordnanceSurveyService);
+  });
+
+  describe('getAddressesByPostcode', () => {
+    const {
+      getAddressByPostcodeResponse,
+      getAddressByPostcodeMultipleResponse,
+      getAddressOrdnanceSurveyResponse,
+      getAddressOrdnanceSurveyMultipleResponse,
+      getAddressOrdnanceSurveyEmptyResponse,
+    } = new GetGeospatialAddressesGenerator(valueGenerator).generate({
+      numberToGenerate: 2,
+    });
+    const postcode = getAddressByPostcodeResponse[0][0].postalCode;
+
+    it('returns address from the backend service', async () => {
+      when(informaticaServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressOrdnanceSurveyResponse[0]);
+
+      const response = await service.getAddressesByPostcode(postcode);
+
+      expect(response).toEqual(getAddressByPostcodeResponse[0]);
+    });
+
+    it('returns multiple addressess from the backend service', async () => {
+      when(informaticaServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressOrdnanceSurveyMultipleResponse);
+
+      const response = await service.getAddressesByPostcode(postcode);
+
+      expect(response).toEqual(getAddressByPostcodeMultipleResponse);
+    });
+
+    it('can handle empty backend response', async () => {
+      when(informaticaServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressOrdnanceSurveyEmptyResponse[0]);
+
+      const response = await service.getAddressesByPostcode(postcode);
+
+      expect(response).toEqual([]);
+    });
+  });
+});
diff --git a/src/modules/geospatial/geospatial.service.ts b/src/modules/geospatial/geospatial.service.ts
index e1cf93df..b3cb515d 100644
--- a/src/modules/geospatial/geospatial.service.ts
+++ b/src/modules/geospatial/geospatial.service.ts
@@ -27,7 +27,7 @@ export class GeospatialService {
         addressLine3: null,
         locality: item_data.POST_TOWN || null,
         postalCode: item_data.POSTCODE || null,
-        country: ENUMS.GEOSPATIAL_COUNTRIES[item_data.COUNTRY_CODE],
+        country: ENUMS.GEOSPATIAL_COUNTRIES[item_data.COUNTRY_CODE] || null,
       });
     });
 
diff --git a/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap b/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
index 77af87e5..b71b8f1e 100644
--- a/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
+++ b/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
@@ -488,10 +488,11 @@ paths:
         - yield-rates
   /api/v1/geospatial/addresses/postcode:
     get:
-      operationId: GeospatialController_getGeospatial
+      operationId: GeospatialController_getAddressesByPostcode
       summary: >-
-        A search based on a property's postcode. Will accept a full postcode
-        consisting of the area, district, sector and unit e.g. SO16 0AS.
+        A search based on a property's postcode. Will accept a full valid
+        postcode. Returns addresses from Ordanance survey Delivery Point Address
+        (DPA) system.
       parameters:
         - name: postcode
           required: true
@@ -502,9 +503,7 @@ paths:
             type: string
       responses:
         '200':
-          description: >-
-            Returns addresses from Ordanance survey Delivery Point Address (DPA)
-            system.
+          description: Returns simplified addresses that are ready to show to users.
           content:
             application/json:
               schema:
diff --git a/test/geospatial/get-address-by-postcode.api-test.ts b/test/geospatial/get-address-by-postcode.api-test.ts
index 770ef319..64f0283b 100644
--- a/test/geospatial/get-address-by-postcode.api-test.ts
+++ b/test/geospatial/get-address-by-postcode.api-test.ts
@@ -15,9 +15,10 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
     ordnanceSurveyPath,
     mdmPath,
     getAddressByPostcodeResponse,
+    getAddressByPostcodeMultipleResponse,
     getAddressOrdnanceSurveyResponse,
     getAddressOrdnanceSurveyEmptyResponse,
-    getAddressessOrdnanceSurveyResponse,
+    getAddressOrdnanceSurveyMultipleResponse,
     ordnanceSurveyAuthErrorResponse,
   } = new GetGeospatialAddressesGenerator(valueGenerator).generate({
     postcode: GEOSPATIAL.EXAMPLES.POSTCODE,
@@ -58,12 +59,12 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
   });
 
   it('returns a 200 response with the addresses if they are returned by Ordnance Survey API', async () => {
-    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(200, getAddressessOrdnanceSurveyResponse);
+    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(200, getAddressOrdnanceSurveyMultipleResponse);
 
     const { status, body } = await api.get(mdmPath[0]);
 
     expect(status).toBe(200);
-    expect(body).toStrictEqual([getAddressByPostcodeResponse[0][0], getAddressByPostcodeResponse[1][0]]);
+    expect(body).toStrictEqual(getAddressByPostcodeMultipleResponse);
   });
 
   it('returns a empty 200 response if Ordnance Survey API returns a 200 without results', async () => {
diff --git a/test/support/generator/get-geospatial-addresses-generator.ts b/test/support/generator/get-geospatial-addresses-generator.ts
index efabd55c..69320761 100644
--- a/test/support/generator/get-geospatial-addresses-generator.ts
+++ b/test/support/generator/get-geospatial-addresses-generator.ts
@@ -52,6 +52,8 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
       },
     ]);
 
+    const getAddressByPostcodeMultipleResponse = getAddressByPostcodeResponse.map((response) => response[0]);
+
     const getAddressOrdnanceSurveyResponse: GetAddressOrdnanceSurveyResponse[] = values.map((v) => ({
       header: {
         uri: 'test',
@@ -107,7 +109,7 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
       ],
     }));
 
-    const getAddressessOrdnanceSurveyResponse: GetAddressOrdnanceSurveyResponse = {
+    const getAddressOrdnanceSurveyMultipleResponse: GetAddressOrdnanceSurveyResponse = {
       header: {
         uri: 'test',
         query: 'test',
@@ -187,13 +189,13 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
 
     return {
       request,
-      // ordnanceSurveyRequest,
       ordnanceSurveyPath,
       mdmPath,
       getAddressByPostcodeResponse,
+      getAddressByPostcodeMultipleResponse,
       getAddressOrdnanceSurveyResponse,
       getAddressOrdnanceSurveyEmptyResponse,
-      getAddressessOrdnanceSurveyResponse,
+      getAddressOrdnanceSurveyMultipleResponse,
       ordnanceSurveyAuthErrorResponse,
     };
   }
@@ -217,12 +219,12 @@ interface GenerateOptions {
 
 interface GenerateResult {
   request: GetAddressByPostcodeQueryDto[];
-  //ordnanceSurveyRequest: GetCustomersInformaticaQueryDto[];
   ordnanceSurveyPath: string[];
   mdmPath: string[];
   getAddressByPostcodeResponse: GetAddressesResponse[];
+  getAddressByPostcodeMultipleResponse: GetAddressesResponse;
   getAddressOrdnanceSurveyResponse: GetAddressOrdnanceSurveyResponse[];
-  getAddressessOrdnanceSurveyResponse: GetAddressOrdnanceSurveyResponse;
+  getAddressOrdnanceSurveyMultipleResponse: GetAddressOrdnanceSurveyResponse;
   getAddressOrdnanceSurveyEmptyResponse: GetAddressOrdnanceSurveyResponse[];
   ordnanceSurveyAuthErrorResponse: OrdnanceSurveyAuthErrorResponse;
 }

From db50210567ef3e3b542daccb4748726476409934 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Mon, 15 Apr 2024 10:42:52 +0100
Subject: [PATCH 14/56] feat(DTFS2-7052): fix address line 1 formating

---
 src/modules/geospatial/geospatial.service.test.ts | 11 +++++++++++
 src/modules/geospatial/geospatial.service.ts      |  5 +++--
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/modules/geospatial/geospatial.service.test.ts b/src/modules/geospatial/geospatial.service.test.ts
index ec3165dd..7853afcf 100644
--- a/src/modules/geospatial/geospatial.service.test.ts
+++ b/src/modules/geospatial/geospatial.service.test.ts
@@ -63,5 +63,16 @@ describe('CustomerService', () => {
 
       expect(response).toEqual([]);
     });
+
+    it('returns addressLine1 formatted correctly even if middle value is missing', async () => {
+      const [modifiedOrdnanceSurveyResponse] = getAddressOrdnanceSurveyResponse;
+      modifiedOrdnanceSurveyResponse.results[0].DPA.BUILDING_NUMBER = null;
+      const address = modifiedOrdnanceSurveyResponse.results[0].DPA;
+      when(informaticaServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(modifiedOrdnanceSurveyResponse);
+
+      const response = await service.getAddressesByPostcode(postcode);
+
+      expect(response[0].addressLine1).toBe(`${address.BUILDING_NAME} ${address.THOROUGHFARE_NAME}`);
+    });
   });
 });
diff --git a/src/modules/geospatial/geospatial.service.ts b/src/modules/geospatial/geospatial.service.ts
index b3cb515d..d0385b92 100644
--- a/src/modules/geospatial/geospatial.service.ts
+++ b/src/modules/geospatial/geospatial.service.ts
@@ -18,11 +18,12 @@ export class GeospatialService {
     }
 
     response.results.forEach((item) => {
-      // Ordnance survey sends duplicated results with the welsh version too via 'CY'
+      // Item can have key DPA or LPI, get data dynamicaly, even if we expect key to always be DPA.
       const item_data = item[Object.keys(item)[0]];
       addresses.push({
         organisationName: item_data.ORGANISATION_NAME || null,
-        addressLine1: `${item_data.BUILDING_NAME || ''} ${item_data.BUILDING_NUMBER || ''} ${item_data.THOROUGHFARE_NAME || ''}`.trim(),
+        // Filter out empty values and join values with single space.
+        addressLine1: [item_data.BUILDING_NAME, item_data.BUILDING_NUMBER, item_data.THOROUGHFARE_NAME].filter(Boolean).join(' '),
         addressLine2: item_data.DEPENDENT_LOCALITY || null,
         addressLine3: null,
         locality: item_data.POST_TOWN || null,

From 4c503caa0895a0c1b8ba5214dd5aeeeeb57f11a1 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Mon, 15 Apr 2024 11:55:39 +0100
Subject: [PATCH 15/56] feat(DTFS2-7052): spelling fix

---
 src/modules/geospatial/geospatial.service.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/geospatial/geospatial.service.ts b/src/modules/geospatial/geospatial.service.ts
index d0385b92..2e07562b 100644
--- a/src/modules/geospatial/geospatial.service.ts
+++ b/src/modules/geospatial/geospatial.service.ts
@@ -18,7 +18,7 @@ export class GeospatialService {
     }
 
     response.results.forEach((item) => {
-      // Item can have key DPA or LPI, get data dynamicaly, even if we expect key to always be DPA.
+      // Item can have key DPA or LPI, get data dynamically, even if we expect key to always be DPA.
       const item_data = item[Object.keys(item)[0]];
       addresses.push({
         organisationName: item_data.ORGANISATION_NAME || null,

From 252b5a132a04e44df1cc4a66f27f5ff16af67207 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 18 Apr 2024 15:26:41 +0100
Subject: [PATCH 16/56] feat(DTFS2-7052): renaming enums/geospatial.ts to
 enums/geospatialCountries.ts

---
 src/constants/enums.ts                                        | 4 ++--
 src/constants/enums/{geospatial.ts => geospatialCountries.ts} | 0
 2 files changed, 2 insertions(+), 2 deletions(-)
 rename src/constants/enums/{geospatial.ts => geospatialCountries.ts} (100%)

diff --git a/src/constants/enums.ts b/src/constants/enums.ts
index dc997052..a6a38f2d 100644
--- a/src/constants/enums.ts
+++ b/src/constants/enums.ts
@@ -1,9 +1,9 @@
 import * as FALLBACK_TO_LEGACY_DATA from './enums/fallbackToLegacyData';
-import * as GEOSPATIAL from './enums/geospatial';
+import * as GEOSPATIAL_COUNTRIES from './enums/geospatialCountries';
 import * as PRODUCTS from './enums/products';
 
 export const ENUMS = {
   PRODUCTS: PRODUCTS.QueryParamProductsEnum,
   FALLBACK_TO_LEGACY_DATA: FALLBACK_TO_LEGACY_DATA.FallbackToLegacyDataEnum,
-  GEOSPATIAL_COUNTRIES: GEOSPATIAL.GeospatialCountriesEnum,
+  GEOSPATIAL_COUNTRIES: GEOSPATIAL_COUNTRIES.GeospatialCountriesEnum,
 };
diff --git a/src/constants/enums/geospatial.ts b/src/constants/enums/geospatialCountries.ts
similarity index 100%
rename from src/constants/enums/geospatial.ts
rename to src/constants/enums/geospatialCountries.ts

From f33edc51178d3f6ba07baf7415c1a5c53d9fb4ab Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Tue, 23 Apr 2024 16:31:02 +0100
Subject: [PATCH 17/56] Update
 src/modules/geospatial/geospatial.controller.test.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 src/modules/geospatial/geospatial.controller.test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/geospatial/geospatial.controller.test.ts b/src/modules/geospatial/geospatial.controller.test.ts
index d60c0a83..0dd22b21 100644
--- a/src/modules/geospatial/geospatial.controller.test.ts
+++ b/src/modules/geospatial/geospatial.controller.test.ts
@@ -41,7 +41,7 @@ describe('GeospatialController', () => {
       expect(response).toEqual(getAddressByPostcodeResponse[0]);
     });
 
-    it('returns multiple addressess for postcode', async () => {
+    it('returns multiple addresses for the postcode when the service returns multiple addresses', async () => {
       when(geospatialServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressByPostcodeMultipleResponse);
 
       const response = await controller.getAddressesByPostcode({ postcode });

From b67337694a3c93252e0accf15c6033d5f93455c3 Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Tue, 23 Apr 2024 16:31:13 +0100
Subject: [PATCH 18/56] Update
 src/modules/geospatial/geospatial.controller.test.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 src/modules/geospatial/geospatial.controller.test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/geospatial/geospatial.controller.test.ts b/src/modules/geospatial/geospatial.controller.test.ts
index 0dd22b21..6037b3a2 100644
--- a/src/modules/geospatial/geospatial.controller.test.ts
+++ b/src/modules/geospatial/geospatial.controller.test.ts
@@ -50,7 +50,7 @@ describe('GeospatialController', () => {
       expect(response).toEqual(getAddressByPostcodeMultipleResponse);
     });
 
-    it('returns empty response for postcode', async () => {
+    it('returns an empty response for the postcode when the service returns an empty response', async () => {
       when(geospatialServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce([]);
 
       const response = await controller.getAddressesByPostcode({ postcode });

From fb2d4bc8699aa993a596f0efb21884afdd3e7f46 Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Tue, 23 Apr 2024 16:31:24 +0100
Subject: [PATCH 19/56] Update
 src/modules/geospatial/geospatial.controller.test.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 src/modules/geospatial/geospatial.controller.test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/geospatial/geospatial.controller.test.ts b/src/modules/geospatial/geospatial.controller.test.ts
index 6037b3a2..45e3dfb7 100644
--- a/src/modules/geospatial/geospatial.controller.test.ts
+++ b/src/modules/geospatial/geospatial.controller.test.ts
@@ -46,7 +46,7 @@ describe('GeospatialController', () => {
 
       const response = await controller.getAddressesByPostcode({ postcode });
 
-      expect(geospatialServiceGetAddressesByPostcode).toHaveBeenCalled();
+      expect(geospatialServiceGetAddressesByPostcode).toHaveBeenCalledTimes(1);
       expect(response).toEqual(getAddressByPostcodeMultipleResponse);
     });
 

From b7a727cd0b1ab4b2f8500a5ccd17353352636215 Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Tue, 23 Apr 2024 16:31:34 +0100
Subject: [PATCH 20/56] Update
 src/modules/geospatial/geospatial.controller.test.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 src/modules/geospatial/geospatial.controller.test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/geospatial/geospatial.controller.test.ts b/src/modules/geospatial/geospatial.controller.test.ts
index 45e3dfb7..700914e2 100644
--- a/src/modules/geospatial/geospatial.controller.test.ts
+++ b/src/modules/geospatial/geospatial.controller.test.ts
@@ -55,7 +55,7 @@ describe('GeospatialController', () => {
 
       const response = await controller.getAddressesByPostcode({ postcode });
 
-      expect(geospatialServiceGetAddressesByPostcode).toHaveBeenCalled();
+      expect(geospatialServiceGetAddressesByPostcode).toHaveBeenCalledTimes(1);
       expect(response).toEqual([]);
     });
   });

From 841268f77775a3dadca7178aa0a54d17ec62055f Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Tue, 23 Apr 2024 16:31:44 +0100
Subject: [PATCH 21/56] Update
 src/modules/geospatial/geospatial.service.test.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 src/modules/geospatial/geospatial.service.test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/geospatial/geospatial.service.test.ts b/src/modules/geospatial/geospatial.service.test.ts
index 7853afcf..4f1ab21a 100644
--- a/src/modules/geospatial/geospatial.service.test.ts
+++ b/src/modules/geospatial/geospatial.service.test.ts
@@ -8,7 +8,7 @@ import { GeospatialService } from './geospatial.service';
 
 jest.mock('@ukef/modules/informatica/informatica.service');
 
-describe('CustomerService', () => {
+describe('GeospatialService', () => {
   const valueGenerator = new RandomValueGenerator();
 
   let service: GeospatialService;

From 7f1110df012953f6ce05d86c8ca3dc446314a0f5 Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Tue, 23 Apr 2024 16:58:41 +0100
Subject: [PATCH 22/56] Update
 src/modules/geospatial/geospatial.service.test.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 src/modules/geospatial/geospatial.service.test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/geospatial/geospatial.service.test.ts b/src/modules/geospatial/geospatial.service.test.ts
index 4f1ab21a..2b34b577 100644
--- a/src/modules/geospatial/geospatial.service.test.ts
+++ b/src/modules/geospatial/geospatial.service.test.ts
@@ -40,7 +40,7 @@ describe('GeospatialService', () => {
     });
     const postcode = getAddressByPostcodeResponse[0][0].postalCode;
 
-    it('returns address from the backend service', async () => {
+    it('returns a single address from the backend service', async () => {
       when(informaticaServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressOrdnanceSurveyResponse[0]);
 
       const response = await service.getAddressesByPostcode(postcode);

From ff5ac7b19b50398c0c4662ca9886987e460dc783 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Tue, 23 Apr 2024 17:11:00 +0100
Subject: [PATCH 23/56] feat(DTFS2-7052): improve address test data

---
 test/support/generator/get-geospatial-addresses-generator.ts | 4 ++--
 test/support/generator/random-value-generator.ts             | 4 ++++
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/test/support/generator/get-geospatial-addresses-generator.ts b/test/support/generator/get-geospatial-addresses-generator.ts
index 69320761..c0ed8415 100644
--- a/test/support/generator/get-geospatial-addresses-generator.ts
+++ b/test/support/generator/get-geospatial-addresses-generator.ts
@@ -14,10 +14,10 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
 
   protected generateValues(): AddressValues {
     return {
-      ORGANISATION_NAME: this.valueGenerator.word({ length: 5 }),
+      ORGANISATION_NAME: this.valueGenerator.sentence({ words: 2 }),
       BUILDING_NAME: this.valueGenerator.word(),
       BUILDING_NUMBER: this.valueGenerator.nonnegativeInteger().toString(),
-      THOROUGHFARE_NAME: this.valueGenerator.word({ length: 7 }),
+      THOROUGHFARE_NAME: this.valueGenerator.sentence({ words: 5 }),
       DEPENDENT_LOCALITY: this.valueGenerator.word(),
       POST_TOWN: this.valueGenerator.word(),
       POSTCODE: this.valueGenerator.postcode(),
diff --git a/test/support/generator/random-value-generator.ts b/test/support/generator/random-value-generator.ts
index 929fce0e..c2dedec9 100644
--- a/test/support/generator/random-value-generator.ts
+++ b/test/support/generator/random-value-generator.ts
@@ -36,6 +36,10 @@ export class RandomValueGenerator {
     return this.chance.word({ length: options?.length });
   }
 
+  sentence(options?: { words?: number }): string {
+    return this.chance.sentence({ words: options?.words });
+  }
+
   httpsUrl(): string {
     return this.chance.url({ protocol: 'https' });
   }

From f68ac66050ea3e0515378657f4893eb65f933ed4 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Wed, 24 Apr 2024 15:28:57 +0100
Subject: [PATCH 24/56] feat(DTFS2-7052): applying Oscars suggestions on my PR

---
 ...-addresses-ordnance-survey-response.dto.ts | 10 +--
 .../ordnance-survey/known-errors.ts           | 13 ---
 .../ordnance-survey.service.test.ts           | 83 +++++++++----------
 .../ordnance-survey.service.ts                | 18 ++--
 ...rap-ordnance-survey-http-error-callback.ts | 32 -------
 ...=> get-addresses-by-postcode-query.dto.ts} |  2 +-
 .../geospatial/geospatial.controller.ts       |  4 +-
 src/modules/geospatial/geospatial.service.ts  |  4 +-
 .../get-geospatial-addresses-generator.ts     | 48 +++++------
 9 files changed, 83 insertions(+), 131 deletions(-)
 delete mode 100644 src/helper-modules/ordnance-survey/known-errors.ts
 delete mode 100644 src/helper-modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts
 rename src/modules/geospatial/dto/{get-address-by-postcode-query.dto.ts => get-addresses-by-postcode-query.dto.ts} (90%)

diff --git a/src/helper-modules/ordnance-survey/dto/get-addresses-ordnance-survey-response.dto.ts b/src/helper-modules/ordnance-survey/dto/get-addresses-ordnance-survey-response.dto.ts
index f1c5b1f5..e500d8d9 100644
--- a/src/helper-modules/ordnance-survey/dto/get-addresses-ordnance-survey-response.dto.ts
+++ b/src/helper-modules/ordnance-survey/dto/get-addresses-ordnance-survey-response.dto.ts
@@ -1,4 +1,4 @@
-export type GetAddressOrdnanceSurveyResponse = {
+export type GetAddressesOrdnanceSurveyResponse = {
   header: {
     uri: string;
     query: string;
@@ -12,14 +12,14 @@ export type GetAddressOrdnanceSurveyResponse = {
     lastupdate: string;
     output_srs: string;
   };
-  results?: GetAddressOrdnanceSurveyResponseItem[];
+  results?: GetAddressesOrdnanceSurveyResponseItem[];
 };
 
-interface GetAddressOrdnanceSurveyResponseItem {
-  DPA: GetAddressOrdnanceSurveyResponseAddress;
+interface GetAddressesOrdnanceSurveyResponseItem {
+  DPA: GetAddressesOrdnanceSurveyResponseAddress;
 }
 
-interface GetAddressOrdnanceSurveyResponseAddress {
+interface GetAddressesOrdnanceSurveyResponseAddress {
   UPRN: string;
   UDPRN: string;
   ADDRESS: string;
diff --git a/src/helper-modules/ordnance-survey/known-errors.ts b/src/helper-modules/ordnance-survey/known-errors.ts
deleted file mode 100644
index 48c9d029..00000000
--- a/src/helper-modules/ordnance-survey/known-errors.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { NotFoundException } from '@nestjs/common';
-import { AxiosError } from 'axios';
-
-export type KnownErrors = KnownError[];
-
-type KnownError = { caseInsensitiveSubstringToFind: string; throwError: (error: AxiosError) => never };
-
-export const getAddressNotFoundKnownOrdnanceSurveyError = (): KnownError => ({
-  caseInsensitiveSubstringToFind: 'Address not found',
-  throwError: (error) => {
-    throw new NotFoundException('Address not found.', error);
-  },
-});
diff --git a/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts b/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
index e3cfceca..611cc5b1 100644
--- a/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
+++ b/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
@@ -4,8 +4,8 @@ import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-
 import { AxiosError } from 'axios';
 import { when } from 'jest-when';
 import { of, throwError } from 'rxjs';
-import expectedResponse = require('./examples/example-response-for-search-places-v1-postcode.json');
-import noResultsResponse = require('./examples/example-response-for-search-places-v1-postcode-no-results.json');
+import expectedResponseData = require('./examples/example-response-for-search-places-v1-postcode.json');
+import noResultsResponseData = require('./examples/example-response-for-search-places-v1-postcode-no-results.json');
 
 import { GEOSPATIAL } from '@ukef/constants';
 
@@ -23,6 +23,14 @@ describe('OrdnanceSurveyService', () => {
   const testKey = valueGenerator.string({ length: 10 });
   const basePath = '/search/places/v1/postcode';
 
+  const expectedResponse = of({
+    data: expectedResponseData,
+    status: 200,
+    statusText: 'OK',
+    config: undefined,
+    headers: undefined,
+  });
+
   beforeEach(() => {
     const httpService = new HttpService();
     const configService = new ConfigService();
@@ -40,26 +48,11 @@ describe('OrdnanceSurveyService', () => {
 
     const expectedHttpServiceGetArgs: [string, object] = [expectedPath, { headers: { 'Content-Type': 'application/json' } }];
 
-    it('sends a GET to the Ordnance Survey API /search endpoint with the specified request', async () => {
-      when(httpServiceGet)
-        .calledWith(...expectedHttpServiceGetArgs)
-        .mockReturnValueOnce(
-          of({
-            data: expectedResponse,
-            status: 200,
-            statusText: 'OK',
-            config: undefined,
-            headers: undefined,
-          }),
-        );
-
-      await service.getAddressesByPostcode(testPostcode);
-
-      expect(httpServiceGet).toHaveBeenCalledTimes(1);
-      expect(httpServiceGet).toHaveBeenCalledWith(...expectedHttpServiceGetArgs);
-    });
-
-    it.each([
+    describe.each([
+      {
+        postcode: testPostcode,
+        expectedUrlQueryPart: `?postcode=${encodeURIComponent(testPostcode)}`,
+      },
       {
         postcode: 'W1A 1AA',
         expectedUrlQueryPart: `?postcode=W1A%201AA`,
@@ -68,34 +61,40 @@ describe('OrdnanceSurveyService', () => {
         postcode: 'W1A1AA',
         expectedUrlQueryPart: '?postcode=W1A1AA',
       },
-    ])('call Ordnance Survey API with correct and safe query parameters "$expectedUrlQueryPart"', async ({ postcode, expectedUrlQueryPart }) => {
-      const expectedPath = `${basePath}${expectedUrlQueryPart}&lr=EN&key=${encodeURIComponent(testKey)}`;
-      const expectedHttpServiceGetArgs: [string, object] = [expectedPath, { headers: { 'Content-Type': 'application/json' } }];
+    ])('test postcode $postcode', ({ postcode, expectedUrlQueryPart }) => {
+      it('call Ordnance Survey with the correct arguments', async () => {
+        const expectedPath = `${basePath}${expectedUrlQueryPart}&lr=EN&key=${encodeURIComponent(testKey)}`;
+        const expectedHttpServiceGetArgs: [string, object] = [expectedPath, { headers: { 'Content-Type': 'application/json' } }];
 
-      when(httpServiceGet)
-        .calledWith(...expectedHttpServiceGetArgs)
-        .mockReturnValueOnce(
-          of({
-            data: expectedResponse,
-            status: 200,
-            statusText: 'OK',
-            config: undefined,
-            headers: undefined,
-          }),
-        );
+        when(httpServiceGet)
+          .calledWith(...expectedHttpServiceGetArgs)
+          .mockReturnValueOnce(expectedResponse);
+        await service.getAddressesByPostcode(postcode);
 
-      await service.getAddressesByPostcode(postcode);
+        expect(httpServiceGet).toHaveBeenCalledTimes(1);
+        expect(httpServiceGet).toHaveBeenCalledWith(...expectedHttpServiceGetArgs);
+      });
 
-      expect(httpServiceGet).toHaveBeenCalledTimes(1);
-      expect(httpServiceGet).toHaveBeenCalledWith(...expectedHttpServiceGetArgs);
+      it('call Ordnance Survey returns expectedResponse', async () => {
+        const expectedPath = `${basePath}${expectedUrlQueryPart}&lr=EN&key=${encodeURIComponent(testKey)}`;
+        const expectedHttpServiceGetArgs: [string, object] = [expectedPath, { headers: { 'Content-Type': 'application/json' } }];
+
+        when(httpServiceGet)
+          .calledWith(...expectedHttpServiceGetArgs)
+          .mockReturnValueOnce(expectedResponse);
+
+        const response = await service.getAddressesByPostcode(postcode);
+
+        expect(response).toBe(expectedResponseData);
+      });
     });
 
-    it('no results - returns 200 without results', async () => {
+    it('returns a 200 response without results when Ordnance Survey returns no results', async () => {
       when(httpServiceGet)
         .calledWith(...expectedHttpServiceGetArgs)
         .mockReturnValueOnce(
           of({
-            data: noResultsResponse,
+            data: noResultsResponseData,
             status: 200,
             statusText: 'OK',
             config: undefined,
@@ -107,7 +106,7 @@ describe('OrdnanceSurveyService', () => {
 
       expect(httpServiceGet).toHaveBeenCalledTimes(1);
       expect(httpServiceGet).toHaveBeenCalledWith(...expectedHttpServiceGetArgs);
-      expect(results).toBe(noResultsResponse);
+      expect(results).toBe(noResultsResponseData);
     });
 
     it('throws an OrdnanceSurveyException if the request to Ordnance Survey fails', async () => {
diff --git a/src/helper-modules/ordnance-survey/ordnance-survey.service.ts b/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
index a079baab..f8ad1a78 100644
--- a/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
+++ b/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
@@ -5,9 +5,8 @@ import { KEY as ORDNANCE_SURVEY_CONFIG_KEY, OrdnanceSurveyConfig } from '@ukef/c
 import { GEOSPATIAL } from '@ukef/constants';
 import { HttpClient } from '@ukef/modules/http/http.client';
 
-import { GetAddressOrdnanceSurveyResponse } from './dto/get-addresses-ordnance-survey-response.dto';
-// import { getCustomersNotFoundKnownOrdnanceSurveyError } from './known-errors';
-import { createWrapOrdnanceSurveyHttpGetErrorCallback } from './wrap-ordnance-survey-http-error-callback';
+import { GetAddressesOrdnanceSurveyResponse } from './dto/get-addresses-ordnance-survey-response.dto';
+import { OrdnanceSurveyException } from './exception/ordnance-survey.exception';
 
 @Injectable()
 export class OrdnanceSurveyService {
@@ -20,16 +19,15 @@ export class OrdnanceSurveyService {
     this.key = key;
   }
 
-  async getAddressesByPostcode(postcode): Promise<GetAddressOrdnanceSurveyResponse> {
+  async getAddressesByPostcode(postcode: string): Promise<GetAddressesOrdnanceSurveyResponse> {
     const path = `/search/places/v1/postcode?postcode=${encodeURIComponent(postcode)}&lr=${GEOSPATIAL.DEFAULT.RESULT_LANGUAGE}&key=${encodeURIComponent(this.key)}`;
-    const { data } = await this.httpClient.get<GetAddressOrdnanceSurveyResponse>({
+    const { data } = await this.httpClient.get<GetAddressesOrdnanceSurveyResponse>({
       path,
       headers: { 'Content-Type': 'application/json' },
-      onError: createWrapOrdnanceSurveyHttpGetErrorCallback({
-        messageForUnknownError: `Failed to get response from Ordnance Survey API.`,
-        knownErrors: [],
-        // knownErrors: [getCustomersNotFoundKnownOrdnanceSurveyError()], // TODO: should we change 200 no results to 404?
-      }),
+      onError: (error: Error) => {
+        console.error('Http call error happened, error %o', error);
+        throw new OrdnanceSurveyException('Failed to get response from Ordnance Survey API.', error);
+      },
     });
     return data;
   }
diff --git a/src/helper-modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts b/src/helper-modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts
deleted file mode 100644
index cf1e4c1a..00000000
--- a/src/helper-modules/ordnance-survey/wrap-ordnance-survey-http-error-callback.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { AxiosError } from 'axios';
-import { ObservableInput, throwError } from 'rxjs';
-
-import { OrdnanceSurveyException } from './exception/ordnance-survey.exception';
-import { KnownErrors } from './known-errors';
-
-type AcbsHttpErrorCallback = (error: Error) => ObservableInput<never>;
-
-export const createWrapOrdnanceSurveyHttpGetErrorCallback =
-  ({ messageForUnknownError, knownErrors }: { messageForUnknownError: string; knownErrors: KnownErrors }): AcbsHttpErrorCallback =>
-  (error: Error) => {
-    let errorString;
-    if (error instanceof AxiosError && error.response) {
-      if (typeof error.response.data === 'object') {
-        errorString = JSON.stringify(error.response.data);
-      }
-      if (typeof error.response.data === 'string') {
-        errorString = error.response.data;
-      }
-      if (errorString) {
-        const errorStringInLowerCase = errorString.toLowerCase();
-
-        knownErrors.forEach(({ caseInsensitiveSubstringToFind, throwError }) => {
-          if (errorStringInLowerCase.includes(caseInsensitiveSubstringToFind.toLowerCase())) {
-            return throwError(error);
-          }
-        });
-      }
-    }
-
-    return throwError(() => new OrdnanceSurveyException(messageForUnknownError, error));
-  };
diff --git a/src/modules/geospatial/dto/get-address-by-postcode-query.dto.ts b/src/modules/geospatial/dto/get-addresses-by-postcode-query.dto.ts
similarity index 90%
rename from src/modules/geospatial/dto/get-address-by-postcode-query.dto.ts
rename to src/modules/geospatial/dto/get-addresses-by-postcode-query.dto.ts
index 6202fdd1..b344a25b 100644
--- a/src/modules/geospatial/dto/get-address-by-postcode-query.dto.ts
+++ b/src/modules/geospatial/dto/get-addresses-by-postcode-query.dto.ts
@@ -4,7 +4,7 @@ import { Matches, MaxLength, MinLength } from 'class-validator';
 
 const UK_POSTCODE = /^[A-Za-z]{1,2}[\dRr][\dA-Za-z]?\s?\d[ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$/;
 
-export class GetAddressByPostcodeQueryDto {
+export class GetAddressesByPostcodeQueryDto {
   @ApiProperty({
     example: GEOSPATIAL.EXAMPLES.POSTCODE,
     description: 'Postcode to search for',
diff --git a/src/modules/geospatial/geospatial.controller.ts b/src/modules/geospatial/geospatial.controller.ts
index 5b7fe670..95813d93 100644
--- a/src/modules/geospatial/geospatial.controller.ts
+++ b/src/modules/geospatial/geospatial.controller.ts
@@ -1,7 +1,7 @@
 import { Controller, Get, Query } from '@nestjs/common';
 import { ApiNotFoundResponse, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
 
-import { GetAddressByPostcodeQueryDto } from './dto/get-address-by-postcode-query.dto';
+import { GetAddressesByPostcodeQueryDto } from './dto/get-addresses-by-postcode-query.dto';
 import { GetAddressesResponse, GetAddressesResponseItem } from './dto/get-addresses-response.dto';
 import { GeospatialService } from './geospatial.service';
 
@@ -23,7 +23,7 @@ export class GeospatialController {
   @ApiNotFoundResponse({
     description: 'Customer not found.',
   })
-  getAddressesByPostcode(@Query() query: GetAddressByPostcodeQueryDto): Promise<GetAddressesResponse> {
+  getAddressesByPostcode(@Query() query: GetAddressesByPostcodeQueryDto): Promise<GetAddressesResponse> {
     return this.geospatialService.getAddressesByPostcode(query.postcode);
   }
 }
diff --git a/src/modules/geospatial/geospatial.service.ts b/src/modules/geospatial/geospatial.service.ts
index 2e07562b..b566e7f0 100644
--- a/src/modules/geospatial/geospatial.service.ts
+++ b/src/modules/geospatial/geospatial.service.ts
@@ -1,6 +1,6 @@
 import { Injectable } from '@nestjs/common';
 import { ENUMS } from '@ukef/constants';
-import { GetAddressOrdnanceSurveyResponse } from '@ukef/helper-modules/ordnance-survey/dto/get-addresses-ordnance-survey-response.dto';
+import { GetAddressesOrdnanceSurveyResponse } from '@ukef/helper-modules/ordnance-survey/dto/get-addresses-ordnance-survey-response.dto';
 import { OrdnanceSurveyService } from '@ukef/helper-modules/ordnance-survey/ordnance-survey.service';
 
 import { GetAddressesResponse } from './dto/get-addresses-response.dto';
@@ -11,7 +11,7 @@ export class GeospatialService {
 
   async getAddressesByPostcode(postcode: string): Promise<GetAddressesResponse> {
     const addresses = [];
-    const response: GetAddressOrdnanceSurveyResponse = await this.ordnanceSurveyService.getAddressesByPostcode(postcode);
+    const response: GetAddressesOrdnanceSurveyResponse = await this.ordnanceSurveyService.getAddressesByPostcode(postcode);
 
     if (!response?.results) {
       return [];
diff --git a/test/support/generator/get-geospatial-addresses-generator.ts b/test/support/generator/get-geospatial-addresses-generator.ts
index c0ed8415..6a8c8e22 100644
--- a/test/support/generator/get-geospatial-addresses-generator.ts
+++ b/test/support/generator/get-geospatial-addresses-generator.ts
@@ -1,7 +1,7 @@
 import { ENUMS, GEOSPATIAL } from '@ukef/constants';
-import { GetAddressOrdnanceSurveyResponse } from '@ukef/helper-modules/ordnance-survey/dto/get-addresses-ordnance-survey-response.dto';
+import { GetAddressesOrdnanceSurveyResponse } from '@ukef/helper-modules/ordnance-survey/dto/get-addresses-ordnance-survey-response.dto';
 import { OrdnanceSurveyAuthErrorResponse } from '@ukef/helper-modules/ordnance-survey/dto/ordnance-survey-auth-error-response.dto';
-import { GetAddressByPostcodeQueryDto } from '@ukef/modules/geospatial/dto/get-address-by-postcode-query.dto';
+import { GetAddressesByPostcodeQueryDto } from '@ukef/modules/geospatial/dto/get-addresses-by-postcode-query.dto';
 import { GetAddressesResponse } from '@ukef/modules/geospatial/dto/get-addresses-response.dto';
 
 import { AbstractGenerator } from './abstract-generator';
@@ -28,19 +28,19 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
   protected transformRawValuesToGeneratedValues(values: AddressValues[], { postcode, key }: GenerateOptions): GenerateResult {
     const useKey = key || 'test';
 
-    const request: GetAddressByPostcodeQueryDto[] = values.map((v) => ({ postcode: postcode || v.POSTCODE }) as GetAddressByPostcodeQueryDto);
+    const requests: GetAddressesByPostcodeQueryDto[] = values.map((v) => ({ postcode: postcode || v.POSTCODE }) as GetAddressesByPostcodeQueryDto);
 
-    const ordnanceSurveyPath: string[] = values.map((v) => {
+    const ordnanceSurveyPaths: string[] = values.map((v) => {
       const usePostcode = postcode || v.POSTCODE;
       return `/search/places/v1/postcode?postcode=${encodeURIComponent(usePostcode)}&lr=${GEOSPATIAL.DEFAULT.RESULT_LANGUAGE}&key=${encodeURIComponent(useKey)}`;
     });
 
-    const mdmPath: string[] = values.map((v) => {
+    const mdmPaths: string[] = values.map((v) => {
       const usePostcode = postcode || v.POSTCODE;
       return `/api/v1/geospatial/addresses/postcode?postcode=${usePostcode}`;
     });
 
-    const getAddressByPostcodeResponse: GetAddressesResponse[] = values.map((v) => [
+    const getAddressesByPostcodeResponse: GetAddressesResponse[] = values.map((v) => [
       {
         organisationName: v.ORGANISATION_NAME,
         addressLine1: `${v.BUILDING_NAME} ${v.BUILDING_NUMBER} ${v.THOROUGHFARE_NAME}`,
@@ -52,9 +52,9 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
       },
     ]);
 
-    const getAddressByPostcodeMultipleResponse = getAddressByPostcodeResponse.map((response) => response[0]);
+    const getAddressesByPostcodeMultipleResponse = getAddressesByPostcodeResponse.map((response) => response[0]);
 
-    const getAddressOrdnanceSurveyResponse: GetAddressOrdnanceSurveyResponse[] = values.map((v) => ({
+    const getAddressesOrdnanceSurveyResponse: GetAddressesOrdnanceSurveyResponse[] = values.map((v) => ({
       header: {
         uri: 'test',
         query: 'test',
@@ -109,7 +109,7 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
       ],
     }));
 
-    const getAddressOrdnanceSurveyMultipleResponse: GetAddressOrdnanceSurveyResponse = {
+    const getAddressesOrdnanceSurveyMultipleResponse: GetAddressesOrdnanceSurveyResponse = {
       header: {
         uri: 'test',
         query: 'test',
@@ -162,7 +162,7 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
       })),
     };
 
-    const getAddressOrdnanceSurveyEmptyResponse: GetAddressOrdnanceSurveyResponse[] = values.map(() => ({
+    const getAddressesOrdnanceSurveyEmptyResponse: GetAddressesOrdnanceSurveyResponse = {
       header: {
         uri: 'test',
         query: 'test',
@@ -176,9 +176,9 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
         lastupdate: 'test',
         output_srs: 'test',
       },
-    }));
+    };
 
-    const ordnanceSurveyAuthErrorResponse = {
+    const ordnanceSurveyAuthErrorResponse: OrdnanceSurveyAuthErrorResponse = {
       fault: {
         faultstring: 'Invalid ApiKey',
         detail: {
@@ -188,14 +188,14 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
     };
 
     return {
-      request,
-      ordnanceSurveyPath,
-      mdmPath,
-      getAddressByPostcodeResponse,
-      getAddressByPostcodeMultipleResponse,
-      getAddressOrdnanceSurveyResponse,
-      getAddressOrdnanceSurveyEmptyResponse,
-      getAddressOrdnanceSurveyMultipleResponse,
+      requests,
+      ordnanceSurveyPath: ordnanceSurveyPaths,
+      mdmPath: mdmPaths,
+      getAddressByPostcodeResponse: getAddressesByPostcodeResponse,
+      getAddressByPostcodeMultipleResponse: getAddressesByPostcodeMultipleResponse,
+      getAddressOrdnanceSurveyResponse: getAddressesOrdnanceSurveyResponse,
+      getAddressOrdnanceSurveyEmptyResponse: getAddressesOrdnanceSurveyEmptyResponse,
+      getAddressOrdnanceSurveyMultipleResponse: getAddressesOrdnanceSurveyMultipleResponse,
       ordnanceSurveyAuthErrorResponse,
     };
   }
@@ -218,13 +218,13 @@ interface GenerateOptions {
 }
 
 interface GenerateResult {
-  request: GetAddressByPostcodeQueryDto[];
+  requests: GetAddressesByPostcodeQueryDto[];
   ordnanceSurveyPath: string[];
   mdmPath: string[];
   getAddressByPostcodeResponse: GetAddressesResponse[];
   getAddressByPostcodeMultipleResponse: GetAddressesResponse;
-  getAddressOrdnanceSurveyResponse: GetAddressOrdnanceSurveyResponse[];
-  getAddressOrdnanceSurveyMultipleResponse: GetAddressOrdnanceSurveyResponse;
-  getAddressOrdnanceSurveyEmptyResponse: GetAddressOrdnanceSurveyResponse[];
+  getAddressOrdnanceSurveyResponse: GetAddressesOrdnanceSurveyResponse[];
+  getAddressOrdnanceSurveyMultipleResponse: GetAddressesOrdnanceSurveyResponse;
+  getAddressOrdnanceSurveyEmptyResponse: GetAddressesOrdnanceSurveyResponse;
   ordnanceSurveyAuthErrorResponse: OrdnanceSurveyAuthErrorResponse;
 }

From 75b34ac8c827ee214b219c0c6eff748af79e70a9 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 09:36:52 +0100
Subject: [PATCH 25/56] feat(DTFS2-7052): moving uk postcode regex to constants
 and doc improvements

---
 src/constants/geospatial.constant.ts                       | 4 ++++
 .../geospatial/dto/get-addresses-by-postcode-query.dto.ts  | 7 ++++---
 test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap     | 4 ++++
 3 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/src/constants/geospatial.constant.ts b/src/constants/geospatial.constant.ts
index 08bd74c2..82c471db 100644
--- a/src/constants/geospatial.constant.ts
+++ b/src/constants/geospatial.constant.ts
@@ -6,4 +6,8 @@ export const GEOSPATIAL = {
   EXAMPLES: {
     POSTCODE: 'SW1A 2AQ',
   },
+  REGEX: {
+    // UK postcode regex is from DTFS project and slightly optimised by lint.
+    UK_POSTCODE: /^[A-Za-z]{1,2}[\dRr][\dA-Za-z]?\s?\d[ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$/,
+  },
 };
diff --git a/src/modules/geospatial/dto/get-addresses-by-postcode-query.dto.ts b/src/modules/geospatial/dto/get-addresses-by-postcode-query.dto.ts
index b344a25b..26f62be0 100644
--- a/src/modules/geospatial/dto/get-addresses-by-postcode-query.dto.ts
+++ b/src/modules/geospatial/dto/get-addresses-by-postcode-query.dto.ts
@@ -2,15 +2,16 @@ import { ApiProperty } from '@nestjs/swagger';
 import { GEOSPATIAL } from '@ukef/constants';
 import { Matches, MaxLength, MinLength } from 'class-validator';
 
-const UK_POSTCODE = /^[A-Za-z]{1,2}[\dRr][\dA-Za-z]?\s?\d[ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$/;
-
 export class GetAddressesByPostcodeQueryDto {
   @ApiProperty({
     example: GEOSPATIAL.EXAMPLES.POSTCODE,
     description: 'Postcode to search for',
+    minLength: 5,
+    maxLength: 8,
+    pattern: GEOSPATIAL.REGEX.UK_POSTCODE.source,
   })
   @MinLength(5)
   @MaxLength(8)
-  @Matches(UK_POSTCODE)
+  @Matches(GEOSPATIAL.REGEX.UK_POSTCODE)
   public postcode: string;
 }
diff --git a/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap b/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
index c69ff11b..d08cd5fe 100644
--- a/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
+++ b/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
@@ -500,6 +500,10 @@ paths:
           example: SW1A 2AQ
           description: Postcode to search for
           schema:
+            minLength: 5
+            maxLength: 8
+            pattern: >-
+              ^[A-Za-z]{1,2}[\\dRr][\\dA-Za-z]?\\s?\\d[ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$
             type: string
       responses:
         '200':

From 5a0ee9342ee2d8505e0e8c08e26b8df67ce37941 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 09:38:06 +0100
Subject: [PATCH 26/56] feat(DTFS2-7052): comments copy and variable name
 improvements

---
 src/helper-modules/ordnance-survey/ordnance-survey.service.ts | 1 -
 src/modules/geospatial/geospatial.service.ts                  | 2 +-
 test/support/generator/get-geospatial-addresses-generator.ts  | 4 ++--
 3 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/helper-modules/ordnance-survey/ordnance-survey.service.ts b/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
index f8ad1a78..8da5ee13 100644
--- a/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
+++ b/src/helper-modules/ordnance-survey/ordnance-survey.service.ts
@@ -25,7 +25,6 @@ export class OrdnanceSurveyService {
       path,
       headers: { 'Content-Type': 'application/json' },
       onError: (error: Error) => {
-        console.error('Http call error happened, error %o', error);
         throw new OrdnanceSurveyException('Failed to get response from Ordnance Survey API.', error);
       },
     });
diff --git a/src/modules/geospatial/geospatial.service.ts b/src/modules/geospatial/geospatial.service.ts
index b566e7f0..cf2199a8 100644
--- a/src/modules/geospatial/geospatial.service.ts
+++ b/src/modules/geospatial/geospatial.service.ts
@@ -18,7 +18,7 @@ export class GeospatialService {
     }
 
     response.results.forEach((item) => {
-      // Item can have key DPA or LPI, get data dynamically, even if we expect key to always be DPA.
+      // Item can have key DPA or LPI, so we get data dynamically, even if we expect key to always be DPA.
       const item_data = item[Object.keys(item)[0]];
       addresses.push({
         organisationName: item_data.ORGANISATION_NAME || null,
diff --git a/test/support/generator/get-geospatial-addresses-generator.ts b/test/support/generator/get-geospatial-addresses-generator.ts
index 6a8c8e22..51522d9f 100644
--- a/test/support/generator/get-geospatial-addresses-generator.ts
+++ b/test/support/generator/get-geospatial-addresses-generator.ts
@@ -109,7 +109,7 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
       ],
     }));
 
-    const getAddressesOrdnanceSurveyMultipleResponse: GetAddressesOrdnanceSurveyResponse = {
+    const getAddressesOrdnanceSurveyMultipleMatchingAddressesResponse: GetAddressesOrdnanceSurveyResponse = {
       header: {
         uri: 'test',
         query: 'test',
@@ -195,7 +195,7 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
       getAddressByPostcodeMultipleResponse: getAddressesByPostcodeMultipleResponse,
       getAddressOrdnanceSurveyResponse: getAddressesOrdnanceSurveyResponse,
       getAddressOrdnanceSurveyEmptyResponse: getAddressesOrdnanceSurveyEmptyResponse,
-      getAddressOrdnanceSurveyMultipleResponse: getAddressesOrdnanceSurveyMultipleResponse,
+      getAddressOrdnanceSurveyMultipleResponse: getAddressesOrdnanceSurveyMultipleMatchingAddressesResponse,
       ordnanceSurveyAuthErrorResponse,
     };
   }

From 6bf5a4d7264d1f388795b679c461f8d753f1b490 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 09:43:10 +0100
Subject: [PATCH 27/56] feat(DTFS2-7052): typescript type improvement in
 api-test helper

---
 test/support/api.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/test/support/api.ts b/test/support/api.ts
index f86625d2..6a077403 100644
--- a/test/support/api.ts
+++ b/test/support/api.ts
@@ -1,6 +1,7 @@
 import { AUTH } from '@ukef/constants';
 import { ENVIRONMENT_VARIABLES } from '@ukef-test/support/environment-variables';
 import request from 'supertest';
+import TestAgent from 'supertest/lib/agent';
 
 import { App } from './app';
 
@@ -33,7 +34,7 @@ export class Api {
     return this.app.destroy();
   }
 
-  private request(): any {
+  private request(): TestAgent<request.Test> {
     return request(this.app.getHttpServer());
   }
 

From de7a0e59450dc7f0f4ea57bebd8251f560a5b36b Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:13:51 +0100
Subject: [PATCH 28/56] feat(DTFS2-7052): moving address examples to constants

---
 src/constants/geospatial.constant.ts                     | 3 +++
 src/modules/geospatial/dto/get-addresses-response.dto.ts | 6 +++---
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/src/constants/geospatial.constant.ts b/src/constants/geospatial.constant.ts
index 82c471db..567b89d2 100644
--- a/src/constants/geospatial.constant.ts
+++ b/src/constants/geospatial.constant.ts
@@ -5,6 +5,9 @@ export const GEOSPATIAL = {
   },
   EXAMPLES: {
     POSTCODE: 'SW1A 2AQ',
+    ORGANISATION_NAME: 'CHURCHILL MUSEUM & CABINET WAR ROOMS',
+    ADDRESS_LINE_1: 'CLIVE STEPS KING CHARLES STREET',
+    LOCALITY: 'LONDON',
   },
   REGEX: {
     // UK postcode regex is from DTFS project and slightly optimised by lint.
diff --git a/src/modules/geospatial/dto/get-addresses-response.dto.ts b/src/modules/geospatial/dto/get-addresses-response.dto.ts
index b458bf19..4780c542 100644
--- a/src/modules/geospatial/dto/get-addresses-response.dto.ts
+++ b/src/modules/geospatial/dto/get-addresses-response.dto.ts
@@ -6,13 +6,13 @@ export type GetAddressesResponse = GetAddressesResponseItem[];
 export class GetAddressesResponseItem {
   @ApiProperty({
     description: 'Organisation name if available',
-    example: 'CHURCHILL MUSEUM & CABINET WAR ROOMS',
+    example: GEOSPATIAL.EXAMPLES.ORGANISATION_NAME,
   })
   readonly organisationName: string | null;
 
   @ApiProperty({
     description: 'Address line 1',
-    example: 'CLIVE STEPS  KING CHARLES STREET',
+    example: GEOSPATIAL.EXAMPLES.ADDRESS_LINE_1,
   })
   readonly addressLine1: string;
 
@@ -30,7 +30,7 @@ export class GetAddressesResponseItem {
 
   @ApiProperty({
     description: 'Locality, Town',
-    example: 'LONDON',
+    example: GEOSPATIAL.EXAMPLES.LOCALITY,
   })
   readonly locality: string | null;
 

From c7ac787dba058ab17b95865681d2b9afe7a4e005 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:16:25 +0100
Subject: [PATCH 29/56] feat(DTFS2-7052): updating spec snapshot

---
 test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap b/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
index d08cd5fe..ee1a49e7 100644
--- a/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
+++ b/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
@@ -1146,7 +1146,7 @@ components:
         addressLine1:
           type: string
           description: Address line 1
-          example: CLIVE STEPS  KING CHARLES STREET
+          example: CLIVE STEPS KING CHARLES STREET
         addressLine2:
           type: string
           description: Address line 2

From 14ea1e6e2e7893bb136e352f228d73a0ac54b1b5 Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:25:06 +0100
Subject: [PATCH 30/56] Update
 src/modules/geospatial/dto/get-addresses-response.dto.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 src/modules/geospatial/dto/get-addresses-response.dto.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/modules/geospatial/dto/get-addresses-response.dto.ts b/src/modules/geospatial/dto/get-addresses-response.dto.ts
index 4780c542..1fb6c90c 100644
--- a/src/modules/geospatial/dto/get-addresses-response.dto.ts
+++ b/src/modules/geospatial/dto/get-addresses-response.dto.ts
@@ -19,6 +19,7 @@ export class GetAddressesResponseItem {
   @ApiProperty({
     description: 'Address line 2',
     example: null,
+    nullable: true,
   })
   readonly addressLine2: string | null;
 

From 1c0bf2c4135eca7474894f1edb4ec929ce482d53 Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:26:04 +0100
Subject: [PATCH 31/56] Update
 src/modules/geospatial/dto/get-addresses-response.dto.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 src/modules/geospatial/dto/get-addresses-response.dto.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/modules/geospatial/dto/get-addresses-response.dto.ts b/src/modules/geospatial/dto/get-addresses-response.dto.ts
index 1fb6c90c..fbdf2344 100644
--- a/src/modules/geospatial/dto/get-addresses-response.dto.ts
+++ b/src/modules/geospatial/dto/get-addresses-response.dto.ts
@@ -26,6 +26,7 @@ export class GetAddressesResponseItem {
   @ApiProperty({
     description: 'Address line 3',
     example: null,
+    nullable: true,
   })
   readonly addressLine3: string | null;
 

From 129b364a0913632a98072d2047ed410659a81642 Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:26:58 +0100
Subject: [PATCH 32/56] Update
 src/modules/geospatial/dto/get-addresses-response.dto.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 src/modules/geospatial/dto/get-addresses-response.dto.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/modules/geospatial/dto/get-addresses-response.dto.ts b/src/modules/geospatial/dto/get-addresses-response.dto.ts
index fbdf2344..cace3bf8 100644
--- a/src/modules/geospatial/dto/get-addresses-response.dto.ts
+++ b/src/modules/geospatial/dto/get-addresses-response.dto.ts
@@ -39,6 +39,7 @@ export class GetAddressesResponseItem {
   @ApiProperty({
     description: 'Postcode',
     example: GEOSPATIAL.EXAMPLES.POSTCODE,
+    nullable: true,
   })
   readonly postalCode: string | null;
 

From 273300312e40e66a634c42590cc0f370fb0c527b Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:27:32 +0100
Subject: [PATCH 33/56] Update
 src/modules/geospatial/dto/get-addresses-response.dto.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 src/modules/geospatial/dto/get-addresses-response.dto.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/modules/geospatial/dto/get-addresses-response.dto.ts b/src/modules/geospatial/dto/get-addresses-response.dto.ts
index cace3bf8..fce2bcdd 100644
--- a/src/modules/geospatial/dto/get-addresses-response.dto.ts
+++ b/src/modules/geospatial/dto/get-addresses-response.dto.ts
@@ -47,6 +47,7 @@ export class GetAddressesResponseItem {
     description: 'Country of address record',
     example: ENUMS.GEOSPATIAL_COUNTRIES.E,
     enum: ENUMS.GEOSPATIAL_COUNTRIES,
+    nullable: true,
   })
   readonly country: string | null;
 }

From fa73657434c73236ae9efa93036283cc110b4c86 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:28:57 +0100
Subject: [PATCH 34/56] feat(DTFS2-7052): updating api spec definition

---
 src/modules/geospatial/dto/get-addresses-response.dto.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/modules/geospatial/dto/get-addresses-response.dto.ts b/src/modules/geospatial/dto/get-addresses-response.dto.ts
index fce2bcdd..7bd22301 100644
--- a/src/modules/geospatial/dto/get-addresses-response.dto.ts
+++ b/src/modules/geospatial/dto/get-addresses-response.dto.ts
@@ -5,8 +5,9 @@ export type GetAddressesResponse = GetAddressesResponseItem[];
 
 export class GetAddressesResponseItem {
   @ApiProperty({
-    description: 'Organisation name if available',
+    description: 'Organisation name, if available',
     example: GEOSPATIAL.EXAMPLES.ORGANISATION_NAME,
+    nullable: true,
   })
   readonly organisationName: string | null;
 

From 3e215d726496ffb206c1e1b954efc9c27c4ec685 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:30:28 +0100
Subject: [PATCH 35/56] feat(DTFS2-7052): updating api spec snapshot

---
 test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap b/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
index ee1a49e7..c6792f2a 100644
--- a/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
+++ b/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
@@ -1141,8 +1141,9 @@ components:
       properties:
         organisationName:
           type: string
-          description: Organisation name if available
+          description: Organisation name, if available
           example: CHURCHILL MUSEUM & CABINET WAR ROOMS
+          nullable: true
         addressLine1:
           type: string
           description: Address line 1
@@ -1151,10 +1152,12 @@ components:
           type: string
           description: Address line 2
           example: null
+          nullable: true
         addressLine3:
           type: string
           description: Address line 3
           example: null
+          nullable: true
         locality:
           type: string
           description: Locality, Town
@@ -1163,6 +1166,7 @@ components:
           type: string
           description: Postcode
           example: SW1A 2AQ
+          nullable: true
         country:
           type: string
           description: Country of address record
@@ -1172,6 +1176,7 @@ components:
             - Scotland
             - Wales
             - Northern Ireland
+          nullable: true
       required:
         - organisationName
         - addressLine1

From 70e5861a5b2f63f0c091c906942e6b68e750dda7 Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:34:22 +0100
Subject: [PATCH 36/56] Update src/modules/geospatial/geospatial.controller.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 src/modules/geospatial/geospatial.controller.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/modules/geospatial/geospatial.controller.ts b/src/modules/geospatial/geospatial.controller.ts
index 95813d93..6e015641 100644
--- a/src/modules/geospatial/geospatial.controller.ts
+++ b/src/modules/geospatial/geospatial.controller.ts
@@ -13,7 +13,8 @@ export class GeospatialController {
   @Get('addresses/postcode')
   @ApiOperation({
     summary:
-      "A search based on a property's postcode. Will accept a full valid postcode. Returns addresses from Ordnance survey Delivery Point Address (DPA) system.",
+      "A search based on a property's postcode. Will accept a full valid postcode. Returns addresses from Ordnance Survey Delivery Point Address (DPA) system.",
+``
   })
   @ApiResponse({
     status: 200,

From 021bdd4ffd8583e0547a388e3ecc65bb69728abe Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:35:05 +0100
Subject: [PATCH 37/56] Update src/modules/geospatial/geospatial.controller.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 src/modules/geospatial/geospatial.controller.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/geospatial/geospatial.controller.ts b/src/modules/geospatial/geospatial.controller.ts
index 6e015641..9dba9e97 100644
--- a/src/modules/geospatial/geospatial.controller.ts
+++ b/src/modules/geospatial/geospatial.controller.ts
@@ -22,7 +22,7 @@ export class GeospatialController {
     type: [GetAddressesResponseItem],
   })
   @ApiNotFoundResponse({
-    description: 'Customer not found.',
+    description: 'Postcode not found.',
   })
   getAddressesByPostcode(@Query() query: GetAddressesByPostcodeQueryDto): Promise<GetAddressesResponse> {
     return this.geospatialService.getAddressesByPostcode(query.postcode);

From d5468340525702a8dadc4625a05e1408941f6d2b Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:35:42 +0100
Subject: [PATCH 38/56] Update
 src/modules/geospatial/geospatial.controller.test.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 src/modules/geospatial/geospatial.controller.test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/geospatial/geospatial.controller.test.ts b/src/modules/geospatial/geospatial.controller.test.ts
index 700914e2..9ff0b25e 100644
--- a/src/modules/geospatial/geospatial.controller.test.ts
+++ b/src/modules/geospatial/geospatial.controller.test.ts
@@ -32,7 +32,7 @@ describe('GeospatialController', () => {
   describe('getAddressesByPostcode()', () => {
     const postcode = GEOSPATIAL.EXAMPLES.POSTCODE;
 
-    it('returns address for postcode', async () => {
+    it('returns a single address for the postcode when the service returns a single address', async () => {
       when(geospatialServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressByPostcodeResponse[0]);
 
       const response = await controller.getAddressesByPostcode({ postcode });

From 5cc241295bed81b5a16f158fc68bdda8383b919d Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:37:02 +0100
Subject: [PATCH 39/56] Update
 src/modules/geospatial/geospatial.controller.test.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 src/modules/geospatial/geospatial.controller.test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/geospatial/geospatial.controller.test.ts b/src/modules/geospatial/geospatial.controller.test.ts
index 9ff0b25e..aca862af 100644
--- a/src/modules/geospatial/geospatial.controller.test.ts
+++ b/src/modules/geospatial/geospatial.controller.test.ts
@@ -37,7 +37,7 @@ describe('GeospatialController', () => {
 
       const response = await controller.getAddressesByPostcode({ postcode });
 
-      expect(geospatialServiceGetAddressesByPostcode).toHaveBeenCalled();
+      expect(geospatialServiceGetAddressesByPostcode).toHaveBeenCalledTimes(1);
       expect(response).toEqual(getAddressByPostcodeResponse[0]);
     });
 

From ad617e22d5928fd4b9dc2d2272565ed27c9867dc Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:39:58 +0100
Subject: [PATCH 40/56] Update
 test/geospatial/get-address-by-postcode.api-test.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 test/geospatial/get-address-by-postcode.api-test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/geospatial/get-address-by-postcode.api-test.ts b/test/geospatial/get-address-by-postcode.api-test.ts
index 64f0283b..ed416c41 100644
--- a/test/geospatial/get-address-by-postcode.api-test.ts
+++ b/test/geospatial/get-address-by-postcode.api-test.ts
@@ -44,7 +44,7 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
   // MDM auth tests
   withClientAuthenticationTests({
     givenTheRequestWouldOtherwiseSucceed: () => {
-      requestToGetAddressesByPostcode(mdmPath[0]).reply(200, getAddressOrdnanceSurveyResponse[0]);
+      requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(200, getAddressOrdnanceSurveyResponse[0]);
     },
     makeRequestWithoutAuth: (incorrectAuth?: IncorrectAuthArg) => api.getWithoutAuth(mdmPath[0], incorrectAuth?.headerName, incorrectAuth?.headerValue),
   });

From 67d682e53fdfa470c60a2154550ca95a7092e20e Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:43:22 +0100
Subject: [PATCH 41/56] Update
 src/modules/geospatial/geospatial.service.test.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 src/modules/geospatial/geospatial.service.test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/geospatial/geospatial.service.test.ts b/src/modules/geospatial/geospatial.service.test.ts
index 2b34b577..44355a66 100644
--- a/src/modules/geospatial/geospatial.service.test.ts
+++ b/src/modules/geospatial/geospatial.service.test.ts
@@ -65,7 +65,7 @@ describe('GeospatialService', () => {
     });
 
     it('returns addressLine1 formatted correctly even if middle value is missing', async () => {
-      const [modifiedOrdnanceSurveyResponse] = getAddressOrdnanceSurveyResponse;
+      const [modifiedOrdnanceSurveyResponse] = structuredClone(getAddressOrdnanceSurveyResponse);
       modifiedOrdnanceSurveyResponse.results[0].DPA.BUILDING_NUMBER = null;
       const address = modifiedOrdnanceSurveyResponse.results[0].DPA;
       when(informaticaServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(modifiedOrdnanceSurveyResponse);

From 708533badb0ee88754e50b07c8af7241cea831d4 Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:45:25 +0100
Subject: [PATCH 42/56] Update
 test/geospatial/get-address-by-postcode.api-test.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 test/geospatial/get-address-by-postcode.api-test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/geospatial/get-address-by-postcode.api-test.ts b/test/geospatial/get-address-by-postcode.api-test.ts
index ed416c41..081fadb7 100644
--- a/test/geospatial/get-address-by-postcode.api-test.ts
+++ b/test/geospatial/get-address-by-postcode.api-test.ts
@@ -67,7 +67,7 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
     expect(body).toStrictEqual(getAddressByPostcodeMultipleResponse);
   });
 
-  it('returns a empty 200 response if Ordnance Survey API returns a 200 without results', async () => {
+  it('returns an empty 200 response if Ordnance Survey API returns a 200 without results', async () => {
     requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(200, getAddressOrdnanceSurveyEmptyResponse[0]);
 
     const { status, body } = await api.get(mdmPath[0]);

From 7d15b07ef4126f99ffcf6189deac2b2391633edd Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 10:49:50 +0100
Subject: [PATCH 43/56] feat(DTFS2-7052): actioning PR comments

---
 .../geospatial/dto/get-addresses-response.dto.ts   |  3 ++-
 src/modules/geospatial/geospatial.service.test.ts  | 14 +++++++-------
 src/modules/mdm.module.ts                          |  3 ---
 3 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/src/modules/geospatial/dto/get-addresses-response.dto.ts b/src/modules/geospatial/dto/get-addresses-response.dto.ts
index 7bd22301..87e997a1 100644
--- a/src/modules/geospatial/dto/get-addresses-response.dto.ts
+++ b/src/modules/geospatial/dto/get-addresses-response.dto.ts
@@ -32,8 +32,9 @@ export class GetAddressesResponseItem {
   readonly addressLine3: string | null;
 
   @ApiProperty({
-    description: 'Locality, Town',
+    description: 'Locality or town',
     example: GEOSPATIAL.EXAMPLES.LOCALITY,
+    nullable: true,
   })
   readonly locality: string | null;
 
diff --git a/src/modules/geospatial/geospatial.service.test.ts b/src/modules/geospatial/geospatial.service.test.ts
index 2b34b577..a656837d 100644
--- a/src/modules/geospatial/geospatial.service.test.ts
+++ b/src/modules/geospatial/geospatial.service.test.ts
@@ -13,16 +13,16 @@ describe('GeospatialService', () => {
 
   let service: GeospatialService;
   let configServiceGet: jest.Mock;
-  let informaticaServiceGetAddressesByPostcode: jest.Mock;
+  let ordnanceSurveyServiceGetAddressesByPostcode: jest.Mock;
 
   beforeEach(() => {
     const configService = new ConfigService();
     configServiceGet = jest.fn().mockReturnValue({ key: valueGenerator.word() });
     configService.get = configServiceGet;
 
-    informaticaServiceGetAddressesByPostcode = jest.fn();
+    ordnanceSurveyServiceGetAddressesByPostcode = jest.fn();
     const ordnanceSurveyService = new OrdnanceSurveyService(null, configService);
-    ordnanceSurveyService.getAddressesByPostcode = informaticaServiceGetAddressesByPostcode;
+    ordnanceSurveyService.getAddressesByPostcode = ordnanceSurveyServiceGetAddressesByPostcode;
     resetAllWhenMocks();
 
     service = new GeospatialService(ordnanceSurveyService);
@@ -41,7 +41,7 @@ describe('GeospatialService', () => {
     const postcode = getAddressByPostcodeResponse[0][0].postalCode;
 
     it('returns a single address from the backend service', async () => {
-      when(informaticaServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressOrdnanceSurveyResponse[0]);
+      when(ordnanceSurveyServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressOrdnanceSurveyResponse[0]);
 
       const response = await service.getAddressesByPostcode(postcode);
 
@@ -49,7 +49,7 @@ describe('GeospatialService', () => {
     });
 
     it('returns multiple addressess from the backend service', async () => {
-      when(informaticaServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressOrdnanceSurveyMultipleResponse);
+      when(ordnanceSurveyServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressOrdnanceSurveyMultipleResponse);
 
       const response = await service.getAddressesByPostcode(postcode);
 
@@ -57,7 +57,7 @@ describe('GeospatialService', () => {
     });
 
     it('can handle empty backend response', async () => {
-      when(informaticaServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressOrdnanceSurveyEmptyResponse[0]);
+      when(ordnanceSurveyServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressOrdnanceSurveyEmptyResponse[0]);
 
       const response = await service.getAddressesByPostcode(postcode);
 
@@ -68,7 +68,7 @@ describe('GeospatialService', () => {
       const [modifiedOrdnanceSurveyResponse] = getAddressOrdnanceSurveyResponse;
       modifiedOrdnanceSurveyResponse.results[0].DPA.BUILDING_NUMBER = null;
       const address = modifiedOrdnanceSurveyResponse.results[0].DPA;
-      when(informaticaServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(modifiedOrdnanceSurveyResponse);
+      when(ordnanceSurveyServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(modifiedOrdnanceSurveyResponse);
 
       const response = await service.getAddressesByPostcode(postcode);
 
diff --git a/src/modules/mdm.module.ts b/src/modules/mdm.module.ts
index 9d3b7bfb..790a1510 100644
--- a/src/modules/mdm.module.ts
+++ b/src/modules/mdm.module.ts
@@ -1,7 +1,6 @@
 import { Module } from '@nestjs/common';
 import { AuthModule } from '@ukef/auth/auth.module';
 import { DatabaseModule } from '@ukef/database/database.module';
-import { OrdnanceSurveyModule } from '@ukef/helper-modules/ordnance-survey/ordnance-survey.module';
 import { CurrenciesModule } from '@ukef/modules/currencies/currencies.module';
 import { CustomersModule } from '@ukef/modules/customers/customers.module';
 import { ExposurePeriodModule } from '@ukef/modules/exposure-period/exposure-period.module';
@@ -28,7 +27,6 @@ import { YieldRatesModule } from '@ukef/modules/yield-rates/yield-rates.module';
     PremiumSchedulesModule,
     SectorIndustriesModule,
     YieldRatesModule,
-    OrdnanceSurveyModule,
     GeospatialModule,
   ],
   exports: [
@@ -44,7 +42,6 @@ import { YieldRatesModule } from '@ukef/modules/yield-rates/yield-rates.module';
     PremiumSchedulesModule,
     SectorIndustriesModule,
     YieldRatesModule,
-    OrdnanceSurveyModule,
     GeospatialModule,
   ],
 })

From 15743c043b478d660097194f54c396eebe2c352f Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 11:28:16 +0100
Subject: [PATCH 44/56] feat(DTFS2-7052): updating API spec

---
 test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap b/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
index c6792f2a..3bcf1ea2 100644
--- a/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
+++ b/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
@@ -491,7 +491,7 @@ paths:
       operationId: GeospatialController_getAddressesByPostcode
       summary: >-
         A search based on a property's postcode. Will accept a full valid
-        postcode. Returns addresses from Ordnance survey Delivery Point Address
+        postcode. Returns addresses from Ordnance Survey Delivery Point Address
         (DPA) system.
       parameters:
         - name: postcode
@@ -515,7 +515,7 @@ paths:
                 items:
                   $ref: '#/components/schemas/GetAddressesResponseItem'
         '404':
-          description: Customer not found.
+          description: Postcode not found.
       tags:
         - geospatial
 info:
@@ -1160,8 +1160,9 @@ components:
           nullable: true
         locality:
           type: string
-          description: Locality, Town
+          description: Locality or town
           example: LONDON
+          nullable: true
         postalCode:
           type: string
           description: Postcode

From c020867afb451723a8f7d361cb33ef54566355ad Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 12:06:48 +0100
Subject: [PATCH 45/56] feat(DTFS2-7052): changing variables to use plural

---
 .../geospatial/geospatial.controller.test.ts  | 10 ++--
 .../geospatial/geospatial.service.test.ts     | 24 ++++-----
 .../get-address-by-postcode.api-test.ts       | 50 +++++++++----------
 .../get-geospatial-addresses-generator.ts     | 28 +++++------
 4 files changed, 56 insertions(+), 56 deletions(-)

diff --git a/src/modules/geospatial/geospatial.controller.test.ts b/src/modules/geospatial/geospatial.controller.test.ts
index aca862af..d125cf04 100644
--- a/src/modules/geospatial/geospatial.controller.test.ts
+++ b/src/modules/geospatial/geospatial.controller.test.ts
@@ -12,7 +12,7 @@ describe('GeospatialController', () => {
   let controller: GeospatialController;
 
   const valueGenerator = new RandomValueGenerator();
-  const { getAddressByPostcodeResponse, getAddressByPostcodeMultipleResponse } = new GetGeospatialAddressesGenerator(valueGenerator).generate({
+  const { getAddressesByPostcodeResponse, getAddressesByPostcodeMultipleResponse } = new GetGeospatialAddressesGenerator(valueGenerator).generate({
     numberToGenerate: 2,
   });
 
@@ -33,21 +33,21 @@ describe('GeospatialController', () => {
     const postcode = GEOSPATIAL.EXAMPLES.POSTCODE;
 
     it('returns a single address for the postcode when the service returns a single address', async () => {
-      when(geospatialServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressByPostcodeResponse[0]);
+      when(geospatialServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressesByPostcodeResponse[0]);
 
       const response = await controller.getAddressesByPostcode({ postcode });
 
       expect(geospatialServiceGetAddressesByPostcode).toHaveBeenCalledTimes(1);
-      expect(response).toEqual(getAddressByPostcodeResponse[0]);
+      expect(response).toEqual(getAddressesByPostcodeResponse[0]);
     });
 
     it('returns multiple addresses for the postcode when the service returns multiple addresses', async () => {
-      when(geospatialServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressByPostcodeMultipleResponse);
+      when(geospatialServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressesByPostcodeMultipleResponse);
 
       const response = await controller.getAddressesByPostcode({ postcode });
 
       expect(geospatialServiceGetAddressesByPostcode).toHaveBeenCalledTimes(1);
-      expect(response).toEqual(getAddressByPostcodeMultipleResponse);
+      expect(response).toEqual(getAddressesByPostcodeMultipleResponse);
     });
 
     it('returns an empty response for the postcode when the service returns an empty response', async () => {
diff --git a/src/modules/geospatial/geospatial.service.test.ts b/src/modules/geospatial/geospatial.service.test.ts
index 1cd89fdb..1718118f 100644
--- a/src/modules/geospatial/geospatial.service.test.ts
+++ b/src/modules/geospatial/geospatial.service.test.ts
@@ -30,34 +30,34 @@ describe('GeospatialService', () => {
 
   describe('getAddressesByPostcode', () => {
     const {
-      getAddressByPostcodeResponse,
-      getAddressByPostcodeMultipleResponse,
-      getAddressOrdnanceSurveyResponse,
-      getAddressOrdnanceSurveyMultipleResponse,
-      getAddressOrdnanceSurveyEmptyResponse,
+      getAddressesByPostcodeResponse,
+      getAddressesByPostcodeMultipleResponse,
+      getAddressesOrdnanceSurveyResponse,
+      getAddressesOrdnanceSurveyMultipleMatchingAddressesResponse,
+      getAddressesOrdnanceSurveyEmptyResponse,
     } = new GetGeospatialAddressesGenerator(valueGenerator).generate({
       numberToGenerate: 2,
     });
-    const postcode = getAddressByPostcodeResponse[0][0].postalCode;
+    const postcode = getAddressesByPostcodeResponse[0][0].postalCode;
 
     it('returns a single address from the backend service', async () => {
-      when(ordnanceSurveyServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressOrdnanceSurveyResponse[0]);
+      when(ordnanceSurveyServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressesOrdnanceSurveyResponse[0]);
 
       const response = await service.getAddressesByPostcode(postcode);
 
-      expect(response).toEqual(getAddressByPostcodeResponse[0]);
+      expect(response).toEqual(getAddressesByPostcodeResponse[0]);
     });
 
     it('returns multiple addressess from the backend service', async () => {
-      when(ordnanceSurveyServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressOrdnanceSurveyMultipleResponse);
+      when(ordnanceSurveyServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressesOrdnanceSurveyMultipleMatchingAddressesResponse);
 
       const response = await service.getAddressesByPostcode(postcode);
 
-      expect(response).toEqual(getAddressByPostcodeMultipleResponse);
+      expect(response).toEqual(getAddressesByPostcodeMultipleResponse);
     });
 
     it('can handle empty backend response', async () => {
-      when(ordnanceSurveyServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressOrdnanceSurveyEmptyResponse[0]);
+      when(ordnanceSurveyServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressesOrdnanceSurveyEmptyResponse[0]);
 
       const response = await service.getAddressesByPostcode(postcode);
 
@@ -65,7 +65,7 @@ describe('GeospatialService', () => {
     });
 
     it('returns addressLine1 formatted correctly even if middle value is missing', async () => {
-      const [modifiedOrdnanceSurveyResponse] = structuredClone(getAddressOrdnanceSurveyResponse);
+      const [modifiedOrdnanceSurveyResponse] = structuredClone(getAddressesOrdnanceSurveyResponse);
       modifiedOrdnanceSurveyResponse.results[0].DPA.BUILDING_NUMBER = null;
       const address = modifiedOrdnanceSurveyResponse.results[0].DPA;
       when(ordnanceSurveyServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(modifiedOrdnanceSurveyResponse);
diff --git a/test/geospatial/get-address-by-postcode.api-test.ts b/test/geospatial/get-address-by-postcode.api-test.ts
index 081fadb7..37002809 100644
--- a/test/geospatial/get-address-by-postcode.api-test.ts
+++ b/test/geospatial/get-address-by-postcode.api-test.ts
@@ -12,13 +12,13 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
   let api: Api;
 
   const {
-    ordnanceSurveyPath,
-    mdmPath,
-    getAddressByPostcodeResponse,
-    getAddressByPostcodeMultipleResponse,
-    getAddressOrdnanceSurveyResponse,
-    getAddressOrdnanceSurveyEmptyResponse,
-    getAddressOrdnanceSurveyMultipleResponse,
+    ordnanceSurveyPaths,
+    mdmPaths,
+    getAddressesByPostcodeResponse,
+    getAddressesByPostcodeMultipleResponse,
+    getAddressesOrdnanceSurveyResponse,
+    getAddressesOrdnanceSurveyEmptyResponse,
+    getAddressesOrdnanceSurveyMultipleMatchingAddressesResponse,
     ordnanceSurveyAuthErrorResponse,
   } = new GetGeospatialAddressesGenerator(valueGenerator).generate({
     postcode: GEOSPATIAL.EXAMPLES.POSTCODE,
@@ -44,42 +44,42 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
   // MDM auth tests
   withClientAuthenticationTests({
     givenTheRequestWouldOtherwiseSucceed: () => {
-      requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(200, getAddressOrdnanceSurveyResponse[0]);
+      requestToGetAddressesByPostcode(ordnanceSurveyPaths[0]).reply(200, getAddressesOrdnanceSurveyResponse[0]);
     },
-    makeRequestWithoutAuth: (incorrectAuth?: IncorrectAuthArg) => api.getWithoutAuth(mdmPath[0], incorrectAuth?.headerName, incorrectAuth?.headerValue),
+    makeRequestWithoutAuth: (incorrectAuth?: IncorrectAuthArg) => api.getWithoutAuth(mdmPaths[0], incorrectAuth?.headerName, incorrectAuth?.headerValue),
   });
 
   it('returns a 200 response with the address if it is returned by Ordnance Survey API', async () => {
-    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(200, getAddressOrdnanceSurveyResponse[0]);
+    requestToGetAddressesByPostcode(ordnanceSurveyPaths[0]).reply(200, getAddressesOrdnanceSurveyResponse[0]);
 
-    const { status, body } = await api.get(mdmPath[0]);
+    const { status, body } = await api.get(mdmPaths[0]);
 
     expect(status).toBe(200);
-    expect(body).toStrictEqual(getAddressByPostcodeResponse[0]);
+    expect(body).toStrictEqual(getAddressesByPostcodeResponse[0]);
   });
 
   it('returns a 200 response with the addresses if they are returned by Ordnance Survey API', async () => {
-    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(200, getAddressOrdnanceSurveyMultipleResponse);
+    requestToGetAddressesByPostcode(ordnanceSurveyPaths[0]).reply(200, getAddressesOrdnanceSurveyMultipleMatchingAddressesResponse);
 
-    const { status, body } = await api.get(mdmPath[0]);
+    const { status, body } = await api.get(mdmPaths[0]);
 
     expect(status).toBe(200);
-    expect(body).toStrictEqual(getAddressByPostcodeMultipleResponse);
+    expect(body).toStrictEqual(getAddressesByPostcodeMultipleResponse);
   });
 
   it('returns an empty 200 response if Ordnance Survey API returns a 200 without results', async () => {
-    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(200, getAddressOrdnanceSurveyEmptyResponse[0]);
+    requestToGetAddressesByPostcode(ordnanceSurveyPaths[0]).reply(200, getAddressesOrdnanceSurveyEmptyResponse[0]);
 
-    const { status, body } = await api.get(mdmPath[0]);
+    const { status, body } = await api.get(mdmPaths[0]);
 
     expect(status).toBe(200);
     expect(body).toStrictEqual([]);
   });
 
   it('returns a 500 response if Ordnance Survey API returns a status code 401', async () => {
-    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(401);
+    requestToGetAddressesByPostcode(ordnanceSurveyPaths[0]).reply(401);
 
-    const { status, body } = await api.get(mdmPath[0]);
+    const { status, body } = await api.get(mdmPaths[0]);
 
     expect(status).toBe(500);
     expect(body).toStrictEqual({
@@ -89,9 +89,9 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
   });
 
   it('returns a 500 response if Ordnance Survey API returns a status code 404', async () => {
-    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(404);
+    requestToGetAddressesByPostcode(ordnanceSurveyPaths[0]).reply(404);
 
-    const { status, body } = await api.get(mdmPath[0]);
+    const { status, body } = await api.get(mdmPaths[0]);
 
     expect(status).toBe(500);
     expect(body).toStrictEqual({
@@ -101,9 +101,9 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
   });
 
   it('returns a 500 response if Ordnance Survey API times out', async () => {
-    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).delay(TIME_EXCEEDING_ORDNANCE_SURVEY_TIMEOUT).reply(200, getAddressOrdnanceSurveyResponse[0]);
+    requestToGetAddressesByPostcode(ordnanceSurveyPaths[0]).delay(TIME_EXCEEDING_ORDNANCE_SURVEY_TIMEOUT).reply(200, getAddressesOrdnanceSurveyResponse[0]);
 
-    const { status, body } = await api.get(mdmPath[0]);
+    const { status, body } = await api.get(mdmPaths[0]);
 
     expect(status).toBe(500);
     expect(body).toStrictEqual({
@@ -113,9 +113,9 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
   });
 
   it('returns a 500 response if Ordnance Survey API returns error', async () => {
-    requestToGetAddressesByPostcode(ordnanceSurveyPath[0]).reply(401, ordnanceSurveyAuthErrorResponse);
+    requestToGetAddressesByPostcode(ordnanceSurveyPaths[0]).reply(401, ordnanceSurveyAuthErrorResponse);
 
-    const { status, body } = await api.get(mdmPath[0]);
+    const { status, body } = await api.get(mdmPaths[0]);
 
     expect(status).toBe(500);
     expect(body).toStrictEqual({
diff --git a/test/support/generator/get-geospatial-addresses-generator.ts b/test/support/generator/get-geospatial-addresses-generator.ts
index 51522d9f..02102b34 100644
--- a/test/support/generator/get-geospatial-addresses-generator.ts
+++ b/test/support/generator/get-geospatial-addresses-generator.ts
@@ -189,13 +189,13 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
 
     return {
       requests,
-      ordnanceSurveyPath: ordnanceSurveyPaths,
-      mdmPath: mdmPaths,
-      getAddressByPostcodeResponse: getAddressesByPostcodeResponse,
-      getAddressByPostcodeMultipleResponse: getAddressesByPostcodeMultipleResponse,
-      getAddressOrdnanceSurveyResponse: getAddressesOrdnanceSurveyResponse,
-      getAddressOrdnanceSurveyEmptyResponse: getAddressesOrdnanceSurveyEmptyResponse,
-      getAddressOrdnanceSurveyMultipleResponse: getAddressesOrdnanceSurveyMultipleMatchingAddressesResponse,
+      ordnanceSurveyPaths,
+      mdmPaths,
+      getAddressesByPostcodeResponse,
+      getAddressesByPostcodeMultipleResponse,
+      getAddressesOrdnanceSurveyResponse,
+      getAddressesOrdnanceSurveyEmptyResponse,
+      getAddressesOrdnanceSurveyMultipleMatchingAddressesResponse,
       ordnanceSurveyAuthErrorResponse,
     };
   }
@@ -219,12 +219,12 @@ interface GenerateOptions {
 
 interface GenerateResult {
   requests: GetAddressesByPostcodeQueryDto[];
-  ordnanceSurveyPath: string[];
-  mdmPath: string[];
-  getAddressByPostcodeResponse: GetAddressesResponse[];
-  getAddressByPostcodeMultipleResponse: GetAddressesResponse;
-  getAddressOrdnanceSurveyResponse: GetAddressesOrdnanceSurveyResponse[];
-  getAddressOrdnanceSurveyMultipleResponse: GetAddressesOrdnanceSurveyResponse;
-  getAddressOrdnanceSurveyEmptyResponse: GetAddressesOrdnanceSurveyResponse;
+  ordnanceSurveyPaths: string[];
+  mdmPaths: string[];
+  getAddressesByPostcodeResponse: GetAddressesResponse[];
+  getAddressesByPostcodeMultipleResponse: GetAddressesResponse;
+  getAddressesOrdnanceSurveyResponse: GetAddressesOrdnanceSurveyResponse[];
+  getAddressesOrdnanceSurveyMultipleMatchingAddressesResponse: GetAddressesOrdnanceSurveyResponse;
+  getAddressesOrdnanceSurveyEmptyResponse: GetAddressesOrdnanceSurveyResponse;
   ordnanceSurveyAuthErrorResponse: OrdnanceSurveyAuthErrorResponse;
 }

From e1ecd0d88102aa70412a17f9938489c76c077f2d Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 14:43:31 +0100
Subject: [PATCH 46/56] Update
 test/support/generator/get-geospatial-addresses-generator.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 test/support/generator/get-geospatial-addresses-generator.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/support/generator/get-geospatial-addresses-generator.ts b/test/support/generator/get-geospatial-addresses-generator.ts
index 02102b34..347cb262 100644
--- a/test/support/generator/get-geospatial-addresses-generator.ts
+++ b/test/support/generator/get-geospatial-addresses-generator.ts
@@ -14,10 +14,10 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
 
   protected generateValues(): AddressValues {
     return {
-      ORGANISATION_NAME: this.valueGenerator.sentence({ words: 2 }),
+      ORGANISATION_NAME: this.valueGenerator.sentence({ words: 5 }),
       BUILDING_NAME: this.valueGenerator.word(),
       BUILDING_NUMBER: this.valueGenerator.nonnegativeInteger().toString(),
-      THOROUGHFARE_NAME: this.valueGenerator.sentence({ words: 5 }),
+      THOROUGHFARE_NAME: this.valueGenerator.sentence({ words: 2 }),
       DEPENDENT_LOCALITY: this.valueGenerator.word(),
       POST_TOWN: this.valueGenerator.word(),
       POSTCODE: this.valueGenerator.postcode(),

From a6f33b4037fa5907f2a3f69398571a1385d5e891 Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 14:44:54 +0100
Subject: [PATCH 47/56] Update
 src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 .../ordnance-survey/ordnance-survey.service.test.ts             | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts b/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
index 611cc5b1..b4f0d769 100644
--- a/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
+++ b/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
@@ -62,7 +62,7 @@ describe('OrdnanceSurveyService', () => {
         expectedUrlQueryPart: '?postcode=W1A1AA',
       },
     ])('test postcode $postcode', ({ postcode, expectedUrlQueryPart }) => {
-      it('call Ordnance Survey with the correct arguments', async () => {
+      it('calls Ordnance Survey with the correct arguments', async () => {
         const expectedPath = `${basePath}${expectedUrlQueryPart}&lr=EN&key=${encodeURIComponent(testKey)}`;
         const expectedHttpServiceGetArgs: [string, object] = [expectedPath, { headers: { 'Content-Type': 'application/json' } }];
 

From 370c3422dd404039c5c5f94169508a986169c47d Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 14:45:29 +0100
Subject: [PATCH 48/56] Update
 src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 .../ordnance-survey/ordnance-survey.service.test.ts             | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts b/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
index b4f0d769..4202d607 100644
--- a/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
+++ b/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
@@ -89,7 +89,7 @@ describe('OrdnanceSurveyService', () => {
       });
     });
 
-    it('returns a 200 response without results when Ordnance Survey returns no results', async () => {
+    it('returns no results without erroring when Ordnance Survey returns a 200 without results', async () => {
       when(httpServiceGet)
         .calledWith(...expectedHttpServiceGetArgs)
         .mockReturnValueOnce(

From 90eb4d385cf2988b48d3cce89dc7ea4099593b0c Mon Sep 17 00:00:00 2001
From: Audrius <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 14:45:44 +0100
Subject: [PATCH 49/56] Update
 src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts

Co-authored-by: oscar-richardson-softwire <116292912+oscar-richardson-softwire@users.noreply.github.com>
---
 .../ordnance-survey/ordnance-survey.service.test.ts             | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts b/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
index 4202d607..2bc641f9 100644
--- a/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
+++ b/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
@@ -75,7 +75,7 @@ describe('OrdnanceSurveyService', () => {
         expect(httpServiceGet).toHaveBeenCalledWith(...expectedHttpServiceGetArgs);
       });
 
-      it('call Ordnance Survey returns expectedResponse', async () => {
+      it('returns the results when Ordnance Survey returns a 200 with results', async () => {
         const expectedPath = `${basePath}${expectedUrlQueryPart}&lr=EN&key=${encodeURIComponent(testKey)}`;
         const expectedHttpServiceGetArgs: [string, object] = [expectedPath, { headers: { 'Content-Type': 'application/json' } }];
 

From 3ca57f0cfdbddf1ac1285164b0aa2c214e04a429 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Thu, 25 Apr 2024 15:12:18 +0100
Subject: [PATCH 50/56] feat(DTFS2-7049): adding new env variables to
 docker-compose.yml

---
 docker-compose.yml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/docker-compose.yml b/docker-compose.yml
index 159be608..b662d6e1 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -33,6 +33,10 @@ services:
       APIM_INFORMATICA_PASSWORD:
       APIM_INFORMATICA_MAX_REDIRECTS:
       APIM_INFORMATICA_TIMEOUT:
+      ORDNANCE_SURVEY_URL:
+      ORDNANCE_SURVEY_KEY:
+      ORDNANCE_SURVEY_MAX_REDIRECTS:
+      ORDNANCE_SURVEY_TIMEOUT:
       API_KEY:
     healthcheck:
       test: ["CMD", "curl", "-f", "http://localhost:${PORT}"]

From 32aa1ef3e41f4cf5e56ae1952b710d4e8a4fc1da Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Mon, 29 Apr 2024 14:04:57 +0100
Subject: [PATCH 51/56] feat(DTFS2-7052): removing unused class
 GetSearchPostcodeOrdnanceSurveyQueryDto

---
 .../dto/get-search-postcode-ordnance-survey-query.dto.ts       | 3 ---
 1 file changed, 3 deletions(-)
 delete mode 100644 src/helper-modules/ordnance-survey/dto/get-search-postcode-ordnance-survey-query.dto.ts

diff --git a/src/helper-modules/ordnance-survey/dto/get-search-postcode-ordnance-survey-query.dto.ts b/src/helper-modules/ordnance-survey/dto/get-search-postcode-ordnance-survey-query.dto.ts
deleted file mode 100644
index 8f8ea0f2..00000000
--- a/src/helper-modules/ordnance-survey/dto/get-search-postcode-ordnance-survey-query.dto.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export class GetSearchPostcodeOrdnanceSurveyQueryDto {
-  public postcode: string;
-}

From 38939db77917c9679311e6a793bd339b5f817a26 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Mon, 13 May 2024 09:23:01 +0100
Subject: [PATCH 52/56] feat(DTFS2-7052): code style improvements based on PR
 feedback

---
 .env.sample                                                  | 4 ++--
 src/config/informatica.config.test.ts                        | 2 +-
 src/config/informatica.config.ts                             | 2 +-
 src/config/ordnance-survey.config.test.ts                    | 2 +-
 src/config/ordnance-survey.config.ts                         | 2 +-
 src/constants/enums/geospatialCountries.ts                   | 2 +-
 src/constants/geospatial.constant.ts                         | 1 +
 src/helper-modules/ordnance-survey/ordnance-survey.module.ts | 4 ++--
 8 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/.env.sample b/.env.sample
index 90bced05..536e7f69 100644
--- a/.env.sample
+++ b/.env.sample
@@ -38,11 +38,11 @@ APIM_INFORMATICA_URL=
 APIM_INFORMATICA_USERNAME=
 APIM_INFORMATICA_PASSWORD=
 APIM_INFORMATICA_MAX_REDIRECTS=
-APIM_INFORMATICA_TIMEOUT=
+APIM_INFORMATICA_TIMEOUT= # in milliseconds
 
 # ORDNANCE SURVEY
 # Use .uk domain, instead of .co.uk
 ORDNANCE_SURVEY_URL=
 ORDNANCE_SURVEY_KEY=
 ORDNANCE_SURVEY_MAX_REDIRECTS=
-ORDNANCE_SURVEY_TIMEOUT=
+ORDNANCE_SURVEY_TIMEOUT= # in milliseconds
diff --git a/src/config/informatica.config.test.ts b/src/config/informatica.config.test.ts
index 97cf8d24..84a59cad 100644
--- a/src/config/informatica.config.test.ts
+++ b/src/config/informatica.config.test.ts
@@ -31,7 +31,7 @@ describe('informaticaConfig', () => {
     {
       configPropertyName: 'timeout',
       environmentVariableName: 'APIM_INFORMATICA_TIMEOUT',
-      defaultConfigValue: 30000,
+      defaultConfigValue: 30000, // in milliseconds
     },
   ];
 
diff --git a/src/config/informatica.config.ts b/src/config/informatica.config.ts
index fdf9dc67..6860bddb 100644
--- a/src/config/informatica.config.ts
+++ b/src/config/informatica.config.ts
@@ -18,6 +18,6 @@ export default registerAs(
     username: process.env.APIM_INFORMATICA_USERNAME,
     password: process.env.APIM_INFORMATICA_PASSWORD,
     maxRedirects: getIntConfig(process.env.APIM_INFORMATICA_MAX_REDIRECTS, 5),
-    timeout: getIntConfig(process.env.APIM_INFORMATICA_TIMEOUT, 30000),
+    timeout: getIntConfig(process.env.APIM_INFORMATICA_TIMEOUT, 30000), // in milliseconds
   }),
 );
diff --git a/src/config/ordnance-survey.config.test.ts b/src/config/ordnance-survey.config.test.ts
index 5b22020e..f077012d 100644
--- a/src/config/ordnance-survey.config.test.ts
+++ b/src/config/ordnance-survey.config.test.ts
@@ -27,7 +27,7 @@ describe('ordnanceSurveyConfig', () => {
     {
       configPropertyName: 'timeout',
       environmentVariableName: 'ORDNANCE_SURVEY_TIMEOUT',
-      defaultConfigValue: 30000,
+      defaultConfigValue: 30000, // in milliseconds
     },
   ];
 
diff --git a/src/config/ordnance-survey.config.ts b/src/config/ordnance-survey.config.ts
index e30acf8b..13da5637 100644
--- a/src/config/ordnance-survey.config.ts
+++ b/src/config/ordnance-survey.config.ts
@@ -16,6 +16,6 @@ export default registerAs(
     baseUrl: process.env.ORDNANCE_SURVEY_URL,
     key: process.env.ORDNANCE_SURVEY_KEY,
     maxRedirects: getIntConfig(process.env.ORDNANCE_SURVEY_MAX_REDIRECTS, 5),
-    timeout: getIntConfig(process.env.ORDNANCE_SURVEY_TIMEOUT, 30000),
+    timeout: getIntConfig(process.env.ORDNANCE_SURVEY_TIMEOUT, 30000), // in milliseconds
   }),
 );
diff --git a/src/constants/enums/geospatialCountries.ts b/src/constants/enums/geospatialCountries.ts
index f181289d..d6a4df1f 100644
--- a/src/constants/enums/geospatialCountries.ts
+++ b/src/constants/enums/geospatialCountries.ts
@@ -1,6 +1,6 @@
 export enum GeospatialCountriesEnum {
   E = 'England',
+  N = 'Northern Ireland',
   S = 'Scotland',
   W = 'Wales',
-  N = 'Northern Ireland',
 }
diff --git a/src/constants/geospatial.constant.ts b/src/constants/geospatial.constant.ts
index 567b89d2..d4d3102f 100644
--- a/src/constants/geospatial.constant.ts
+++ b/src/constants/geospatial.constant.ts
@@ -11,6 +11,7 @@ export const GEOSPATIAL = {
   },
   REGEX: {
     // UK postcode regex is from DTFS project and slightly optimised by lint.
+    // https://github.com/UK-Export-Finance/dtfs2/blob/main/portal-api/src/constants/regex.js
     UK_POSTCODE: /^[A-Za-z]{1,2}[\dRr][\dA-Za-z]?\s?\d[ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$/,
   },
 };
diff --git a/src/helper-modules/ordnance-survey/ordnance-survey.module.ts b/src/helper-modules/ordnance-survey/ordnance-survey.module.ts
index 2afb21df..dc5f11ce 100644
--- a/src/helper-modules/ordnance-survey/ordnance-survey.module.ts
+++ b/src/helper-modules/ordnance-survey/ordnance-survey.module.ts
@@ -11,9 +11,9 @@ import { OrdnanceSurveyService } from './ordnance-survey.service';
       imports: [ConfigModule],
       inject: [ConfigService],
       useFactory: (configService: ConfigService) => {
-        const { baseUrl, maxRedirects, timeout } = configService.get<OrdnanceSurveyConfig>(ORDNANCE_SURVEY_CONFIG_KEY);
+        const { baseUrl: baseURL, maxRedirects, timeout } = configService.get<OrdnanceSurveyConfig>(ORDNANCE_SURVEY_CONFIG_KEY);
         return {
-          baseURL: baseUrl,
+          baseURL,
           maxRedirects,
           timeout,
         };

From 33c9e65961e67fdff36c854a7ae9f78c3c1ccd0c Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Mon, 13 May 2024 11:30:48 +0100
Subject: [PATCH 53/56] feat(DTFS2-7052): change GET
 /geospatial/addresses/postcode?postcode= empty response from 200 to 404

---
 src/constants/geospatial.constant.ts          |  5 +-
 .../ordnance-survey.service.test.ts           |  2 +-
 .../get-addresses-by-postcode-query.dto.ts    |  5 +-
 .../dto/get-addresses-response.dto.ts         |  2 +-
 .../geospatial/geospatial.controller.test.ts  | 14 +++--
 .../geospatial/geospatial.controller.ts       |  2 +-
 .../geospatial/geospatial.service.test.ts     |  8 ++-
 src/modules/geospatial/geospatial.service.ts  |  6 +-
 .../get-docs-yaml.api-test.ts.snap            |  4 +-
 .../exposure-period.api-test.ts               |  7 +++
 .../get-address-by-postcode.api-test.ts       | 60 ++++++++++++++++---
 .../get-geospatial-addresses-generator.ts     |  4 +-
 12 files changed, 91 insertions(+), 28 deletions(-)

diff --git a/src/constants/geospatial.constant.ts b/src/constants/geospatial.constant.ts
index d4d3102f..f3accec2 100644
--- a/src/constants/geospatial.constant.ts
+++ b/src/constants/geospatial.constant.ts
@@ -4,7 +4,10 @@ export const GEOSPATIAL = {
     DATASET: 'DPA',
   },
   EXAMPLES: {
-    POSTCODE: 'SW1A 2AQ',
+    ENGLISH_POSTCODE: 'SW1A 2AQ',
+    NORTHERN_IRELAND_POSTCODE: 'BT7 3GG',
+    WALES_POSTCODE: 'SY23 3AR',
+    SCOTLAND_POSTCODE: 'EH1 1JF',
     ORGANISATION_NAME: 'CHURCHILL MUSEUM & CABINET WAR ROOMS',
     ADDRESS_LINE_1: 'CLIVE STEPS KING CHARLES STREET',
     LOCALITY: 'LONDON',
diff --git a/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts b/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
index 2bc641f9..fe3a1dbc 100644
--- a/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
+++ b/src/helper-modules/ordnance-survey/ordnance-survey.service.test.ts
@@ -19,7 +19,7 @@ describe('OrdnanceSurveyService', () => {
   let configServiceGet: jest.Mock;
   let service: OrdnanceSurveyService;
 
-  const testPostcode = GEOSPATIAL.EXAMPLES.POSTCODE;
+  const testPostcode = GEOSPATIAL.EXAMPLES.ENGLISH_POSTCODE;
   const testKey = valueGenerator.string({ length: 10 });
   const basePath = '/search/places/v1/postcode';
 
diff --git a/src/modules/geospatial/dto/get-addresses-by-postcode-query.dto.ts b/src/modules/geospatial/dto/get-addresses-by-postcode-query.dto.ts
index 26f62be0..55c06260 100644
--- a/src/modules/geospatial/dto/get-addresses-by-postcode-query.dto.ts
+++ b/src/modules/geospatial/dto/get-addresses-by-postcode-query.dto.ts
@@ -1,15 +1,16 @@
 import { ApiProperty } from '@nestjs/swagger';
 import { GEOSPATIAL } from '@ukef/constants';
-import { Matches, MaxLength, MinLength } from 'class-validator';
+import { IsString, Matches, MaxLength, MinLength } from 'class-validator';
 
 export class GetAddressesByPostcodeQueryDto {
   @ApiProperty({
-    example: GEOSPATIAL.EXAMPLES.POSTCODE,
+    example: GEOSPATIAL.EXAMPLES.ENGLISH_POSTCODE,
     description: 'Postcode to search for',
     minLength: 5,
     maxLength: 8,
     pattern: GEOSPATIAL.REGEX.UK_POSTCODE.source,
   })
+  @IsString()
   @MinLength(5)
   @MaxLength(8)
   @Matches(GEOSPATIAL.REGEX.UK_POSTCODE)
diff --git a/src/modules/geospatial/dto/get-addresses-response.dto.ts b/src/modules/geospatial/dto/get-addresses-response.dto.ts
index 87e997a1..0947e056 100644
--- a/src/modules/geospatial/dto/get-addresses-response.dto.ts
+++ b/src/modules/geospatial/dto/get-addresses-response.dto.ts
@@ -40,7 +40,7 @@ export class GetAddressesResponseItem {
 
   @ApiProperty({
     description: 'Postcode',
-    example: GEOSPATIAL.EXAMPLES.POSTCODE,
+    example: GEOSPATIAL.EXAMPLES.ENGLISH_POSTCODE,
     nullable: true,
   })
   readonly postalCode: string | null;
diff --git a/src/modules/geospatial/geospatial.controller.test.ts b/src/modules/geospatial/geospatial.controller.test.ts
index d125cf04..0d951cbf 100644
--- a/src/modules/geospatial/geospatial.controller.test.ts
+++ b/src/modules/geospatial/geospatial.controller.test.ts
@@ -1,3 +1,4 @@
+import { NotFoundException } from '@nestjs/common';
 import { GEOSPATIAL } from '@ukef/constants';
 import { GetGeospatialAddressesGenerator } from '@ukef-test/support/generator/get-geospatial-addresses-generator';
 import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator';
@@ -30,7 +31,7 @@ describe('GeospatialController', () => {
   });
 
   describe('getAddressesByPostcode()', () => {
-    const postcode = GEOSPATIAL.EXAMPLES.POSTCODE;
+    const postcode = GEOSPATIAL.EXAMPLES.ENGLISH_POSTCODE;
 
     it('returns a single address for the postcode when the service returns a single address', async () => {
       when(geospatialServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressesByPostcodeResponse[0]);
@@ -50,13 +51,16 @@ describe('GeospatialController', () => {
       expect(response).toEqual(getAddressesByPostcodeMultipleResponse);
     });
 
-    it('returns an empty response for the postcode when the service returns an empty response', async () => {
-      when(geospatialServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce([]);
+    it('passes NotFoundException when it is thrown by geospatialService', async () => {
+      const errorMessage = valueGenerator.sentence();
+      when(geospatialServiceGetAddressesByPostcode).calledWith(postcode).mockRejectedValueOnce(new NotFoundException(errorMessage));
 
-      const response = await controller.getAddressesByPostcode({ postcode });
+      const responsePromise = controller.getAddressesByPostcode({ postcode });
 
       expect(geospatialServiceGetAddressesByPostcode).toHaveBeenCalledTimes(1);
-      expect(response).toEqual([]);
+
+      await expect(responsePromise).rejects.toBeInstanceOf(NotFoundException);
+      await expect(responsePromise).rejects.toThrow(errorMessage);
     });
   });
 });
diff --git a/src/modules/geospatial/geospatial.controller.ts b/src/modules/geospatial/geospatial.controller.ts
index 7ded6c31..8fe5b2c0 100644
--- a/src/modules/geospatial/geospatial.controller.ts
+++ b/src/modules/geospatial/geospatial.controller.ts
@@ -21,7 +21,7 @@ export class GeospatialController {
     type: [GetAddressesResponseItem],
   })
   @ApiNotFoundResponse({
-    description: 'Postcode not found.',
+    description: 'No addresses found',
   })
   getAddressesByPostcode(@Query() query: GetAddressesByPostcodeQueryDto): Promise<GetAddressesResponse> {
     return this.geospatialService.getAddressesByPostcode(query.postcode);
diff --git a/src/modules/geospatial/geospatial.service.test.ts b/src/modules/geospatial/geospatial.service.test.ts
index 1718118f..67c34630 100644
--- a/src/modules/geospatial/geospatial.service.test.ts
+++ b/src/modules/geospatial/geospatial.service.test.ts
@@ -1,3 +1,4 @@
+import { NotFoundException } from '@nestjs/common';
 import { ConfigService } from '@nestjs/config';
 import { OrdnanceSurveyService } from '@ukef/helper-modules/ordnance-survey/ordnance-survey.service';
 import { GetGeospatialAddressesGenerator } from '@ukef-test/support/generator/get-geospatial-addresses-generator';
@@ -56,12 +57,13 @@ describe('GeospatialService', () => {
       expect(response).toEqual(getAddressesByPostcodeMultipleResponse);
     });
 
-    it('can handle empty backend response', async () => {
+    it('throws NotFoundException in case of empty backend response', async () => {
       when(ordnanceSurveyServiceGetAddressesByPostcode).calledWith(postcode).mockResolvedValueOnce(getAddressesOrdnanceSurveyEmptyResponse[0]);
 
-      const response = await service.getAddressesByPostcode(postcode);
+      const responsePromise = service.getAddressesByPostcode(postcode);
 
-      expect(response).toEqual([]);
+      await expect(responsePromise).rejects.toBeInstanceOf(NotFoundException);
+      await expect(responsePromise).rejects.toThrow('No addresses found');
     });
 
     it('returns addressLine1 formatted correctly even if middle value is missing', async () => {
diff --git a/src/modules/geospatial/geospatial.service.ts b/src/modules/geospatial/geospatial.service.ts
index cf2199a8..ff4cfc9e 100644
--- a/src/modules/geospatial/geospatial.service.ts
+++ b/src/modules/geospatial/geospatial.service.ts
@@ -1,4 +1,4 @@
-import { Injectable } from '@nestjs/common';
+import { Injectable, NotFoundException } from '@nestjs/common';
 import { ENUMS } from '@ukef/constants';
 import { GetAddressesOrdnanceSurveyResponse } from '@ukef/helper-modules/ordnance-survey/dto/get-addresses-ordnance-survey-response.dto';
 import { OrdnanceSurveyService } from '@ukef/helper-modules/ordnance-survey/ordnance-survey.service';
@@ -13,8 +13,8 @@ export class GeospatialService {
     const addresses = [];
     const response: GetAddressesOrdnanceSurveyResponse = await this.ordnanceSurveyService.getAddressesByPostcode(postcode);
 
-    if (!response?.results) {
-      return [];
+    if (!response?.results?.length) {
+      throw new NotFoundException('No addresses found');
     }
 
     response.results.forEach((item) => {
diff --git a/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap b/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
index 3bcf1ea2..73ac0a10 100644
--- a/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
+++ b/test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
@@ -515,7 +515,7 @@ paths:
                 items:
                   $ref: '#/components/schemas/GetAddressesResponseItem'
         '404':
-          description: Postcode not found.
+          description: No addresses found
       tags:
         - geospatial
 info:
@@ -1174,9 +1174,9 @@ components:
           example: England
           enum:
             - England
+            - Northern Ireland
             - Scotland
             - Wales
-            - Northern Ireland
           nullable: true
       required:
         - organisationName
diff --git a/test/exposure-period/exposure-period.api-test.ts b/test/exposure-period/exposure-period.api-test.ts
index 0781ea76..74ec9e40 100644
--- a/test/exposure-period/exposure-period.api-test.ts
+++ b/test/exposure-period/exposure-period.api-test.ts
@@ -137,4 +137,11 @@ describe('Exposure period', () => {
     expect(status).toBe(400);
     expect(body.message).toContain('productgroup must be one of the following values: EW, BS');
   });
+
+  it('returns a 404 response if URL is missing /api/v1 at the beginning', async () => {
+    const url = '/exposure-period?startdate=2017-03-05&enddate=2017-04-05&productgroup=new';
+    const { status, body } = await api.get(url);
+    expect(status).toBe(404);
+    expect(body.message).toContain(`Cannot GET ${url}`);
+  });
 });
diff --git a/test/geospatial/get-address-by-postcode.api-test.ts b/test/geospatial/get-address-by-postcode.api-test.ts
index 37002809..733054bc 100644
--- a/test/geospatial/get-address-by-postcode.api-test.ts
+++ b/test/geospatial/get-address-by-postcode.api-test.ts
@@ -13,6 +13,7 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
 
   const {
     ordnanceSurveyPaths,
+    ordnanceSurveyKey,
     mdmPaths,
     getAddressesByPostcodeResponse,
     getAddressesByPostcodeMultipleResponse,
@@ -21,12 +22,14 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
     getAddressesOrdnanceSurveyMultipleMatchingAddressesResponse,
     ordnanceSurveyAuthErrorResponse,
   } = new GetGeospatialAddressesGenerator(valueGenerator).generate({
-    postcode: GEOSPATIAL.EXAMPLES.POSTCODE,
+    postcode: GEOSPATIAL.EXAMPLES.ENGLISH_POSTCODE,
     key: ENVIRONMENT_VARIABLES.ORDNANCE_SURVEY_KEY,
     numberToGenerate: 2,
   });
 
-  const getMdmUrl = (postcode: string) => `/api/v1/geospatial/addresses/postcode?postcode=${encodeURIComponent(postcode)}`;
+  const getMdmUrl = (postcode: any) => `/api/v1/geospatial/addresses/postcode?postcode=${encodeURIComponent(postcode)}`;
+  const getOsUrl = (postcode: any) =>
+    `/search/places/v1/postcode?postcode=${encodeURIComponent(postcode)}&lr=${GEOSPATIAL.DEFAULT.RESULT_LANGUAGE}&key=${encodeURIComponent(ordnanceSurveyKey)}`;
 
   beforeAll(async () => {
     api = await Api.create();
@@ -49,10 +52,27 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
     makeRequestWithoutAuth: (incorrectAuth?: IncorrectAuthArg) => api.getWithoutAuth(mdmPaths[0], incorrectAuth?.headerName, incorrectAuth?.headerValue),
   });
 
-  it('returns a 200 response with the address if it is returned by Ordnance Survey API', async () => {
-    requestToGetAddressesByPostcode(ordnanceSurveyPaths[0]).reply(200, getAddressesOrdnanceSurveyResponse[0]);
+  it.each([
+    {
+      postcode: GEOSPATIAL.EXAMPLES.ENGLISH_POSTCODE,
+      description: 'for English postcode',
+    },
+    {
+      postcode: GEOSPATIAL.EXAMPLES.NORTHERN_IRELAND_POSTCODE,
+      description: 'for Northern Ireland postcode',
+    },
+    {
+      postcode: GEOSPATIAL.EXAMPLES.SCOTLAND_POSTCODE,
+      description: 'for Scotish postcode',
+    },
+    {
+      postcode: GEOSPATIAL.EXAMPLES.WALES_POSTCODE,
+      description: 'for Wales postcode',
+    },
+  ])('returns a 200 response $description', async ({ postcode }) => {
+    requestToGetAddressesByPostcode(getOsUrl(postcode)).reply(200, getAddressesOrdnanceSurveyResponse[0]);
 
-    const { status, body } = await api.get(mdmPaths[0]);
+    const { status, body } = await api.get(getMdmUrl(postcode));
 
     expect(status).toBe(200);
     expect(body).toStrictEqual(getAddressesByPostcodeResponse[0]);
@@ -67,13 +87,17 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
     expect(body).toStrictEqual(getAddressesByPostcodeMultipleResponse);
   });
 
-  it('returns an empty 200 response if Ordnance Survey API returns a 200 without results', async () => {
+  it('returns a 404 response if Ordnance Survey API returns a 200 without results', async () => {
     requestToGetAddressesByPostcode(ordnanceSurveyPaths[0]).reply(200, getAddressesOrdnanceSurveyEmptyResponse[0]);
 
     const { status, body } = await api.get(mdmPaths[0]);
 
-    expect(status).toBe(200);
-    expect(body).toStrictEqual([]);
+    expect(status).toBe(404);
+    expect(body).toEqual({
+      statusCode: 404,
+      message: 'No addresses found',
+      error: 'Not Found',
+    });
   });
 
   it('returns a 500 response if Ordnance Survey API returns a status code 401', async () => {
@@ -125,6 +149,26 @@ describe('GET /geospatial/addresses/postcode?postcode=', () => {
   });
 
   it.each([
+    {
+      postcode: false,
+      expectedError: 'postcode must match /^[A-Za-z]{1,2}[\\dRr][\\dA-Za-z]?\\s?\\d[ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$/ regular expression',
+    },
+    {
+      postcode: 1234567,
+      expectedError: 'postcode must match /^[A-Za-z]{1,2}[\\dRr][\\dA-Za-z]?\\s?\\d[ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$/ regular expression',
+    },
+    {
+      postcode: null,
+      expectedError: 'postcode must match /^[A-Za-z]{1,2}[\\dRr][\\dA-Za-z]?\\s?\\d[ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$/ regular expression',
+    },
+    {
+      postcode: {},
+      expectedError: 'postcode must match /^[A-Za-z]{1,2}[\\dRr][\\dA-Za-z]?\\s?\\d[ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}$/ regular expression',
+    },
+    {
+      postcode: '',
+      expectedError: 'postcode must be longer than or equal to 5 characters',
+    },
     {
       postcode: valueGenerator.string({ length: 4 }),
       expectedError: 'postcode must be longer than or equal to 5 characters',
diff --git a/test/support/generator/get-geospatial-addresses-generator.ts b/test/support/generator/get-geospatial-addresses-generator.ts
index 347cb262..45bc5e73 100644
--- a/test/support/generator/get-geospatial-addresses-generator.ts
+++ b/test/support/generator/get-geospatial-addresses-generator.ts
@@ -26,7 +26,7 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
   }
 
   protected transformRawValuesToGeneratedValues(values: AddressValues[], { postcode, key }: GenerateOptions): GenerateResult {
-    const useKey = key || 'test';
+    const useKey = key || this.valueGenerator.string();
 
     const requests: GetAddressesByPostcodeQueryDto[] = values.map((v) => ({ postcode: postcode || v.POSTCODE }) as GetAddressesByPostcodeQueryDto);
 
@@ -191,6 +191,7 @@ export class GetGeospatialAddressesGenerator extends AbstractGenerator<AddressVa
       requests,
       ordnanceSurveyPaths,
       mdmPaths,
+      ordnanceSurveyKey: useKey,
       getAddressesByPostcodeResponse,
       getAddressesByPostcodeMultipleResponse,
       getAddressesOrdnanceSurveyResponse,
@@ -221,6 +222,7 @@ interface GenerateResult {
   requests: GetAddressesByPostcodeQueryDto[];
   ordnanceSurveyPaths: string[];
   mdmPaths: string[];
+  ordnanceSurveyKey: string;
   getAddressesByPostcodeResponse: GetAddressesResponse[];
   getAddressesByPostcodeMultipleResponse: GetAddressesResponse;
   getAddressesOrdnanceSurveyResponse: GetAddressesOrdnanceSurveyResponse[];

From 37519922172ef04acba5ca98d5e24c8039426dcc Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Mon, 13 May 2024 12:10:44 +0100
Subject: [PATCH 54/56] feat(DTFS2-7052): changed numeric status code 200 to
 HttpStatus.OK, but just in controller

---
 src/modules/geospatial/geospatial.controller.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/modules/geospatial/geospatial.controller.ts b/src/modules/geospatial/geospatial.controller.ts
index 8fe5b2c0..5cf5e010 100644
--- a/src/modules/geospatial/geospatial.controller.ts
+++ b/src/modules/geospatial/geospatial.controller.ts
@@ -1,4 +1,4 @@
-import { Controller, Get, Query } from '@nestjs/common';
+import { Controller, Get, HttpStatus, Query } from '@nestjs/common';
 import { ApiNotFoundResponse, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
 
 import { GetAddressesByPostcodeQueryDto } from './dto/get-addresses-by-postcode-query.dto';
@@ -16,7 +16,7 @@ export class GeospatialController {
       "A search based on a property's postcode. Will accept a full valid postcode. Returns addresses from Ordnance Survey Delivery Point Address (DPA) system.",
   })
   @ApiResponse({
-    status: 200,
+    status: HttpStatus.OK,
     description: 'Returns simplified addresses that are ready to show to users.',
     type: [GetAddressesResponseItem],
   })

From 18fdf4a7bdfa77cc84a33f4da43b4c6cbab5f86b Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Mon, 13 May 2024 12:56:12 +0100
Subject: [PATCH 55/56] feat(DTFS2-7052): changed documentation for .env
 setting ORDNANCE_SURVEY_URL

---
 .env.sample | 3 +--
 README.md   | 4 ++++
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/.env.sample b/.env.sample
index 536e7f69..af236dee 100644
--- a/.env.sample
+++ b/.env.sample
@@ -41,8 +41,7 @@ APIM_INFORMATICA_MAX_REDIRECTS=
 APIM_INFORMATICA_TIMEOUT= # in milliseconds
 
 # ORDNANCE SURVEY
-# Use .uk domain, instead of .co.uk
-ORDNANCE_SURVEY_URL=
+ORDNANCE_SURVEY_URL=https://api.os.uk
 ORDNANCE_SURVEY_KEY=
 ORDNANCE_SURVEY_MAX_REDIRECTS=
 ORDNANCE_SURVEY_TIMEOUT= # in milliseconds
diff --git a/README.md b/README.md
index 0f7e3edc..8dbe732c 100644
--- a/README.md
+++ b/README.md
@@ -155,3 +155,7 @@ The most important prefixes you should have in mind are:
 1. `fix:` which represents bug fixes, and correlates to a [SemVer](https://semver.org/) **patch**.
 2. `feat:` which represents a new feature, and correlates to a [SemVer](https://semver.org/) **minor**.
 3. `feat!:`, `fix!:` or `refactor!:`, etc., which represent a breaking change (indicated by the `!`) and will result in a [SemVer](https://semver.org/) **major**.
+
+### Special notes for .env settings
+
+- ORDNANCE_SURVEY_URL - Domain [https://api.os.co.uk](https://api.os.co.uk) redirects to [https://api.os.uk](https://api.os.uk), so please use [https://api.os.uk](https://api.os.uk).

From 0da25c3304cf9491236dbf169092d68972ba1815 Mon Sep 17 00:00:00 2001
From: Audrius Vaitonis <audrius.vaitonis@ukexportfinance.gov.uk>
Date: Mon, 13 May 2024 13:14:42 +0100
Subject: [PATCH 56/56] feat(DTFS2-7052): tidying up map function

---
 src/modules/geospatial/geospatial.service.ts | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/modules/geospatial/geospatial.service.ts b/src/modules/geospatial/geospatial.service.ts
index ff4cfc9e..492cebd2 100644
--- a/src/modules/geospatial/geospatial.service.ts
+++ b/src/modules/geospatial/geospatial.service.ts
@@ -20,10 +20,11 @@ export class GeospatialService {
     response.results.forEach((item) => {
       // Item can have key DPA or LPI, so we get data dynamically, even if we expect key to always be DPA.
       const item_data = item[Object.keys(item)[0]];
+      // Filter out empty values and join values with single space.
+      const addressLine1 = [item_data.BUILDING_NAME, item_data.BUILDING_NUMBER, item_data.THOROUGHFARE_NAME].filter(Boolean).join(' ');
       addresses.push({
         organisationName: item_data.ORGANISATION_NAME || null,
-        // Filter out empty values and join values with single space.
-        addressLine1: [item_data.BUILDING_NAME, item_data.BUILDING_NUMBER, item_data.THOROUGHFARE_NAME].filter(Boolean).join(' '),
+        addressLine1,
         addressLine2: item_data.DEPENDENT_LOCALITY || null,
         addressLine3: null,
         locality: item_data.POST_TOWN || null,