diff --git a/.github/workflows/node-api.yml b/.github/workflows/node-api.yml index 22b7c2a88..3c45e2800 100644 --- a/.github/workflows/node-api.yml +++ b/.github/workflows/node-api.yml @@ -114,12 +114,6 @@ jobs: - name: Build Python API working-directory: ./api-node run: yarn python-api:build - - name: Start Python API + - name: Run E2E tests working-directory: ./api-node - run: yarn python-api:start - - name: Run e2e tests - working-directory: ./api-node - run: yarn test:e2e - - name: Stop Python API - working-directory: ./api-node - run: yarn python-api:stop + run: yarn test:e2e:ci diff --git a/api-node/package.json b/api-node/package.json index f6b4d9ef7..1113148e1 100644 --- a/api-node/package.json +++ b/api-node/package.json @@ -8,6 +8,8 @@ "scripts": { "build": "nest build && yarn sentry:sourcemaps", "build:ci": "nest build", + "clean": "rm -rf dist", + "clean:all": "yarn clean && rm -rf dist node_modules && yarn install", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", @@ -19,10 +21,11 @@ "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json --testPathIgnorePatterns=everything.e2e-spec.ts", + "test:e2e:ci": "NODE_ENV=test ts-node ./scripts/run-e2e-tests.ts", "test:e2e:urls": "NODE_ENV=test jest --config ./test/jest-e2e.json everything.e2e-spec.ts", "test:e2e:urls:generate": "ts-node ./test/utils/getUrls.ts", "python-api:build": "docker build -t registry-api-server ../", - "python-api:start": "docker run -d -p 5031:5030 registry-api-server", + "python-api:start": "docker start $(docker ps -aq --filter ancestor=registry-api-server) && echo 'Flask API started successfully' || (docker run -d -p 5031:5030 registry-api-server && echo 'Flask API started successfully')", "python-api:stop": "docker stop $(docker ps -q --filter ancestor=registry-api-server)", "start:all": "yarn build && yarn start:dev & yarn python-api:start", "sentry:sourcemaps": "sentry-cli sourcemaps inject --org sentry --project release-registry-nestjs ./dist && sentry-cli sourcemaps upload --org sentry --project release-registry-nestjs ./dist" diff --git a/api-node/scripts/run-e2e-tests.ts b/api-node/scripts/run-e2e-tests.ts new file mode 100644 index 000000000..65f0980f3 --- /dev/null +++ b/api-node/scripts/run-e2e-tests.ts @@ -0,0 +1,68 @@ +// A script that runs the e2e tests +// 1. Starts the NestJS and Flask APIs in Docker +// 2. Runs the tests +// 3. Stops the NestJS and Flask APIs + +import * as childProcess from 'child_process'; + +const nodeApi = childProcess.spawn('yarn', ['start:prod']); + +let nodeApiStarted = false; +let pythonApiStarted = false; + +nodeApi.stdout.on('data', (data) => { + console.log('[NestJS]', data.toString()); + if (data.toString().includes('application successfully started')) { + nodeApiStarted = true; + checkAndRunTests(); + } +}); + +nodeApi.stderr.on('data', (data) => { + console.error('[NestJS]', data.toString()); +}); + +const pythonApi = childProcess.spawn('yarn', ['python-api:start']); + +pythonApi.stdout.on('data', (data) => { + console.log('[Flask]', data.toString()); + if (data.toString().includes('Flask API started successfully')) { + pythonApiStarted = true; + checkAndRunTests(); + } +}); + +pythonApi.stderr.on('data', (data) => { + console.error('[Flask]', data.toString()); +}); + +function checkAndRunTests(): void { + console.log('checkAndRunTests', { nodeApiStarted, pythonApiStarted }); + if (nodeApiStarted && pythonApiStarted) { + console.log('Both APIs are running. Starting e2e tests...'); + const tests = childProcess.spawn('yarn', ['test:e2e']); + + tests.stdout.on('data', (data) => { + console.log('[Tests]', data.toString()); + }); + + tests.stderr.on('data', (data) => { + console.error('[Tests]', data.toString()); + }); + + tests.on('close', (code) => { + console.log(`Tests finished with code ${code}`); + cleanup(code); + }); + } +} + +function cleanup(code: number): void { + console.log('Cleaning up...'); + nodeApi.kill(); + pythonApi.kill(); + childProcess.execSync('yarn python-api:stop'); + process.exit(code); +} + +process.on('SIGINT', () => cleanup(0)); diff --git a/api-node/test/utils/getUrls.ts b/api-node/test/utils/getUrls.ts index 94df39fc5..1c6ee3cb8 100644 --- a/api-node/test/utils/getUrls.ts +++ b/api-node/test/utils/getUrls.ts @@ -1,4 +1,4 @@ -import { PYTHON_API_URL } from '../utils'; +import { PYTHON_API_URL } from './makeRequest'; import * as fs from 'fs'; import * as path from 'path'; diff --git a/api-node/test/utils/makeRequest.ts b/api-node/test/utils/makeRequest.ts index e5b7bd467..f8152f575 100644 --- a/api-node/test/utils/makeRequest.ts +++ b/api-node/test/utils/makeRequest.ts @@ -26,10 +26,11 @@ export async function makeDuplexRequest( path: string, options: RequestInit = { redirect: 'manual' }, ): Promise<{ python: ResponseParts; node: ResponseParts }> { - const [pythonResponse, nodeResponse] = await Promise.all([ - fetch(`${PYTHON_API_URL}${path}`, options), - fetch(`${API_NODE_URL}${path}`, options), - ]); + const pythonResponse = await fetch(`${PYTHON_API_URL}${path}`, options); + const pythonBody = await pythonResponse.text(); + + const nodeResponse = await fetch(`${API_NODE_URL}${path}`, options); + const nodeBody = await nodeResponse.text(); const pythonStatus = pythonResponse.status; const nodeStatus = nodeResponse.status; @@ -45,9 +46,6 @@ export async function makeDuplexRequest( nodeHeaders: normalizedNodeHeaders, } = normalizeHeaders(originalPythonHeaders, originalNodeHeaders); - const pythonBody = await pythonResponse.text(); - const nodeBody = await nodeResponse.text(); - return { python: { status: pythonStatus, @@ -86,7 +84,7 @@ function normalizeHeaders( const pythonContentLength = parseInt(pythonHeaders['content-length']); const nodeContentLength = parseInt(nodeHeaders['content-length']); if (pythonContentLength - nodeContentLength === 1) { - pythonHeaders['content-length'] = nodeHeaders['content-length']; + nodeHeaders['content-length'] = pythonHeaders['content-length']; } } diff --git a/api-node/tsconfig.build.json b/api-node/tsconfig.build.json index 64f86c6bd..052134cd3 100644 --- a/api-node/tsconfig.build.json +++ b/api-node/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", - "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] + "exclude": ["node_modules", "test", "dist", "**/*spec.ts", "scripts"] } diff --git a/api-node/tsconfig.json b/api-node/tsconfig.json index cc47eb846..abc2067b8 100644 --- a/api-node/tsconfig.json +++ b/api-node/tsconfig.json @@ -18,6 +18,6 @@ "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false, "inlineSources": true, - "sourceRoot": "/" + "sourceRoot": "/src" } }