From 02f61ee53ed2730c4d27e1d46603000faa2484a0 Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Wed, 18 Mar 2026 10:15:39 +0000 Subject: [PATCH 1/7] CCM-14615: add temp workflow for unit tests only --- .github/workflows/temp-unit-tests-only.yaml | 62 +++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/temp-unit-tests-only.yaml diff --git a/.github/workflows/temp-unit-tests-only.yaml b/.github/workflows/temp-unit-tests-only.yaml new file mode 100644 index 00000000..5b53fad6 --- /dev/null +++ b/.github/workflows/temp-unit-tests-only.yaml @@ -0,0 +1,62 @@ +name: "Temp unit tests only" + +on: + workflow_dispatch: + inputs: + nodejs_version: + description: "Node.js version, set by the CI/CD pipeline workflow" + required: true + type: string + python_version: + description: "Python version, set by the CI/CD pipeline workflow" + required: true + type: string + +env: + AWS_REGION: eu-west-2 + TERM: xterm-256color + +jobs: + test-unit: + name: "Unit tests" + runs-on: ubuntu-latest + timeout-minutes: 7 + permissions: + contents: read + packages: read + steps: + - name: "Checkout code" + uses: actions/checkout@v5 + - uses: ./.github/actions/node-install + with: + node-version: ${{ inputs.nodejs_version }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: "Setup Python" + uses: actions/setup-python@v6 + with: + python-version: ${{ inputs.python_version }} + cache: 'pip' + cache-dependency-path: '**/requirements*.txt' + - name: "Run unit test suite" + run: | + make test-unit + - name: "Save the result of fast test suite" + uses: actions/upload-artifact@v4 + with: + name: unit-tests + path: "**/.reports/unit" + include-hidden-files: true + if: always() + - name: "Save the result of code coverage" + uses: actions/upload-artifact@v4 + with: + name: code-coverage-report + path: ".reports/lcov.info" + - name: "Save Python coverage reports" + uses: actions/upload-artifact@v4 + with: + name: python-coverage-reports + path: | + src/**/coverage.xml + utils/**/coverage.xml + lambdas/**/coverage.xml From 6d198635c73e26a3d01f206b66fed10aeb351088 Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Fri, 20 Mar 2026 10:30:34 +0000 Subject: [PATCH 2/7] CCM-14615: add temp workflow for unit tests only --- .github/workflows/temp-unit-tests-only.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/temp-unit-tests-only.yaml b/.github/workflows/temp-unit-tests-only.yaml index 5b53fad6..fc790c36 100644 --- a/.github/workflows/temp-unit-tests-only.yaml +++ b/.github/workflows/temp-unit-tests-only.yaml @@ -1,7 +1,7 @@ name: "Temp unit tests only" on: - workflow_dispatch: + workflow_dispatch: inputs: nodejs_version: description: "Node.js version, set by the CI/CD pipeline workflow" @@ -11,6 +11,9 @@ on: description: "Python version, set by the CI/CD pipeline workflow" required: true type: string + push: + branches: + - feature/CCM-14615_unit-test-quickening env: AWS_REGION: eu-west-2 From 3ab0de75ea90ebc0d2002e239d0b0635eaec98ba Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Fri, 20 Mar 2026 10:31:41 +0000 Subject: [PATCH 3/7] CCM-14615: run parrallel using jest workspaces --- jest.config.cjs | 151 ++++++++++++++++++ .../__tests__/app/notify-api-client.test.ts | 8 +- .../src/__tests__/domain/mapper.test.ts | 8 +- package.json | 1 + scripts/tests/unit.sh | 92 ++++++++--- .../builder/__tests__/build-schema.test.ts | 28 ++-- 6 files changed, 250 insertions(+), 38 deletions(-) create mode 100644 jest.config.cjs diff --git a/jest.config.cjs b/jest.config.cjs new file mode 100644 index 00000000..07f9b723 --- /dev/null +++ b/jest.config.cjs @@ -0,0 +1,151 @@ +/** + * Root Jest config — runs all TypeScript/JavaScript workspace test suites in + * parallel via Jest's native `projects` support. + * + * Written as CJS (.cjs) so Jest can load it without needing a root tsconfig.json. + * The base config is inlined from jest.config.base.ts to keep this file + * self-contained and avoid any TypeScript compilation at load time, which would + * require a root tsconfig.json and risk interfering with workspace ts-jest runs. + * + * When jest.config.base.ts changes, this file must be kept in sync manually. + * + * Each project's rootDir is set to its workspace directory so that relative + * paths (coverageDirectory, HTML reporter outputPath, etc.) resolve relative + * to the workspace, not the repo root. + * + * Note: src/cloudevents has a hand-rolled jest.config.cjs; it is included via + * its directory path so Jest discovers that file directly. + * + * Note: src/digital-letters-events and tests/playwright have no Jest tests + * and are intentionally excluded. + */ + +const base = { + preset: 'ts-jest', + clearMocks: true, + collectCoverage: true, + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', + '!src/**/__tests__/**', + '!src/**/*.test.{ts,tsx}', + '!src/**/*.spec.{ts,tsx}', + ], + coverageDirectory: './.reports/unit/coverage', + coverageProvider: 'babel', + coverageThreshold: { + global: { branches: 100, functions: 100, lines: 100, statements: -10 }, + }, + coveragePathIgnorePatterns: ['/__tests__/'], + transform: { '^.+\\.ts$': 'ts-jest' }, + testPathIgnorePatterns: ['.build'], + testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], + reporters: [ + 'default', + [ + 'jest-html-reporter', + { + pageTitle: 'Test Report', + outputPath: './.reports/unit/test-report.html', + includeFailureMsg: true, + }, + ], + ], + testEnvironment: 'node', + moduleDirectories: ['node_modules', 'src'], +}; + +// Workspaces that use the base config with no overrides +const standardWorkspaces = [ + 'lambdas/file-scanner-lambda', + 'lambdas/key-generation', + 'lambdas/refresh-apim-access-token', + 'lambdas/pdm-mock-lambda', + 'lambdas/pdm-poll-lambda', + 'lambdas/ttl-create-lambda', + 'lambdas/ttl-handle-expiry-lambda', + 'lambdas/ttl-poll-lambda', + 'lambdas/pdm-uploader-lambda', + 'lambdas/print-sender-lambda', + 'lambdas/print-analyser', + 'lambdas/report-scheduler', + 'lambdas/report-event-transformer', + 'lambdas/move-scanned-files-lambda', + 'lambdas/report-generator', + 'utils/sender-management', +]; + +/** @type {import('jest').Config} */ +module.exports = { + projects: [ + // Standard workspaces — no overrides + ...standardWorkspaces.map((ws) => ({ + ...base, + rootDir: `/${ws}`, + displayName: ws, + })), + + // utils/utils — relaxed coverage thresholds + exclude index.ts + { + ...base, + rootDir: '/utils/utils', + displayName: 'utils/utils', + coverageThreshold: { + global: { branches: 85, functions: 85, lines: 85, statements: -10 }, + }, + coveragePathIgnorePatterns: [...base.coveragePathIgnorePatterns, 'index.ts'], + }, + + // lambdas/core-notifier-lambda — moduleNameMapper unifies `crypto` and + // `node:crypto` in Jest's registry so that jest.mock('node:crypto') in the + // test files also intercepts the bare require('crypto') call made by + // node-jose at module-load time, preventing an undefined helpers.nodeCrypto + // crash in ecdsa.js. + { + ...base, + rootDir: '/lambdas/core-notifier-lambda', + displayName: 'lambdas/core-notifier-lambda', + }, + + // lambdas/print-status-handler — @nhsdigital/nhs-notify-event-schemas-supplier-api + // ships ESM source; it must be transformed by ts-jest rather than skipped. + { + ...base, + rootDir: '/lambdas/print-status-handler', + displayName: 'lambdas/print-status-handler', + transformIgnorePatterns: [ + 'node_modules/(?!@nhsdigital/nhs-notify-event-schemas-supplier-api)', + ], + }, + + // src/python-schema-generator — excludes merge-allof CLI entry point + { + ...base, + rootDir: '/src/python-schema-generator', + displayName: 'src/python-schema-generator', + coveragePathIgnorePatterns: [ + ...base.coveragePathIgnorePatterns, + 'src/merge-allof-cli.ts', + ], + }, + + // src/typescript-schema-generator — excludes CLI entry points. + // Requires --experimental-vm-modules (set via NODE_OPTIONS in the + // test:unit:parallel script) because json-schema-to-typescript uses a + // dynamic import() of prettier at runtime, which Node.js rejects inside a + // Jest VM context without the flag. + { + ...base, + rootDir: '/src/typescript-schema-generator', + displayName: 'src/typescript-schema-generator', + coveragePathIgnorePatterns: [ + ...base.coveragePathIgnorePatterns, + 'src/generate-types-cli.ts', + 'src/generate-validators-cli.ts', + ], + }, + + // src/cloudevents — uses its own jest.config.cjs (hand-rolled ts-jest options) + '/src/cloudevents', + ], +}; diff --git a/lambdas/core-notifier-lambda/src/__tests__/app/notify-api-client.test.ts b/lambdas/core-notifier-lambda/src/__tests__/app/notify-api-client.test.ts index 59af51fd..80077f41 100644 --- a/lambdas/core-notifier-lambda/src/__tests__/app/notify-api-client.test.ts +++ b/lambdas/core-notifier-lambda/src/__tests__/app/notify-api-client.test.ts @@ -12,7 +12,13 @@ import { IAccessTokenRepository, NotifyClient } from 'app/notify-api-client'; import { RequestAlreadyReceivedError } from 'domain/request-already-received-error'; jest.mock('utils'); -jest.mock('node:crypto'); +// Use a partial manual mock so that node-jose's require('crypto') still gets +// the real crypto implementation (needed for getHashes() etc.) while +// randomUUID is replaced with a jest.fn() for test control. +jest.mock('node:crypto', () => ({ + ...jest.requireActual('node:crypto'), + randomUUID: jest.fn(), +})); jest.mock('axios', () => { const original: AxiosStatic = jest.requireActual('axios'); diff --git a/lambdas/core-notifier-lambda/src/__tests__/domain/mapper.test.ts b/lambdas/core-notifier-lambda/src/__tests__/domain/mapper.test.ts index e1bde3bc..cf2c37fd 100644 --- a/lambdas/core-notifier-lambda/src/__tests__/domain/mapper.test.ts +++ b/lambdas/core-notifier-lambda/src/__tests__/domain/mapper.test.ts @@ -9,7 +9,13 @@ import { PDMResourceAvailable } from 'digital-letters-events'; import { randomUUID } from 'node:crypto'; jest.mock('utils'); -jest.mock('node:crypto'); +// Use a partial manual mock so that node-jose's require('crypto') still gets +// the real crypto implementation (needed for getHashes() etc.) while +// randomUUID is replaced with a jest.fn() for test control. +jest.mock('node:crypto', () => ({ + ...jest.requireActual('node:crypto'), + randomUUID: jest.fn(), +})); const mockLogger = jest.mocked(logger); const mockRandomUUID = jest.mocked(randomUUID); diff --git a/package.json b/package.json index ca8080a4..81b84538 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "lint:fix": "npm run lint:fix --workspaces", "start": "npm run start --workspace frontend", "test:unit": "npm run test:unit --workspaces", + "test:unit:parallel": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --config jest.config.cjs", "typecheck": "npm run typecheck --workspaces" }, "version": "0.0.1", diff --git a/scripts/tests/unit.sh b/scripts/tests/unit.sh index 1a4a23e6..67ee6037 100755 --- a/scripts/tests/unit.sh +++ b/scripts/tests/unit.sh @@ -17,59 +17,103 @@ cd "$(git rev-parse --show-toplevel)" # tests from here. If you want to run other test suites, see the predefined # tasks in scripts/test.mk. +# Timing helpers — records wall-clock duration for each labelled step and prints +# a summary table at exit so it's easy to see where the time is going. +_timer_labels=() +_timer_seconds=() + +run_timed() { + local label="$1" + shift + local start + start=$(date +%s) + local rc=0 + "$@" || rc=$? + local end + end=$(date +%s) + _timer_labels+=("$label") + _timer_seconds+=("$((end - start))") + return "$rc" +} + +print_timing_summary() { + echo "" + echo "===== Timing Summary =====" + local total=0 + for i in "${!_timer_labels[@]}"; do + printf " %-55s %4ds\n" "${_timer_labels[$i]}" "${_timer_seconds[$i]}" + total=$((total + _timer_seconds[$i])) + done + echo " ---------------------------------------------------------" + printf " %-55s %4ds\n" "TOTAL" "$total" + echo "==========================" +} + +trap print_timing_summary EXIT + # run tests # TypeScript/JavaScript projects (npm workspace) -# Note: src/cloudevents is included in workspaces, so it will be tested here -npm ci -npm run generate-dependencies -npm run test:unit --workspaces +# Runs all Jest workspaces in parallel via the root jest.config.cjs projects +# config, which is faster than sequential `npm run test:unit --workspaces`. +# Note: src/cloudevents is included in the projects list in jest.config.cjs. +# Use || to capture any Jest failure so that Python tests always run; the exit +# code is propagated at the end of the script. +run_timed "npm ci" npm ci +run_timed "npm run generate-dependencies" npm run generate-dependencies +run_timed "npm run test:unit:parallel" npm run test:unit:parallel || jest_exit=$? # Python projects - asyncapigenerator echo "Setting up and running asyncapigenerator tests..." -make -C ./src/asyncapigenerator install-dev -make -C ./src/asyncapigenerator coverage # Run with coverage to generate coverage.xml for SonarCloud +run_timed "asyncapigenerator: install-dev" make -C ./src/asyncapigenerator install-dev +run_timed "asyncapigenerator: coverage" make -C ./src/asyncapigenerator coverage # Python projects - cloudeventjekylldocs echo "Setting up and running cloudeventjekylldocs tests..." -make -C ./src/cloudeventjekylldocs install-dev -make -C ./src/cloudeventjekylldocs coverage # Run with coverage to generate coverage.xml for SonarCloud +run_timed "cloudeventjekylldocs: install-dev" make -C ./src/cloudeventjekylldocs install-dev +run_timed "cloudeventjekylldocs: coverage" make -C ./src/cloudeventjekylldocs coverage # Python projects - eventcatalogasyncapiimporter echo "Setting up and running eventcatalogasyncapiimporter tests..." -make -C ./src/eventcatalogasyncapiimporter install-dev -make -C ./src/eventcatalogasyncapiimporter coverage # Run with coverage to generate coverage.xml for SonarCloud +run_timed "eventcatalogasyncapiimporter: install-dev" make -C ./src/eventcatalogasyncapiimporter install-dev +run_timed "eventcatalogasyncapiimporter: coverage" make -C ./src/eventcatalogasyncapiimporter coverage # Python utility packages - py-utils echo "Setting up and running py-utils tests..." -make -C ./utils/py-utils install-dev -make -C ./utils/py-utils coverage # Run with coverage to generate coverage.xml for SonarCloud +run_timed "py-utils: install-dev" make -C ./utils/py-utils install-dev +run_timed "py-utils: coverage" make -C ./utils/py-utils coverage # Python projects - python-schema-generator echo "Setting up and running python-schema-generator tests..." -make -C ./src/python-schema-generator install-dev -make -C ./src/python-schema-generator coverage # Run with coverage to generate coverage.xml for SonarCloud +run_timed "python-schema-generator: install-dev" make -C ./src/python-schema-generator install-dev +run_timed "python-schema-generator: coverage" make -C ./src/python-schema-generator coverage # Python Lambda - mesh-acknowledge echo "Setting up and running mesh-acknowledge tests..." -make -C ./lambdas/mesh-acknowledge install-dev -make -C ./lambdas/mesh-acknowledge coverage # Run with coverage to generate coverage.xml for SonarCloud +run_timed "mesh-acknowledge: install-dev" make -C ./lambdas/mesh-acknowledge install-dev +run_timed "mesh-acknowledge: coverage" make -C ./lambdas/mesh-acknowledge coverage # Python Lambda - mesh-poll echo "Setting up and running mesh-poll tests..." -make -C ./lambdas/mesh-poll install-dev -make -C ./lambdas/mesh-poll coverage # Run with coverage to generate coverage.xml for SonarCloud +run_timed "mesh-poll: install-dev" make -C ./lambdas/mesh-poll install-dev +run_timed "mesh-poll: coverage" make -C ./lambdas/mesh-poll coverage # Python Lambda - mesh-download echo "Setting up and running mesh-download tests..." -make -C ./lambdas/mesh-download install-dev -make -C ./lambdas/mesh-download coverage # Run with coverage to generate coverage.xml for SonarCloud +run_timed "mesh-download: install-dev" make -C ./lambdas/mesh-download install-dev +run_timed "mesh-download: coverage" make -C ./lambdas/mesh-download coverage # Python Lambda - report-sender echo "Setting up and running report-sender tests..." -make -C ./lambdas/report-sender install-dev -make -C ./lambdas/report-sender coverage # Run with coverage to generate coverage.xml for SonarCloud +run_timed "report-sender: install-dev" make -C ./lambdas/report-sender install-dev +run_timed "report-sender: coverage" make -C ./lambdas/report-sender coverage # merge coverage reports -mkdir -p .reports -TMPDIR="./.reports" ./node_modules/.bin/lcov-result-merger "**/.reports/unit/coverage/lcov.info" ".reports/lcov.info" --ignore "node_modules" --prepend-source-files --prepend-path-fix "../../.." +run_timed "lcov-result-merger" \ + bash -c 'mkdir -p .reports && TMPDIR="./.reports" ./node_modules/.bin/lcov-result-merger "**/.reports/unit/coverage/lcov.info" ".reports/lcov.info" --ignore "node_modules" --prepend-source-files --prepend-path-fix "../../.."' + +# Propagate any Jest failure now that all other test suites have completed +if [ "${jest_exit:-0}" -ne 0 ]; then + echo "Jest tests failed with exit code ${jest_exit}" + exit "${jest_exit}" +fi diff --git a/src/cloudevents/tools/builder/__tests__/build-schema.test.ts b/src/cloudevents/tools/builder/__tests__/build-schema.test.ts index f7c35f90..7c7fecf9 100644 --- a/src/cloudevents/tools/builder/__tests__/build-schema.test.ts +++ b/src/cloudevents/tools/builder/__tests__/build-schema.test.ts @@ -9,6 +9,10 @@ import fs from 'fs'; import path from 'path'; import { execSync } from 'child_process'; +// Resolve paths relative to this test file so the suite works whether Jest +// runs from the workspace directory or from the repository root. +const cloudEventsRoot = path.resolve(__dirname, '..', '..', '..'); + describe('build-schema CLI', () => { let testDir: string; let sourceDir: string; @@ -16,7 +20,7 @@ describe('build-schema CLI', () => { beforeAll(() => { // Create test directories - testDir = path.join(process.cwd(), 'test-build-' + Date.now()); + testDir = path.join(cloudEventsRoot, 'test-build-' + Date.now()); sourceDir = path.join(testDir, 'src'); outputDir = path.join(testDir, 'output'); @@ -52,7 +56,7 @@ describe('build-schema CLI', () => { execSync( `tsx tools/builder/build-schema.ts "${inputFile}" "${outputDir}"`, { - cwd: process.cwd(), + cwd: cloudEventsRoot, stdio: 'pipe' } ); @@ -91,7 +95,7 @@ properties: execSync( `tsx tools/builder/build-schema.ts "${inputFile}" "${outputDir}"`, { - cwd: process.cwd(), + cwd: cloudEventsRoot, stdio: 'pipe' } ); @@ -120,7 +124,7 @@ properties: const result = execSync( `tsx tools/builder/build-schema.ts "${inputFile}" "${outputDir}"`, { - cwd: process.cwd(), + cwd: cloudEventsRoot, stdio: 'pipe', encoding: 'utf-8' } @@ -139,7 +143,7 @@ properties: execSync( 'tsx tools/builder/build-schema.ts', { - cwd: process.cwd(), + cwd: cloudEventsRoot, stdio: 'pipe' } ); @@ -160,7 +164,7 @@ properties: execSync( `tsx tools/builder/build-schema.ts "${inputFile}" "${outputDir}" "https://example.com"`, { - cwd: process.cwd(), + cwd: cloudEventsRoot, stdio: 'pipe' } ); @@ -198,7 +202,7 @@ properties: execSync( `tsx tools/builder/build-schema.ts "${mainFile}" "${outputDir}"`, { - cwd: process.cwd(), + cwd: cloudEventsRoot, stdio: 'pipe' } ); @@ -224,7 +228,7 @@ properties: execSync( `tsx tools/builder/build-schema.ts "${yamlFile}" "${outputDir}"`, { - cwd: process.cwd(), + cwd: cloudEventsRoot, stdio: 'pipe' } ); @@ -246,7 +250,7 @@ properties: execSync( `tsx tools/builder/build-schema.ts "nonexistent.json" "${outputDir}"`, { - cwd: process.cwd(), + cwd: cloudEventsRoot, stdio: 'pipe' } ); @@ -266,7 +270,7 @@ properties: execSync( `tsx tools/builder/build-schema.ts "${invalidFile}" "${outputDir}"`, { - cwd: process.cwd(), + cwd: cloudEventsRoot, stdio: 'pipe' } ); @@ -282,7 +286,7 @@ properties: describe('module structure', () => { it('should export expected functions (if any)', () => { // build-schema-cli.ts contains the testable logic - const buildSchemaCliPath = path.join(process.cwd(), 'tools/builder/build-schema-cli.ts'); + const buildSchemaCliPath = path.join(cloudEventsRoot, 'tools/builder/build-schema-cli.ts'); expect(fs.existsSync(buildSchemaCliPath)).toBe(true); // Verify file has expected structure @@ -293,7 +297,7 @@ properties: }); it('should have proper imports', () => { - const buildSchemaCliPath = path.join(process.cwd(), 'tools/builder/build-schema-cli.ts'); + const buildSchemaCliPath = path.join(cloudEventsRoot, 'tools/builder/build-schema-cli.ts'); const content = fs.readFileSync(buildSchemaCliPath, 'utf-8'); expect(content).toContain('import fs from'); From 52c6caf5c9fd334752d7c915813d821932a17450 Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Fri, 20 Mar 2026 11:18:55 +0000 Subject: [PATCH 4/7] CCM-14615: run pytets in parrallel --- lambdas/mesh-acknowledge/pytest.ini | 1 + lambdas/mesh-download/pytest.ini | 1 + lambdas/mesh-poll/pytest.ini | 1 + lambdas/report-sender/pytest.ini | 1 + scripts/tests/unit.sh | 106 ++++++++++++-------- src/asyncapigenerator/pytest.ini | 1 + src/cloudeventjekylldocs/pytest.ini | 1 + src/eventcatalogasyncapiimporter/pytest.ini | 1 + src/python-schema-generator/pytest.ini | 1 + utils/py-utils/pytest.ini | 1 + 10 files changed, 71 insertions(+), 44 deletions(-) diff --git a/lambdas/mesh-acknowledge/pytest.ini b/lambdas/mesh-acknowledge/pytest.ini index e19306a7..7f80cf1a 100644 --- a/lambdas/mesh-acknowledge/pytest.ini +++ b/lambdas/mesh-acknowledge/pytest.ini @@ -7,6 +7,7 @@ addopts = -v --tb=short [coverage:run] relative_files = True +data_file = lambdas/mesh-acknowledge/.coverage omit = */mesh_acknowledge/__tests__/* */test_*.py diff --git a/lambdas/mesh-download/pytest.ini b/lambdas/mesh-download/pytest.ini index 303659aa..b2483b3c 100644 --- a/lambdas/mesh-download/pytest.ini +++ b/lambdas/mesh-download/pytest.ini @@ -7,6 +7,7 @@ addopts = -v --tb=short [coverage:run] relative_files = True +data_file = lambdas/mesh-download/.coverage omit = */tests/* */test_*.py diff --git a/lambdas/mesh-poll/pytest.ini b/lambdas/mesh-poll/pytest.ini index 93372031..8657f96d 100644 --- a/lambdas/mesh-poll/pytest.ini +++ b/lambdas/mesh-poll/pytest.ini @@ -7,6 +7,7 @@ addopts = -v --tb=short [coverage:run] relative_files = True +data_file = lambdas/mesh-poll/.coverage omit = */mesh_poll/__tests__/* */test_*.py diff --git a/lambdas/report-sender/pytest.ini b/lambdas/report-sender/pytest.ini index 91879c29..9402fd11 100644 --- a/lambdas/report-sender/pytest.ini +++ b/lambdas/report-sender/pytest.ini @@ -7,6 +7,7 @@ addopts = -v --tb=short [coverage:run] relative_files = True +data_file = lambdas/report-sender/.coverage omit = */report_sender/__tests__/* */test_*.py diff --git a/scripts/tests/unit.sh b/scripts/tests/unit.sh index 67ee6037..63a24fd3 100755 --- a/scripts/tests/unit.sh +++ b/scripts/tests/unit.sh @@ -63,50 +63,60 @@ run_timed "npm ci" npm ci run_timed "npm run generate-dependencies" npm run generate-dependencies run_timed "npm run test:unit:parallel" npm run test:unit:parallel || jest_exit=$? -# Python projects - asyncapigenerator -echo "Setting up and running asyncapigenerator tests..." -run_timed "asyncapigenerator: install-dev" make -C ./src/asyncapigenerator install-dev -run_timed "asyncapigenerator: coverage" make -C ./src/asyncapigenerator coverage - -# Python projects - cloudeventjekylldocs -echo "Setting up and running cloudeventjekylldocs tests..." -run_timed "cloudeventjekylldocs: install-dev" make -C ./src/cloudeventjekylldocs install-dev -run_timed "cloudeventjekylldocs: coverage" make -C ./src/cloudeventjekylldocs coverage - -# Python projects - eventcatalogasyncapiimporter -echo "Setting up and running eventcatalogasyncapiimporter tests..." -run_timed "eventcatalogasyncapiimporter: install-dev" make -C ./src/eventcatalogasyncapiimporter install-dev -run_timed "eventcatalogasyncapiimporter: coverage" make -C ./src/eventcatalogasyncapiimporter coverage - -# Python utility packages - py-utils -echo "Setting up and running py-utils tests..." -run_timed "py-utils: install-dev" make -C ./utils/py-utils install-dev -run_timed "py-utils: coverage" make -C ./utils/py-utils coverage - -# Python projects - python-schema-generator -echo "Setting up and running python-schema-generator tests..." -run_timed "python-schema-generator: install-dev" make -C ./src/python-schema-generator install-dev -run_timed "python-schema-generator: coverage" make -C ./src/python-schema-generator coverage - -# Python Lambda - mesh-acknowledge -echo "Setting up and running mesh-acknowledge tests..." -run_timed "mesh-acknowledge: install-dev" make -C ./lambdas/mesh-acknowledge install-dev -run_timed "mesh-acknowledge: coverage" make -C ./lambdas/mesh-acknowledge coverage - -# Python Lambda - mesh-poll -echo "Setting up and running mesh-poll tests..." -run_timed "mesh-poll: install-dev" make -C ./lambdas/mesh-poll install-dev -run_timed "mesh-poll: coverage" make -C ./lambdas/mesh-poll coverage - -# Python Lambda - mesh-download -echo "Setting up and running mesh-download tests..." -run_timed "mesh-download: install-dev" make -C ./lambdas/mesh-download install-dev -run_timed "mesh-download: coverage" make -C ./lambdas/mesh-download coverage - -# Python Lambda - report-sender -echo "Setting up and running report-sender tests..." -run_timed "report-sender: install-dev" make -C ./lambdas/report-sender install-dev -run_timed "report-sender: coverage" make -C ./lambdas/report-sender coverage +# Python projects - run all install-dev steps sequentially (they share the same +# pip environment so cannot be parallelised), then run all coverage (pytest) +# steps in parallel since each writes to its own isolated output directory. + +# ---- Phase 1: install all Python dev dependencies (sequential, shared pip env) ---- +echo "Installing Python dev dependencies..." +_python_projects=( + ./src/asyncapigenerator + ./src/cloudeventjekylldocs + ./src/eventcatalogasyncapiimporter + ./utils/py-utils + ./src/python-schema-generator + ./lambdas/mesh-acknowledge + ./lambdas/mesh-poll + ./lambdas/mesh-download + ./lambdas/report-sender +) +for proj in "${_python_projects[@]}"; do + run_timed "${proj}: install-dev" make -C "$proj" install-dev +done + +# ---- Phase 2: run all coverage steps in parallel ---- +# Each job writes output to a temp file; we print them sequentially on +# completion so the log is readable. Non-zero exit codes are all collected and +# the script fails at the end if any job failed. +echo "Running Python coverage in parallel..." + +_py_pids=() +_py_labels=() +_py_logs=() +_py_exits=() + +for proj in "${_python_projects[@]}"; do + label="${proj}: coverage" + logfile=$(mktemp) + make -C "$proj" coverage >"$logfile" 2>&1 & + _py_pids+=("$!") + _py_labels+=("$label") + _py_logs+=("$logfile") +done + +# Collect results in launch order (preserves deterministic output) +_py_start=$(date +%s) +for i in "${!_py_pids[@]}"; do + wait "${_py_pids[$i]}" + _py_exits+=("$?") + echo "" + echo "--- ${_py_labels[$i]} ---" + cat "${_py_logs[$i]}" + rm -f "${_py_logs[$i]}" +done +_py_end=$(date +%s) +_timer_labels+=("Python coverage (parallel)") +_timer_seconds+=("$((_py_end - _py_start))") # merge coverage reports run_timed "lcov-result-merger" \ @@ -117,3 +127,11 @@ if [ "${jest_exit:-0}" -ne 0 ]; then echo "Jest tests failed with exit code ${jest_exit}" exit "${jest_exit}" fi + +# Propagate any Python coverage failure +for i in "${!_py_exits[@]}"; do + if [ "${_py_exits[$i]}" -ne 0 ]; then + echo "${_py_labels[$i]} failed with exit code ${_py_exits[$i]}" + exit "${_py_exits[$i]}" + fi +done diff --git a/src/asyncapigenerator/pytest.ini b/src/asyncapigenerator/pytest.ini index 94bd46ad..fcdb5a3f 100644 --- a/src/asyncapigenerator/pytest.ini +++ b/src/asyncapigenerator/pytest.ini @@ -19,6 +19,7 @@ markers = [coverage:run] relative_files = True +data_file = src/asyncapigenerator/.coverage omit = */tests/* */test_*.py diff --git a/src/cloudeventjekylldocs/pytest.ini b/src/cloudeventjekylldocs/pytest.ini index dba4156c..b79a593f 100644 --- a/src/cloudeventjekylldocs/pytest.ini +++ b/src/cloudeventjekylldocs/pytest.ini @@ -18,6 +18,7 @@ markers = [coverage:run] relative_files = True +data_file = src/cloudeventjekylldocs/.coverage omit = */tests/* */test_*.py diff --git a/src/eventcatalogasyncapiimporter/pytest.ini b/src/eventcatalogasyncapiimporter/pytest.ini index 0b2a4551..41b39ff4 100644 --- a/src/eventcatalogasyncapiimporter/pytest.ini +++ b/src/eventcatalogasyncapiimporter/pytest.ini @@ -20,6 +20,7 @@ testpaths = tests [coverage:run] relative_files = True +data_file = src/eventcatalogasyncapiimporter/.coverage omit = */tests/* */test_*.py diff --git a/src/python-schema-generator/pytest.ini b/src/python-schema-generator/pytest.ini index 0d63d6be..001fba94 100644 --- a/src/python-schema-generator/pytest.ini +++ b/src/python-schema-generator/pytest.ini @@ -9,6 +9,7 @@ addopts = [coverage:run] relative_files = True +data_file = src/python-schema-generator/.coverage omit = */tests/* */test_*.py diff --git a/utils/py-utils/pytest.ini b/utils/py-utils/pytest.ini index f704cd77..b5bbd23b 100644 --- a/utils/py-utils/pytest.ini +++ b/utils/py-utils/pytest.ini @@ -7,6 +7,7 @@ addopts = -v --tb=short [coverage:run] relative_files = True +data_file = utils/py-utils/.coverage omit = */dl_utils/__tests__/* */test_*.py From b6dadac7a67aafbced5581e4cb460b9c667a6fd3 Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Fri, 20 Mar 2026 12:10:01 +0000 Subject: [PATCH 5/7] CCM-14615: more parrallels --- src/cloudevents/domains/common.mk | 32 +++++++++++-------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/src/cloudevents/domains/common.mk b/src/cloudevents/domains/common.mk index e96dcd34..e4891919 100644 --- a/src/cloudevents/domains/common.mk +++ b/src/cloudevents/domains/common.mk @@ -56,31 +56,23 @@ build-no-bundle: @echo "Building $(DOMAIN) schemas to output/..." @if [ -n "$(PROFILE_NAMES)" ]; then \ echo "Building profile schemas..."; \ - for schema in $(PROFILE_NAMES); do \ - echo " - $$schema"; \ - cd $(CLOUD_EVENTS_DIR) && npm run build -- --root-dir $(ROOT_DIR) $(SRC_DIR)/$$schema.schema.yaml $(OUTPUT_DIR) || exit 1; \ - done; \ + printf '%s\n' $(PROFILE_NAMES) | xargs -P 0 -I{} sh -c \ + 'cd $(CLOUD_EVENTS_DIR) && npm run build -- --root-dir $(ROOT_DIR) $(SRC_DIR)/{}.schema.yaml $(OUTPUT_DIR) || exit 1'; \ fi @if [ -n "$(DEFS_NAMES)" ]; then \ echo "Building defs schemas..."; \ - for schema in $(DEFS_NAMES); do \ - echo " - $$schema"; \ - cd $(CLOUD_EVENTS_DIR) && npm run build -- --root-dir $(ROOT_DIR) $(SRC_DIR)/defs/$$schema.yaml $(OUTPUT_DIR)/defs || exit 1; \ - done; \ + printf '%s\n' $(DEFS_NAMES) | xargs -P 0 -I{} sh -c \ + 'cd $(CLOUD_EVENTS_DIR) && npm run build -- --root-dir $(ROOT_DIR) $(SRC_DIR)/defs/{}.yaml $(OUTPUT_DIR)/defs || exit 1'; \ fi @if [ -n "$(DATA_NAMES)" ]; then \ echo "Building data schemas..."; \ - for schema in $(DATA_NAMES); do \ - echo " - $$schema"; \ - cd $(CLOUD_EVENTS_DIR) && npm run build -- --root-dir $(ROOT_DIR) $(SRC_DIR)/data/$$schema.yaml $(OUTPUT_DIR)/data || exit 1; \ - done; \ + printf '%s\n' $(DATA_NAMES) | xargs -P 0 -I{} sh -c \ + 'cd $(CLOUD_EVENTS_DIR) && npm run build -- --root-dir $(ROOT_DIR) $(SRC_DIR)/data/{}.yaml $(OUTPUT_DIR)/data || exit 1'; \ fi @if [ -n "$(EVENT_NAMES)" ]; then \ echo "Building event schemas..."; \ - for schema in $(EVENT_NAMES); do \ - echo " - $$schema"; \ - cd $(CLOUD_EVENTS_DIR) && npm run build -- --root-dir $(ROOT_DIR) $(SRC_DIR)/events/$$schema.schema.yaml $(OUTPUT_DIR)/events || exit 1; \ - done; \ + printf '%s\n' $(EVENT_NAMES) | xargs -P 0 -I{} sh -c \ + 'cd $(CLOUD_EVENTS_DIR) && npm run build -- --root-dir $(ROOT_DIR) $(SRC_DIR)/events/{}.schema.yaml $(OUTPUT_DIR)/events || exit 1'; \ fi publish-json: @@ -138,11 +130,9 @@ publish-json: publish-bundled-json: @if [ -n "$(EVENT_NAMES)" ]; then \ - @echo "Flattening published event schemas..."; \ - for schema in $(EVENT_NAMES); do \ - echo " - $$schema (flatten)"; \ - cd $(CLOUD_EVENTS_DIR) && npm run bundle -- --flatten --root-dir $(ROOT_DIR) --base-url $(SCHEMA_BASE_URL) $(OUTPUT_DIR)/events/$$schema.schema.json $(SCHEMAS_DIR)/events/$$schema.flattened.schema.json || exit 1; \ - done; \ + echo "Flattening published event schemas..."; \ + printf '%s\n' $(EVENT_NAMES) | xargs -P 0 -I{} sh -c \ + 'cd $(CLOUD_EVENTS_DIR) && npm run bundle -- --flatten --root-dir $(ROOT_DIR) --base-url $(SCHEMA_BASE_URL) $(OUTPUT_DIR)/events/{}.schema.json $(SCHEMAS_DIR)/events/{}.flattened.schema.json || exit 1'; \ fi publish-yaml: From c47ac23c66c22faa1dae091ee56bcf5857ada0f5 Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Mon, 23 Mar 2026 13:32:46 +0000 Subject: [PATCH 6/7] CCM-14615: clean ups --- .github/workflows/temp-unit-tests-only.yaml | 65 - .gitignore | 3 + jest.config.cjs | 1322 +++++++++++++++-- .../__tests__/app/notify-api-client.test.ts | 3 - .../src/__tests__/domain/mapper.test.ts | 3 - package.json | 2 +- scripts/generate-parallel-jest-config.ts | 153 ++ scripts/tests/unit.sh | 54 +- src/cloudevents/jest.config.cjs | 57 - src/cloudevents/jest.config.ts | 57 + 10 files changed, 1427 insertions(+), 292 deletions(-) delete mode 100644 .github/workflows/temp-unit-tests-only.yaml create mode 100644 scripts/generate-parallel-jest-config.ts delete mode 100644 src/cloudevents/jest.config.cjs create mode 100644 src/cloudevents/jest.config.ts diff --git a/.github/workflows/temp-unit-tests-only.yaml b/.github/workflows/temp-unit-tests-only.yaml deleted file mode 100644 index fc790c36..00000000 --- a/.github/workflows/temp-unit-tests-only.yaml +++ /dev/null @@ -1,65 +0,0 @@ -name: "Temp unit tests only" - -on: - workflow_dispatch: - inputs: - nodejs_version: - description: "Node.js version, set by the CI/CD pipeline workflow" - required: true - type: string - python_version: - description: "Python version, set by the CI/CD pipeline workflow" - required: true - type: string - push: - branches: - - feature/CCM-14615_unit-test-quickening - -env: - AWS_REGION: eu-west-2 - TERM: xterm-256color - -jobs: - test-unit: - name: "Unit tests" - runs-on: ubuntu-latest - timeout-minutes: 7 - permissions: - contents: read - packages: read - steps: - - name: "Checkout code" - uses: actions/checkout@v5 - - uses: ./.github/actions/node-install - with: - node-version: ${{ inputs.nodejs_version }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: "Setup Python" - uses: actions/setup-python@v6 - with: - python-version: ${{ inputs.python_version }} - cache: 'pip' - cache-dependency-path: '**/requirements*.txt' - - name: "Run unit test suite" - run: | - make test-unit - - name: "Save the result of fast test suite" - uses: actions/upload-artifact@v4 - with: - name: unit-tests - path: "**/.reports/unit" - include-hidden-files: true - if: always() - - name: "Save the result of code coverage" - uses: actions/upload-artifact@v4 - with: - name: code-coverage-report - path: ".reports/lcov.info" - - name: "Save Python coverage reports" - uses: actions/upload-artifact@v4 - with: - name: python-coverage-reports - path: | - src/**/coverage.xml - utils/**/coverage.xml - lambdas/**/coverage.xml diff --git a/.gitignore b/.gitignore index cf7d4b6a..8732a35f 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,6 @@ coverage-*/ **/playwright-report **/test-results plugin-cache + +# Generated by npm run test:unit:parallel — do not commit +jest.config.cjs diff --git a/jest.config.cjs b/jest.config.cjs index 07f9b723..81b984e2 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -1,151 +1,1233 @@ /** - * Root Jest config — runs all TypeScript/JavaScript workspace test suites in + * Root Jest config — runs all TypeScript workspace test suites in * parallel via Jest's native `projects` support. * - * Written as CJS (.cjs) so Jest can load it without needing a root tsconfig.json. - * The base config is inlined from jest.config.base.ts to keep this file - * self-contained and avoid any TypeScript compilation at load time, which would - * require a root tsconfig.json and risk interfering with workspace ts-jest runs. + * ⚠️ THIS FILE IS AUTO-GENERATED. Do not edit it directly. * - * When jest.config.base.ts changes, this file must be kept in sync manually. - * - * Each project's rootDir is set to its workspace directory so that relative - * paths (coverageDirectory, HTML reporter outputPath, etc.) resolve relative - * to the workspace, not the repo root. - * - * Note: src/cloudevents has a hand-rolled jest.config.cjs; it is included via - * its directory path so Jest discovers that file directly. - * - * Note: src/digital-letters-events and tests/playwright have no Jest tests - * and are intentionally excluded. + * Generated by scripts/generate-parallel-jest-config.ts */ -const base = { - preset: 'ts-jest', - clearMocks: true, - collectCoverage: true, - collectCoverageFrom: [ - 'src/**/*.{ts,tsx}', - '!src/**/*.d.ts', - '!src/**/__tests__/**', - '!src/**/*.test.{ts,tsx}', - '!src/**/*.spec.{ts,tsx}', - ], - coverageDirectory: './.reports/unit/coverage', - coverageProvider: 'babel', - coverageThreshold: { - global: { branches: 100, functions: 100, lines: 100, statements: -10 }, - }, - coveragePathIgnorePatterns: ['/__tests__/'], - transform: { '^.+\\.ts$': 'ts-jest' }, - testPathIgnorePatterns: ['.build'], - testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], - reporters: [ - 'default', - [ - 'jest-html-reporter', - { - pageTitle: 'Test Report', - outputPath: './.reports/unit/test-report.html', - includeFailureMsg: true, - }, - ], - ], - testEnvironment: 'node', - moduleDirectories: ['node_modules', 'src'], -}; - -// Workspaces that use the base config with no overrides -const standardWorkspaces = [ - 'lambdas/file-scanner-lambda', - 'lambdas/key-generation', - 'lambdas/refresh-apim-access-token', - 'lambdas/pdm-mock-lambda', - 'lambdas/pdm-poll-lambda', - 'lambdas/ttl-create-lambda', - 'lambdas/ttl-handle-expiry-lambda', - 'lambdas/ttl-poll-lambda', - 'lambdas/pdm-uploader-lambda', - 'lambdas/print-sender-lambda', - 'lambdas/print-analyser', - 'lambdas/report-scheduler', - 'lambdas/report-event-transformer', - 'lambdas/move-scanned-files-lambda', - 'lambdas/report-generator', - 'utils/sender-management', -]; - /** @type {import('jest').Config} */ module.exports = { projects: [ - // Standard workspaces — no overrides - ...standardWorkspaces.map((ws) => ({ - ...base, - rootDir: `/${ws}`, - displayName: ws, - })), + // lambdas/file-scanner-lambda + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/file-scanner-lambda", + "displayName": "lambdas/file-scanner-lambda", + }, + + // lambdas/key-generation + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + "lambda.ts", + "/config.ts", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/key-generation", + "displayName": "lambdas/key-generation", + }, + + // lambdas/refresh-apim-access-token + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 90, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + "cli.ts", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/refresh-apim-access-token", + "displayName": "lambdas/refresh-apim-access-token", + }, + + // lambdas/pdm-mock-lambda + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 90, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/pdm-mock-lambda", + "displayName": "lambdas/pdm-mock-lambda", + }, + + // lambdas/pdm-poll-lambda + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/pdm-poll-lambda", + "displayName": "lambdas/pdm-poll-lambda", + }, + + // lambdas/ttl-create-lambda + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/ttl-create-lambda", + "displayName": "lambdas/ttl-create-lambda", + }, + + // lambdas/ttl-handle-expiry-lambda + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/ttl-handle-expiry-lambda", + "displayName": "lambdas/ttl-handle-expiry-lambda", + }, + + // lambdas/ttl-poll-lambda + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/ttl-poll-lambda", + "displayName": "lambdas/ttl-poll-lambda", + }, + + // lambdas/pdm-uploader-lambda + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/pdm-uploader-lambda", + "displayName": "lambdas/pdm-uploader-lambda", + }, + + // lambdas/print-sender-lambda + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/print-sender-lambda", + "displayName": "lambdas/print-sender-lambda", + }, - // utils/utils — relaxed coverage thresholds + exclude index.ts + // lambdas/core-notifier-lambda { - ...base, - rootDir: '/utils/utils', - displayName: 'utils/utils', - coverageThreshold: { - global: { branches: 85, functions: 85, lines: 85, statements: -10 }, + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", }, - coveragePathIgnorePatterns: [...base.coveragePathIgnorePatterns, 'index.ts'], + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/core-notifier-lambda", + "displayName": "lambdas/core-notifier-lambda", }, - // lambdas/core-notifier-lambda — moduleNameMapper unifies `crypto` and - // `node:crypto` in Jest's registry so that jest.mock('node:crypto') in the - // test files also intercepts the bare require('crypto') call made by - // node-jose at module-load time, preventing an undefined helpers.nodeCrypto - // crash in ecdsa.js. + // lambdas/print-status-handler { - ...base, - rootDir: '/lambdas/core-notifier-lambda', - displayName: 'lambdas/core-notifier-lambda', + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "transformIgnorePatterns": [ + "node_modules/(?!@nhsdigital/nhs-notify-event-schemas-supplier-api)", + ], + "rootDir": "/lambdas/print-status-handler", + "displayName": "lambdas/print-status-handler", + }, + + // lambdas/print-analyser + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/print-analyser", + "displayName": "lambdas/print-analyser", }, - // lambdas/print-status-handler — @nhsdigital/nhs-notify-event-schemas-supplier-api - // ships ESM source; it must be transformed by ts-jest rather than skipped. + // lambdas/report-scheduler { - ...base, - rootDir: '/lambdas/print-status-handler', - displayName: 'lambdas/print-status-handler', - transformIgnorePatterns: [ - 'node_modules/(?!@nhsdigital/nhs-notify-event-schemas-supplier-api)', + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/report-scheduler", + "displayName": "lambdas/report-scheduler", }, - // src/python-schema-generator — excludes merge-allof CLI entry point + // lambdas/report-event-transformer { - ...base, - rootDir: '/src/python-schema-generator', - displayName: 'src/python-schema-generator', - coveragePathIgnorePatterns: [ - ...base.coveragePathIgnorePatterns, - 'src/merge-allof-cli.ts', + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/report-event-transformer", + "displayName": "lambdas/report-event-transformer", }, - // src/typescript-schema-generator — excludes CLI entry points. - // Requires --experimental-vm-modules (set via NODE_OPTIONS in the - // test:unit:parallel script) because json-schema-to-typescript uses a - // dynamic import() of prettier at runtime, which Node.js rejects inside a - // Jest VM context without the flag. + // lambdas/move-scanned-files-lambda { - ...base, - rootDir: '/src/typescript-schema-generator', - displayName: 'src/typescript-schema-generator', - coveragePathIgnorePatterns: [ - ...base.coveragePathIgnorePatterns, - 'src/generate-types-cli.ts', - 'src/generate-validators-cli.ts', + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/move-scanned-files-lambda", + "displayName": "lambdas/move-scanned-files-lambda", }, - // src/cloudevents — uses its own jest.config.cjs (hand-rolled ts-jest options) - '/src/cloudevents', + // lambdas/report-generator + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/lambdas/report-generator", + "displayName": "lambdas/report-generator", + }, + + // utils/utils + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 85, + "functions": 85, + "lines": 85, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + "index.ts", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/utils/utils", + "displayName": "utils/utils", + }, + + // utils/sender-management + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 84, + "functions": 91, + "lines": 90, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/utils/sender-management", + "displayName": "utils/sender-management", + }, + + // src/cloudevents + { + "preset": "ts-jest", + "testEnvironment": "node", + "roots": [ + "", + ], + "testMatch": [ + "**/__tests__/**/*.ts", + "**/?(*.)+(spec|test).ts", + ], + "transform": { + "^.+\\.ts$": [ + "ts-jest", + { + "tsconfig": { + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "allowImportingTsExtensions": true, + "module": "commonjs", + "target": "ES2020", + "moduleResolution": "node", + "noEmit": true, + }, + "diagnostics": { + "ignoreCodes": [ + 1343, + ], + }, + }, + ], + }, + "collectCoverageFrom": [ + "tools/**/*.{ts,js,cjs}", + "!tools/**/*.d.ts", + "!tools/**/__tests__/**", + "!tools/**/*.test.ts", + "!tools/**/*.spec.ts", + "!tools/builder/build-schema.ts", + "!tools/generator/generate-example.ts", + "!tools/generator/manual-bundle-schema.ts", + "!tools/validator/validate.ts", + ], + "coverageDirectory": "coverage", + "coverageReporters": [ + "text", + "lcov", + "html", + "cobertura", + ], + "coveragePathIgnorePatterns": [ + "/node_modules/", + "/__tests__/", + ], + "coverageThreshold": { + "global": { + "branches": 60, + "functions": 60, + "lines": 60, + "statements": 60, + }, + }, + "moduleFileExtensions": [ + "ts", + "js", + "json", + ], + "moduleNameMapper": { + "^(.*)\\.ts$": "$1", + }, + "verbose": true, + "testTimeout": 10000, + "rootDir": "/src/cloudevents", + "displayName": "src/cloudevents", + }, + + // src/python-schema-generator + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + "src/merge-allof-cli.ts", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/src/python-schema-generator", + "displayName": "src/python-schema-generator", + }, + + // src/typescript-schema-generator + { + "preset": "ts-jest", + "clearMocks": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/__tests__/**", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.spec.{ts,tsx}", + ], + "coverageDirectory": "./.reports/unit/coverage", + "coverageProvider": "babel", + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": -10, + }, + }, + "coveragePathIgnorePatterns": [ + "/__tests__/", + "src/generate-types-cli.ts", + "src/generate-validators-cli.ts", + ], + "transform": { + "^.+\\.ts$": "ts-jest", + }, + "testPathIgnorePatterns": [ + ".build", + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)", + ], + "reporters": [ + "default", + [ + "jest-html-reporter", + { + "pageTitle": "Test Report", + "outputPath": "./.reports/unit/test-report.html", + "includeFailureMsg": true, + }, + ], + ], + "testEnvironment": "node", + "moduleDirectories": [ + "node_modules", + "src", + ], + "rootDir": "/src/typescript-schema-generator", + "displayName": "src/typescript-schema-generator", + } ], }; diff --git a/lambdas/core-notifier-lambda/src/__tests__/app/notify-api-client.test.ts b/lambdas/core-notifier-lambda/src/__tests__/app/notify-api-client.test.ts index 80077f41..e84eb293 100644 --- a/lambdas/core-notifier-lambda/src/__tests__/app/notify-api-client.test.ts +++ b/lambdas/core-notifier-lambda/src/__tests__/app/notify-api-client.test.ts @@ -12,9 +12,6 @@ import { IAccessTokenRepository, NotifyClient } from 'app/notify-api-client'; import { RequestAlreadyReceivedError } from 'domain/request-already-received-error'; jest.mock('utils'); -// Use a partial manual mock so that node-jose's require('crypto') still gets -// the real crypto implementation (needed for getHashes() etc.) while -// randomUUID is replaced with a jest.fn() for test control. jest.mock('node:crypto', () => ({ ...jest.requireActual('node:crypto'), randomUUID: jest.fn(), diff --git a/lambdas/core-notifier-lambda/src/__tests__/domain/mapper.test.ts b/lambdas/core-notifier-lambda/src/__tests__/domain/mapper.test.ts index cf2c37fd..dfb1d3bf 100644 --- a/lambdas/core-notifier-lambda/src/__tests__/domain/mapper.test.ts +++ b/lambdas/core-notifier-lambda/src/__tests__/domain/mapper.test.ts @@ -9,9 +9,6 @@ import { PDMResourceAvailable } from 'digital-letters-events'; import { randomUUID } from 'node:crypto'; jest.mock('utils'); -// Use a partial manual mock so that node-jose's require('crypto') still gets -// the real crypto implementation (needed for getHashes() etc.) while -// randomUUID is replaced with a jest.fn() for test control. jest.mock('node:crypto', () => ({ ...jest.requireActual('node:crypto'), randomUUID: jest.fn(), diff --git a/package.json b/package.json index 81b84538..459fe5fe 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "lint:fix": "npm run lint:fix --workspaces", "start": "npm run start --workspace frontend", "test:unit": "npm run test:unit --workspaces", - "test:unit:parallel": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --config jest.config.cjs", + "test:unit:parallel": "tsx scripts/generate-parallel-jest-config.ts && cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --config jest.config.cjs", "typecheck": "npm run typecheck --workspaces" }, "version": "0.0.1", diff --git a/scripts/generate-parallel-jest-config.ts b/scripts/generate-parallel-jest-config.ts new file mode 100644 index 00000000..ea7fe771 --- /dev/null +++ b/scripts/generate-parallel-jest-config.ts @@ -0,0 +1,153 @@ +/** + * Generates jest.config.cjs from the individual workspace jest.config.ts files. + */ + +import { execFileSync } from 'node:child_process'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; + +const repoRoot = path.resolve(__dirname, '..'); +const outputPath = path.join(repoRoot, 'jest.config.cjs'); + +interface PackageJson { + workspaces?: string[]; +} + +const rootPkg = JSON.parse( + fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'), +) as PackageJson; + +const workspaces: string[] = rootPkg.workspaces ?? []; + +/** + * Inline TypeScript written to a temp .ts file and executed by tsx so each + * workspace jest.config.ts is evaluated in an isolated Node process with a + * fresh module registry (preventing shared mutable baseJestConfig state). + */ +const EVALUATOR = (configPath: string): string => ` +import config from ${JSON.stringify(configPath)}; +process.stdout.write(JSON.stringify(config)); +`; + +/** Serialise a plain JS value to source code, indented with the given prefix. */ +function serialise(value: unknown, indent = ' '): string { + if (value === null) return 'null'; + if (value === undefined) return 'undefined'; + if (typeof value === 'string') return JSON.stringify(value); + if (typeof value === 'number' || typeof value === 'boolean') + return String(value); + + if (Array.isArray(value)) { + if (value.length === 0) return '[]'; + const items = value + .map((v) => `${indent} ${serialise(v, indent + ' ')}`) + .join(',\n'); + return `[\n${items},\n${indent}]`; + } + + if (typeof value === 'object') { + const entries = Object.entries(value as Record).filter( + ([, v]) => v !== undefined, + ); + if (entries.length === 0) return '{}'; + const lines = entries + .map( + ([k, v]) => + `${indent} ${JSON.stringify(k)}: ${serialise(v, indent + ' ')}`, + ) + .join(',\n'); + return `{\n${lines},\n${indent}}`; + } + + return String(value); +} + +interface ProjectEntry { + workspace: string; + config: Record; +} + +function main(): void { + const projects: ProjectEntry[] = []; + + for (const ws of workspaces) { + const wsDir = path.join(repoRoot, ws); + + const hasCjs = fs.existsSync(path.join(wsDir, 'jest.config.cjs')); + const hasTs = fs.existsSync(path.join(wsDir, 'jest.config.ts')); + + if (hasCjs && !hasTs) { + throw new Error( + `${ws} has jest.config.cjs but no jest.config.ts. ` + + `Migrate it to jest.config.ts so the generator can handle it uniformly.`, + ); + } + + if (!hasTs) { + // No Jest config → no Jest tests (e.g. src/digital-letters-events) + continue; + } + + // Evaluate the workspace config in an isolated tsx subprocess so that the + // shared mutable `baseJestConfig` object is freshly initialised for every + // workspace. Dynamic import() in the parent process would share the cached + // module instance and accumulate mutations. + const configPath = path.join(wsDir, 'jest.config.ts'); + const tsxBin = path.join(repoRoot, 'node_modules', '.bin', 'tsx'); + const tmpFile = path.join(os.tmpdir(), `jest-config-eval-${Date.now()}.ts`); + let json: string; + try { + fs.writeFileSync(tmpFile, EVALUATOR(configPath), 'utf8'); + json = execFileSync(tsxBin, [tmpFile], { + cwd: repoRoot, + encoding: 'utf8', + }); + } finally { + fs.rmSync(tmpFile, { force: true }); + } + const wsConfig = JSON.parse(json) as Record; + + // Inject rootDir and displayName. Jest resolves all relative paths inside a + // project entry relative to that project's rootDir. + const entry: Record = { + ...wsConfig, + rootDir: `/${ws}`, + displayName: ws, + }; + + projects.push({ workspace: ws, config: entry }); + } + + // Build the projects array source + const projectLines = projects.map((p) => { + const body = serialise(p.config, ' '); + return ` // ${p.workspace}\n ${body}`; + }); + + const banner = `/** + * Root Jest config — runs all TypeScript workspace test suites in + * parallel via Jest's native \`projects\` support. + * + * ⚠️ THIS FILE IS AUTO-GENERATED. Do not edit it directly. + * + * Generated by scripts/generate-parallel-jest-config.ts + */ + +/** @type {import('jest').Config} */ +module.exports = { + projects: [ +${projectLines.join(',\n\n')} + ], +}; +`; + + fs.writeFileSync(outputPath, banner, 'utf8'); + console.log(`Written: ${path.relative(repoRoot, outputPath)}`); + console.log(` ${projects.length} project(s) included`); + for (const p of projects) { + console.log(` ${p.workspace}`); + } +} + +main(); diff --git a/scripts/tests/unit.sh b/scripts/tests/unit.sh index 63a24fd3..d671e0d1 100755 --- a/scripts/tests/unit.sh +++ b/scripts/tests/unit.sh @@ -4,21 +4,6 @@ set -euo pipefail cd "$(git rev-parse --show-toplevel)" -# This file is for you! Edit it to call your unit test suite. Note that the same -# file will be called if you run it locally as if you run it on CI. - -# Replace the following line with something like: -# -# rails test:unit -# python manage.py test -# npm run test -# -# or whatever is appropriate to your project. You should *only* run your fast -# tests from here. If you want to run other test suites, see the predefined -# tasks in scripts/test.mk. - -# Timing helpers — records wall-clock duration for each labelled step and prints -# a summary table at exit so it's easy to see where the time is going. _timer_labels=() _timer_seconds=() @@ -51,43 +36,26 @@ print_timing_summary() { trap print_timing_summary EXIT -# run tests - -# TypeScript/JavaScript projects (npm workspace) -# Runs all Jest workspaces in parallel via the root jest.config.cjs projects -# config, which is faster than sequential `npm run test:unit --workspaces`. -# Note: src/cloudevents is included in the projects list in jest.config.cjs. -# Use || to capture any Jest failure so that Python tests always run; the exit -# code is propagated at the end of the script. run_timed "npm ci" npm ci run_timed "npm run generate-dependencies" npm run generate-dependencies -run_timed "npm run test:unit:parallel" npm run test:unit:parallel || jest_exit=$? - -# Python projects - run all install-dev steps sequentially (they share the same -# pip environment so cannot be parallelised), then run all coverage (pytest) -# steps in parallel since each writes to its own isolated output directory. +run_timed "Node unit tests (parallel)" npm run test:unit:parallel || jest_exit=$? -# ---- Phase 1: install all Python dev dependencies (sequential, shared pip env) ---- +# ---- Phase 1: install all Python dev dependencies (sequential) ---- +# Discover Python projects dynamically: any directory under src/, utils/, or +# lambdas/ whose Makefile defines both an `install-dev` target (Python deps) +# and a `coverage` target (pytest). This avoids maintaining a hardcoded list. echo "Installing Python dev dependencies..." -_python_projects=( - ./src/asyncapigenerator - ./src/cloudeventjekylldocs - ./src/eventcatalogasyncapiimporter - ./utils/py-utils - ./src/python-schema-generator - ./lambdas/mesh-acknowledge - ./lambdas/mesh-poll - ./lambdas/mesh-download - ./lambdas/report-sender +mapfile -t _python_projects < <( + grep -rl "^install-dev:" src/ utils/ lambdas/ --include="Makefile" 2>/dev/null \ + | xargs grep -l "^coverage:" \ + | xargs -I{} dirname {} \ + | sort ) for proj in "${_python_projects[@]}"; do run_timed "${proj}: install-dev" make -C "$proj" install-dev done # ---- Phase 2: run all coverage steps in parallel ---- -# Each job writes output to a temp file; we print them sequentially on -# completion so the log is readable. Non-zero exit codes are all collected and -# the script fails at the end if any job failed. echo "Running Python coverage in parallel..." _py_pids=() @@ -115,7 +83,7 @@ for i in "${!_py_pids[@]}"; do rm -f "${_py_logs[$i]}" done _py_end=$(date +%s) -_timer_labels+=("Python coverage (parallel)") +_timer_labels+=("Python unit tests (parallel)") _timer_seconds+=("$((_py_end - _py_start))") # merge coverage reports diff --git a/src/cloudevents/jest.config.cjs b/src/cloudevents/jest.config.cjs deleted file mode 100644 index f4045041..00000000 --- a/src/cloudevents/jest.config.cjs +++ /dev/null @@ -1,57 +0,0 @@ -/** @type {import('jest').Config} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - roots: [''], - testMatch: [ - '**/__tests__/**/*.ts', - '**/?(*.)+(spec|test).ts' - ], - transform: { - '^.+\\.ts$': ['ts-jest', { - tsconfig: { - esModuleInterop: true, - allowSyntheticDefaultImports: true, - allowImportingTsExtensions: true, - module: 'commonjs', - target: 'ES2020', - moduleResolution: 'node', - noEmit: true - }, - diagnostics: { - ignoreCodes: [1343] // Ignore TS1343: import.meta errors - } - }] - }, - collectCoverageFrom: [ - 'tools/**/*.{ts,js,cjs}', - '!tools/**/*.d.ts', - '!tools/**/__tests__/**', - '!tools/**/*.test.ts', - '!tools/**/*.spec.ts', - '!tools/builder/build-schema.ts', - '!tools/generator/generate-example.ts', - '!tools/generator/manual-bundle-schema.ts', - '!tools/validator/validate.ts' - ], - coverageDirectory: 'coverage', - coverageReporters: ['text', 'lcov', 'html', 'cobertura'], - coveragePathIgnorePatterns: [ - '/node_modules/', - '/__tests__/' - ], - coverageThreshold: { - global: { - branches: 60, - functions: 60, - lines: 60, - statements: 60 - } - }, - moduleFileExtensions: ['ts', 'js', 'json'], - moduleNameMapper: { - '^(.*)\\.ts$': '$1', - }, - verbose: true, - testTimeout: 10000 -}; diff --git a/src/cloudevents/jest.config.ts b/src/cloudevents/jest.config.ts new file mode 100644 index 00000000..d03dd4d4 --- /dev/null +++ b/src/cloudevents/jest.config.ts @@ -0,0 +1,57 @@ +import type { Config } from 'jest'; + +const config: Config = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: [''], + testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'], + transform: { + '^.+\\.ts$': [ + 'ts-jest', + { + tsconfig: { + esModuleInterop: true, + allowSyntheticDefaultImports: true, + allowImportingTsExtensions: true, + module: 'commonjs', + target: 'ES2020', + moduleResolution: 'node', + noEmit: true, + }, + diagnostics: { + ignoreCodes: [1343], // Ignore TS1343: import.meta errors + }, + }, + ], + }, + collectCoverageFrom: [ + 'tools/**/*.{ts,js,cjs}', + '!tools/**/*.d.ts', + '!tools/**/__tests__/**', + '!tools/**/*.test.ts', + '!tools/**/*.spec.ts', + '!tools/builder/build-schema.ts', + '!tools/generator/generate-example.ts', + '!tools/generator/manual-bundle-schema.ts', + '!tools/validator/validate.ts', + ], + coverageDirectory: 'coverage', + coverageReporters: ['text', 'lcov', 'html', 'cobertura'], + coveragePathIgnorePatterns: ['/node_modules/', '/__tests__/'], + coverageThreshold: { + global: { + branches: 60, + functions: 60, + lines: 60, + statements: 60, + }, + }, + moduleFileExtensions: ['ts', 'js', 'json'], + moduleNameMapper: { + '^(.*)\\.ts$': '$1', + }, + verbose: true, + testTimeout: 10000, +}; + +export default config; From 2a54862b18d7da86b071210abbde85b8b958b699 Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Mon, 23 Mar 2026 13:33:54 +0000 Subject: [PATCH 7/7] CCM-14615: clean ups --- jest.config.cjs | 1233 ----------------------------------------------- 1 file changed, 1233 deletions(-) delete mode 100644 jest.config.cjs diff --git a/jest.config.cjs b/jest.config.cjs deleted file mode 100644 index 81b984e2..00000000 --- a/jest.config.cjs +++ /dev/null @@ -1,1233 +0,0 @@ -/** - * Root Jest config — runs all TypeScript workspace test suites in - * parallel via Jest's native `projects` support. - * - * ⚠️ THIS FILE IS AUTO-GENERATED. Do not edit it directly. - * - * Generated by scripts/generate-parallel-jest-config.ts - */ - -/** @type {import('jest').Config} */ -module.exports = { - projects: [ - // lambdas/file-scanner-lambda - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/file-scanner-lambda", - "displayName": "lambdas/file-scanner-lambda", - }, - - // lambdas/key-generation - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - "lambda.ts", - "/config.ts", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/key-generation", - "displayName": "lambdas/key-generation", - }, - - // lambdas/refresh-apim-access-token - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 90, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - "cli.ts", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/refresh-apim-access-token", - "displayName": "lambdas/refresh-apim-access-token", - }, - - // lambdas/pdm-mock-lambda - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 90, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/pdm-mock-lambda", - "displayName": "lambdas/pdm-mock-lambda", - }, - - // lambdas/pdm-poll-lambda - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/pdm-poll-lambda", - "displayName": "lambdas/pdm-poll-lambda", - }, - - // lambdas/ttl-create-lambda - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/ttl-create-lambda", - "displayName": "lambdas/ttl-create-lambda", - }, - - // lambdas/ttl-handle-expiry-lambda - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/ttl-handle-expiry-lambda", - "displayName": "lambdas/ttl-handle-expiry-lambda", - }, - - // lambdas/ttl-poll-lambda - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/ttl-poll-lambda", - "displayName": "lambdas/ttl-poll-lambda", - }, - - // lambdas/pdm-uploader-lambda - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/pdm-uploader-lambda", - "displayName": "lambdas/pdm-uploader-lambda", - }, - - // lambdas/print-sender-lambda - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/print-sender-lambda", - "displayName": "lambdas/print-sender-lambda", - }, - - // lambdas/core-notifier-lambda - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/core-notifier-lambda", - "displayName": "lambdas/core-notifier-lambda", - }, - - // lambdas/print-status-handler - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "transformIgnorePatterns": [ - "node_modules/(?!@nhsdigital/nhs-notify-event-schemas-supplier-api)", - ], - "rootDir": "/lambdas/print-status-handler", - "displayName": "lambdas/print-status-handler", - }, - - // lambdas/print-analyser - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/print-analyser", - "displayName": "lambdas/print-analyser", - }, - - // lambdas/report-scheduler - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/report-scheduler", - "displayName": "lambdas/report-scheduler", - }, - - // lambdas/report-event-transformer - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/report-event-transformer", - "displayName": "lambdas/report-event-transformer", - }, - - // lambdas/move-scanned-files-lambda - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/move-scanned-files-lambda", - "displayName": "lambdas/move-scanned-files-lambda", - }, - - // lambdas/report-generator - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/lambdas/report-generator", - "displayName": "lambdas/report-generator", - }, - - // utils/utils - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 85, - "functions": 85, - "lines": 85, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - "index.ts", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/utils/utils", - "displayName": "utils/utils", - }, - - // utils/sender-management - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 84, - "functions": 91, - "lines": 90, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/utils/sender-management", - "displayName": "utils/sender-management", - }, - - // src/cloudevents - { - "preset": "ts-jest", - "testEnvironment": "node", - "roots": [ - "", - ], - "testMatch": [ - "**/__tests__/**/*.ts", - "**/?(*.)+(spec|test).ts", - ], - "transform": { - "^.+\\.ts$": [ - "ts-jest", - { - "tsconfig": { - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "allowImportingTsExtensions": true, - "module": "commonjs", - "target": "ES2020", - "moduleResolution": "node", - "noEmit": true, - }, - "diagnostics": { - "ignoreCodes": [ - 1343, - ], - }, - }, - ], - }, - "collectCoverageFrom": [ - "tools/**/*.{ts,js,cjs}", - "!tools/**/*.d.ts", - "!tools/**/__tests__/**", - "!tools/**/*.test.ts", - "!tools/**/*.spec.ts", - "!tools/builder/build-schema.ts", - "!tools/generator/generate-example.ts", - "!tools/generator/manual-bundle-schema.ts", - "!tools/validator/validate.ts", - ], - "coverageDirectory": "coverage", - "coverageReporters": [ - "text", - "lcov", - "html", - "cobertura", - ], - "coveragePathIgnorePatterns": [ - "/node_modules/", - "/__tests__/", - ], - "coverageThreshold": { - "global": { - "branches": 60, - "functions": 60, - "lines": 60, - "statements": 60, - }, - }, - "moduleFileExtensions": [ - "ts", - "js", - "json", - ], - "moduleNameMapper": { - "^(.*)\\.ts$": "$1", - }, - "verbose": true, - "testTimeout": 10000, - "rootDir": "/src/cloudevents", - "displayName": "src/cloudevents", - }, - - // src/python-schema-generator - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - "src/merge-allof-cli.ts", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/src/python-schema-generator", - "displayName": "src/python-schema-generator", - }, - - // src/typescript-schema-generator - { - "preset": "ts-jest", - "clearMocks": true, - "collectCoverage": true, - "collectCoverageFrom": [ - "src/**/*.{ts,tsx}", - "!src/**/*.d.ts", - "!src/**/__tests__/**", - "!src/**/*.test.{ts,tsx}", - "!src/**/*.spec.{ts,tsx}", - ], - "coverageDirectory": "./.reports/unit/coverage", - "coverageProvider": "babel", - "coverageThreshold": { - "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": -10, - }, - }, - "coveragePathIgnorePatterns": [ - "/__tests__/", - "src/generate-types-cli.ts", - "src/generate-validators-cli.ts", - ], - "transform": { - "^.+\\.ts$": "ts-jest", - }, - "testPathIgnorePatterns": [ - ".build", - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)", - ], - "reporters": [ - "default", - [ - "jest-html-reporter", - { - "pageTitle": "Test Report", - "outputPath": "./.reports/unit/test-report.html", - "includeFailureMsg": true, - }, - ], - ], - "testEnvironment": "node", - "moduleDirectories": [ - "node_modules", - "src", - ], - "rootDir": "/src/typescript-schema-generator", - "displayName": "src/typescript-schema-generator", - } - ], -};