From 714461d87a0a8db5b8d6d03005dbc28dc367ca21 Mon Sep 17 00:00:00 2001 From: Pranish Nepal Date: Fri, 29 May 2026 11:23:30 -0400 Subject: [PATCH] ci: add infra setup for integ tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the Docker foundation for integration testing advanced-wallets. Previously, all tests stubbed the AWM client entirely using Nock, meaning the MBE → AWM HTTP contract was never exercised automatically. This PR establishes the infrastructure to run integ tests where AWM and MBE start as actual HTTP servers and communicate over real TCP sockets. This commit adds a validation test to verify the changes. Follow up PRs will add tests and expand on this PR. Ticket: WAL-1504, WAL-1507 --- .mocharc.integ.js | 9 +++ docker-compose.integ.yml | 7 +++ integration-tests.Dockerfile | 39 ++++++++++++ package.json | 2 + scripts/run-integration-tests.sh | 13 ++++ .../integration/health.integ.test.ts | 60 +++++++++++++++++++ src/__tests__/integration/helpers/servers.ts | 15 +++++ tsconfig.integ.json | 8 +++ 8 files changed, 153 insertions(+) create mode 100644 .mocharc.integ.js create mode 100644 docker-compose.integ.yml create mode 100644 integration-tests.Dockerfile create mode 100755 scripts/run-integration-tests.sh create mode 100644 src/__tests__/integration/health.integ.test.ts create mode 100644 src/__tests__/integration/helpers/servers.ts create mode 100644 tsconfig.integ.json diff --git a/.mocharc.integ.js b/.mocharc.integ.js new file mode 100644 index 00000000..cc81db13 --- /dev/null +++ b/.mocharc.integ.js @@ -0,0 +1,9 @@ +module.exports = { + require: ['ts-node/register'], + extension: ['ts'], + timeout: 30000, + ui: 'bdd', + spec: 'src/**/__tests__/integration/**/*.integ.test.ts', + recursive: true, + exit: true, +}; diff --git a/docker-compose.integ.yml b/docker-compose.integ.yml new file mode 100644 index 00000000..34b3dae3 --- /dev/null +++ b/docker-compose.integ.yml @@ -0,0 +1,7 @@ +services: + integration-tests: + build: + context: . + dockerfile: integration-tests.Dockerfile + environment: + - NODE_ENV=test diff --git a/integration-tests.Dockerfile b/integration-tests.Dockerfile new file mode 100644 index 00000000..8ae34bfc --- /dev/null +++ b/integration-tests.Dockerfile @@ -0,0 +1,39 @@ +# Stage 1 — build +FROM node:22.1.0-alpine@sha256:487dc5d5122d578e13f2231aa4ac0f63068becd921099c4c677c850df93bede8 AS builder + +ENV NODE_ENV=test \ + TZ=UTC \ + LANG=C.UTF-8 + +WORKDIR /usr/src/app + +# native addons (e.g. keccak) require build tools +RUN apk add --no-cache python3 make g++ gcc linux-headers + +COPY package.json package-lock.json ./ + +RUN npm ci + +COPY . . + +RUN npm run build + +# Stage 2 — test runner +FROM node:22.1.0-alpine@sha256:487dc5d5122d578e13f2231aa4ac0f63068becd921099c4c677c850df93bede8 AS runner + +ENV NODE_ENV=test \ + TZ=UTC \ + LANG=C.UTF-8 + +WORKDIR /usr/src/app + +COPY --from=builder /usr/src/app/dist ./dist +COPY --from=builder /usr/src/app/node_modules ./node_modules +COPY --from=builder /usr/src/app/bin ./bin +COPY --from=builder /usr/src/app/src ./src +COPY --from=builder /usr/src/app/package.json . +COPY --from=builder /usr/src/app/.mocharc.integ.js . +COPY --from=builder /usr/src/app/tsconfig.integ.json . +COPY --from=builder /usr/src/app/tsconfig.json . + +CMD ["npm", "run", "test:integration"] diff --git a/package.json b/package.json index 7674eb1c..2a014720 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "lint": "eslint --quiet --ignore-pattern scripts/bump-version.ts .", "lint:fix": "eslint --quiet --ignore-pattern scripts/bump-version.ts . --fix", "generate-test-ssl": "openssl req -x509 -newkey rsa:2048 -keyout demo.key -out demo.crt -days 365 -nodes -subj '/CN=localhost'", + "test:integration": "NODE_ENV=test TS_NODE_PROJECT=tsconfig.integ.json mocha --config .mocharc.integ.js", + "docker:test:integration": "bash scripts/run-integration-tests.sh", "generate:openapi:masterExpress": "npx @api-ts/openapi-generator --name @bitgo/master-bitgo-express ./src/masterBitgoExpress/routers/index.ts > masterBitgoExpress.json", "container:build:master-bitgo-express": "podman build --build-arg PORT=3081 -t master-bitgo-express .", "container:build:advanced-wallet-manager": "podman build --build-arg PORT=3080 -t advanced-wallet-manager .", diff --git a/scripts/run-integration-tests.sh b/scripts/run-integration-tests.sh new file mode 100755 index 00000000..4bb812f7 --- /dev/null +++ b/scripts/run-integration-tests.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +echo "Running integration tests..." + +docker-compose -f docker-compose.integ.yml up --build --abort-on-container-exit || true + +exit_code=$? + +docker-compose -f docker-compose.integ.yml down + +exit $exit_code diff --git a/src/__tests__/integration/health.integ.test.ts b/src/__tests__/integration/health.integ.test.ts new file mode 100644 index 00000000..2ec54fbc --- /dev/null +++ b/src/__tests__/integration/health.integ.test.ts @@ -0,0 +1,60 @@ +import 'should'; +import * as http from 'http'; +import { app as awmApp } from '../../advancedWalletManagerApp'; +import { app as mbeApp } from '../../masterBitGoExpressApp'; +import { AppMode, TlsMode, SigningMode } from '../../shared/types'; +import { listen, close } from './helpers/servers'; + +describe('integration — health checks', () => { + let awmServer: http.Server; + let mbeServer: http.Server; + let awmPort: number; + let mbePort: number; + + before(async () => { + awmServer = http.createServer( + awmApp({ + appMode: AppMode.ADVANCED_WALLET_MANAGER, + tlsMode: TlsMode.DISABLED, + signingMode: SigningMode.LOCAL, + port: 0, + bind: '127.0.0.1', + timeout: 30000, + httpLoggerFile: '', + keyProviderUrl: 'http://127.0.0.1:3082', + }), + ); + awmPort = await listen(awmServer); + + mbeServer = http.createServer( + mbeApp({ + appMode: AppMode.MASTER_EXPRESS, + tlsMode: TlsMode.DISABLED, + port: 0, + bind: '127.0.0.1', + timeout: 30000, + httpLoggerFile: '', + env: 'test', + disableEnvCheck: true, + advancedWalletManagerUrl: `http://127.0.0.1:${awmPort}`, + awmServerCertAllowSelfSigned: true, + }), + ); + mbePort = await listen(mbeServer); + }); + + after(async () => { + await close(awmServer); + await close(mbeServer); + }); + + it('AWM /ping returns 200', async () => { + const res = await fetch(`http://127.0.0.1:${awmPort}/ping`, { method: 'POST' }); + res.status.should.equal(200); + }); + + it('MBE /advancedwallet/ping returns 200', async () => { + const res = await fetch(`http://127.0.0.1:${mbePort}/advancedwallet/ping`, { method: 'POST' }); + res.status.should.equal(200); + }); +}); diff --git a/src/__tests__/integration/helpers/servers.ts b/src/__tests__/integration/helpers/servers.ts new file mode 100644 index 00000000..b17a3f82 --- /dev/null +++ b/src/__tests__/integration/helpers/servers.ts @@ -0,0 +1,15 @@ +import * as http from 'http'; +import * as net from 'net'; + +export function listen(server: http.Server): Promise { + return new Promise((resolve, reject) => { + server.once('error', reject); + server.listen(0, '127.0.0.1', () => { + resolve((server.address() as net.AddressInfo).port); + }); + }); +} + +export function close(server: http.Server): Promise { + return new Promise((resolve) => server.close(() => resolve())); +} diff --git a/tsconfig.integ.json b/tsconfig.integ.json new file mode 100644 index 00000000..eabb113b --- /dev/null +++ b/tsconfig.integ.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "lib": ["ES2020", "DOM"], + "types": ["mocha", "node"] + }, + "include": ["src/__tests__/integration/**/*"] +}