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/**/*"] +}