From 8402bcbeedc33e8a255bcf8a01e3a0cb6918b997 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Fri, 1 Mar 2024 17:17:23 +0100 Subject: [PATCH 01/43] feat: move from fake ip vars to test types vars --- CONTRIBUTING.md | 4 ++-- docs/staging-env.md | 12 ++++++------ package.json | 10 +++++----- src/lib/get-probe-ip.ts | 6 +++--- src/lib/ws/helper/probe-ip-limit.ts | 2 +- src/probe/builder.ts | 2 +- wallaby.js | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2fd4fbef..644ecdac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ Once the API is live, you can spin up a probe instance by running as described a ### Environment variables - `PORT=3000` environment variable can start the API on another port (default is 3000) -- `FAKE_PROBE_IP=api` environment variable can be used to make debug easier. When defined, every Probe +- `FAKE_PROBE_IP=1` environment variable can be used to make debug easier. When defined, every Probe that connects to the API will get an IP address from the list of predefined "real" addresses. ### Testing @@ -43,7 +43,7 @@ Most IDEs have plugins integrating the used linter (eslint), including support f - `NEW_RELIC_LICENSE_KEY={value}` used in production to send APM metrics to new relic - `NEW_RELIC_APP_NAME={value}` used in production to send APM mentrics to new relic - `NEW_RELIC_ENABLED=false` used in development to disable newrelic monitoring -- `FAKE_PROBE_IP={api|probe}` used in development to either use a random fake ip from the API or a fake ip from Probe +- `FAKE_PROBE_IP=1` used in development to use a random fake ip assigned by the API - `ADMIN_KEY={value}` used to access additional information over the API - `SYSTEM_API_KEY={value}` used for integration with the dashboard - `DB_CONNECTION_HOST`, `DB_CONNECTION_USER`, `DB_CONNECTION_PASSWORD`, and `DB_CONNECTION_DATABASE` database connection details diff --git a/docs/staging-env.md b/docs/staging-env.md index 573eaf92..712495a5 100644 --- a/docs/staging-env.md +++ b/docs/staging-env.md @@ -35,7 +35,7 @@ exit ## API -Runs 2 API instances on ports 3001 and 3002 behind the haproxy on port 80. Geoip client is mocked so all probes get same location. `FAKE_PROBE_IP=probe` makes API to use fake ip provided by the probe. +Runs 2 API instances on ports 3001 and 3002 behind the haproxy on port 80. Geoip client is mocked so all probes get same location. `TEST_MODE=perf` makes API to use fake ip provided by the probe. ```bash # Update that variables before start @@ -79,20 +79,20 @@ npm run build # Run the app echo 'Run 2 app instances using: -PORT=3001 HOSTNAME=3001 REDIS_URL=redis://default:$REDIS_PASSWORD@$REDIS_HOST:6379 NODE_ENV=production ADMIN_KEY=admin FAKE_PROBE_IP=probe NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false node dist/index.js +PORT=3001 HOSTNAME=3001 REDIS_URL=redis://default:$REDIS_PASSWORD@$REDIS_HOST:6379 NODE_ENV=production ADMIN_KEY=admin TEST_MODE=perf NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false node dist/index.js and -PORT=3002 HOSTNAME=3002 REDIS_URL=redis://default:$REDIS_PASSWORD@$REDIS_HOST:6379 NODE_ENV=production ADMIN_KEY=admin FAKE_PROBE_IP=probe NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false node dist/index.js +PORT=3002 HOSTNAME=3002 REDIS_URL=redis://default:$REDIS_PASSWORD@$REDIS_HOST:6379 NODE_ENV=production ADMIN_KEY=admin TEST_MODE=perf NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false node dist/index.js ' ``` ## Probe -Runs `PROBES_COUNT` number of probe processes. They all get a random fake ip which is passed to the API. Value of the `FAKE_PROBE_IP` is the first octet of the fake ip. Each probe process requires ~40mb of RAM. +Runs `PROBES_COUNT` number of probe processes. They all get a random fake ip which is passed to the API. Value of the `FAKE_IP_FIRST_OCTET` is the first octet of the fake ip. Each probe process requires ~40mb of RAM. ```bash # Update that variables before start API_HOST= -FAKE_PROBE_IP=1 +FAKE_IP_FIRST_OCTET=1 PROBES_COUNT=300 # Install node @@ -124,6 +124,6 @@ sudo dpkg --extract "/tmp/expect.deb" / # Auto start the probes sudo npm i -g pm2 sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u ubuntu --hp /home/ubuntu -FAKE_PROBE_IP=$FAKE_PROBE_IP NODE_ENV=development PROBES_COUNT=$PROBES_COUNT API_HOST=ws://$API_HOST pm2 start dist/index.js +FAKE_IP_FIRST_OCTET=$FAKE_IP_FIRST_OCTET NODE_ENV=development PROBES_COUNT=$PROBES_COUNT API_HOST=ws://$API_HOST pm2 start dist/index.js pm2 save ``` diff --git a/package.json b/package.json index bb886220..034c26d4 100644 --- a/package.json +++ b/package.json @@ -112,14 +112,14 @@ "scripts": { "build": "tsc && npm run download:files && npm run commit:hash && cp -r public dist", "commit:hash": "git rev-parse HEAD > data/LAST_API_COMMIT_HASH.txt", - "coverage": "npm run clean && NODE_ENV=test FAKE_PROBE_IP=api NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false c8 mocha", + "coverage": "npm run clean && NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false c8 mocha", "clean": "rimraf coverage", "download:files": "tsx src/lib/download-files.ts", "prepare": "npm run download:files; husky install || echo 'Failed to install husky'", "stats": "NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false tsx probes-stats/known.ts", "start": "NODE_ENV=production node --max_old_space_size=3584 --max-semi-space-size=128 --experimental-loader newrelic/esm-loader.mjs -r newrelic dist/src/index.js", - "start:dev": "NODE_ENV=development FAKE_PROBE_IP=api NEW_RELIC_ENABLED=false tsx src/index.ts", - "start:test": "NODE_ENV=test FAKE_PROBE_IP=api NEW_RELIC_ENABLED=false tsx src/index.ts", + "start:dev": "NODE_ENV=development FAKE_PROBE_IP=1 NEW_RELIC_ENABLED=false tsx src/index.ts", + "start:test": "NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false tsx src/index.ts", "lint": "npm run lint:js && npm run lint:types && npm run lint:docs", "lint:fix": "npm run lint:js:fix && npm run lint:types && npm run lint:docs", "lint:docs": "spectral lint public/**/spec.yaml", @@ -128,8 +128,8 @@ "lint:types": "tsc --noEmit", "test": "npm run lint && npm run test:mocha && npm run test:portman", "test:dist": "node --test-reporter=spec test/dist.js", - "test:mocha": "NODE_ENV=test FAKE_PROBE_IP=api NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false mocha", - "test:mocha:dev": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test FAKE_PROBE_IP=api NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false mocha", + "test:mocha": "NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false mocha", + "test:mocha:dev": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false mocha", "test:perf": "tsx test-perf/index.ts", "test:portman": "TEST_DONT_RESTART_WORKERS=1 start-test 'npm run start:test' http://localhost:3000/health 'npm run test:portman:create && npm run test:portman:run' 2> /dev/null || E=$?; rm -rf tmp; exit $E;", "test:portman:create": "mkdir -p tmp && redocly bundle public/v1/spec.yaml > tmp/spec.yaml && portman --cliOptionsFile test/tests/contract/portman-cli.json", diff --git a/src/lib/get-probe-ip.ts b/src/lib/get-probe-ip.ts index a5ce2f83..8668b768 100644 --- a/src/lib/get-probe-ip.ts +++ b/src/lib/get-probe-ip.ts @@ -2,12 +2,12 @@ import requestIp from 'request-ip'; import type { Socket } from 'socket.io'; const getProbeIp = (socket: Socket) => { - if (process.env['NODE_ENV'] === 'test') { + if (process.env['TEST_MODE'] === 'unit') { return '1.2.3.4'; } // Use random ip assigned by the API - if (process.env['FAKE_PROBE_IP'] === 'api') { + if (process.env['FAKE_PROBE_IP']) { const samples = [ '213.136.174.80', '18.200.0.1', @@ -27,7 +27,7 @@ const getProbeIp = (socket: Socket) => { } // Use fake ip provided by the probe - if (process.env['FAKE_PROBE_IP'] === 'probe') { + if (process.env['TEST_MODE'] === 'perf') { return socket.handshake.query['fakeIp'] as string; } diff --git a/src/lib/ws/helper/probe-ip-limit.ts b/src/lib/ws/helper/probe-ip-limit.ts index 6cde057d..5efe0b63 100644 --- a/src/lib/ws/helper/probe-ip-limit.ts +++ b/src/lib/ws/helper/probe-ip-limit.ts @@ -6,7 +6,7 @@ import type { LRUOptions } from './throttle.js'; const logger = scopedLogger('ws:limit'); export const verifyIpLimit = async (ip: string, socketId: string): Promise => { - if (process.env['FAKE_PROBE_IP'] === 'api') { + if (process.env['FAKE_PROBE_IP'] || process.env['TEST_MODE'] === 'unit') { return; } diff --git a/src/probe/builder.ts b/src/probe/builder.ts index 1ee4be73..d0db01b5 100644 --- a/src/probe/builder.ts +++ b/src/probe/builder.ts @@ -52,7 +52,7 @@ export const buildProbe = async (socket: Socket): Promise => { let ipInfo; - if (process.env['FAKE_PROBE_IP'] === 'probe') { + if (process.env['TEST_MODE'] === 'perf') { ipInfo = fakeLookup(); } else if (!isIpPrivate(ip)) { ipInfo = await geoipClient.lookup(ip); diff --git a/wallaby.js b/wallaby.js index 73f52fcb..9dbc6e7b 100644 --- a/wallaby.js +++ b/wallaby.js @@ -42,7 +42,7 @@ export default function wallaby () { params: { runner: '--experimental-specifier-resolution=node --loader ' + url.pathToFileURL(path.join(__dirname, 'node_modules/testdouble/lib/index.mjs')), - env: 'NODE_ENV=test;NEW_RELIC_ENABLED=false;NEW_RELIC_LOG_ENABLED=false;FAKE_PROBE_IP=api', + env: 'NODE_ENV=test;NEW_RELIC_ENABLED=false;NEW_RELIC_LOG_ENABLED=false;TEST_MODE=unit', }, }, preprocessors: { From c03d3b1c5b967f1911eb7077c0c24980e36a266e Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Fri, 1 Mar 2024 17:30:59 +0100 Subject: [PATCH 02/43] feat: add mock e2e scripts --- package.json | 6 ++++-- src/lib/get-probe-ip.ts | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 034c26d4..4b2d7c49 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "stats": "NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false tsx probes-stats/known.ts", "start": "NODE_ENV=production node --max_old_space_size=3584 --max-semi-space-size=128 --experimental-loader newrelic/esm-loader.mjs -r newrelic dist/src/index.js", "start:dev": "NODE_ENV=development FAKE_PROBE_IP=1 NEW_RELIC_ENABLED=false tsx src/index.ts", - "start:test": "NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false tsx src/index.ts", + "start:test": "NODE_ENV=test knex migrate:latest && NODE_ENV=test TEST_MODE=e2e NEW_RELIC_ENABLED=false tsx src/index.ts", "lint": "npm run lint:js && npm run lint:types && npm run lint:docs", "lint:fix": "npm run lint:js:fix && npm run lint:types && npm run lint:docs", "lint:docs": "spectral lint public/**/spec.yaml", @@ -133,7 +133,9 @@ "test:perf": "tsx test-perf/index.ts", "test:portman": "TEST_DONT_RESTART_WORKERS=1 start-test 'npm run start:test' http://localhost:3000/health 'npm run test:portman:create && npm run test:portman:run' 2> /dev/null || E=$?; rm -rf tmp; exit $E;", "test:portman:create": "mkdir -p tmp && redocly bundle public/v1/spec.yaml > tmp/spec.yaml && portman --cliOptionsFile test/tests/contract/portman-cli.json", - "test:portman:run": "newman run tmp/converted/globalpingApi.json -e test/tests/contract/newman-env.json --ignore-redirects" + "test:portman:run": "newman run tmp/converted/globalpingApi.json -e test/tests/contract/newman-env.json --ignore-redirects", + "test:e2e": "start-test 'npm run start:test' http://localhost:3000/health 'npm run test:e2e:cases'", + "test:e2e:cases": "echo e2e_tests" }, "lint-staged": { "*.{cjs,js,json,ts}": "eslint --cache --fix" diff --git a/src/lib/get-probe-ip.ts b/src/lib/get-probe-ip.ts index 8668b768..e2fb6995 100644 --- a/src/lib/get-probe-ip.ts +++ b/src/lib/get-probe-ip.ts @@ -6,6 +6,10 @@ const getProbeIp = (socket: Socket) => { return '1.2.3.4'; } + if (process.env['TEST_MODE'] === 'e2e') { + return '51.158.22.211'; // Paris + } + // Use random ip assigned by the API if (process.env['FAKE_PROBE_IP']) { const samples = [ @@ -15,7 +19,6 @@ const getProbeIp = (socket: Socket) => { '95.155.94.127', '65.49.22.66', '185.229.226.83', - '51.158.22.211', '131.255.7.26', '94.214.253.78', '79.205.97.254', From 8f78c221b6db971d37cf0e313290323fc4676298 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Fri, 1 Mar 2024 18:11:27 +0100 Subject: [PATCH 03/43] feat: add mocha setup --- package.json | 10 +++++----- test-e2e/.mocharc.json | 42 ++++++++++++++++++++++++++++++++++++++++++ test-e2e/index.test.ts | 5 +++++ test-e2e/setup.ts | 3 +++ 4 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 test-e2e/.mocharc.json create mode 100644 test-e2e/index.test.ts create mode 100644 test-e2e/setup.ts diff --git a/package.json b/package.json index 4b2d7c49..2013075e 100644 --- a/package.json +++ b/package.json @@ -112,11 +112,11 @@ "scripts": { "build": "tsc && npm run download:files && npm run commit:hash && cp -r public dist", "commit:hash": "git rev-parse HEAD > data/LAST_API_COMMIT_HASH.txt", - "coverage": "npm run clean && NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false c8 mocha", + "coverage": "npm run clean && NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false c8 mocha", "clean": "rimraf coverage", "download:files": "tsx src/lib/download-files.ts", "prepare": "npm run download:files; husky install || echo 'Failed to install husky'", - "stats": "NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false tsx probes-stats/known.ts", + "stats": "NEW_RELIC_ENABLED=false tsx probes-stats/known.ts", "start": "NODE_ENV=production node --max_old_space_size=3584 --max-semi-space-size=128 --experimental-loader newrelic/esm-loader.mjs -r newrelic dist/src/index.js", "start:dev": "NODE_ENV=development FAKE_PROBE_IP=1 NEW_RELIC_ENABLED=false tsx src/index.ts", "start:test": "NODE_ENV=test knex migrate:latest && NODE_ENV=test TEST_MODE=e2e NEW_RELIC_ENABLED=false tsx src/index.ts", @@ -128,14 +128,14 @@ "lint:types": "tsc --noEmit", "test": "npm run lint && npm run test:mocha && npm run test:portman", "test:dist": "node --test-reporter=spec test/dist.js", - "test:mocha": "NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false mocha", - "test:mocha:dev": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false NEW_RELIC_LOG_ENABLED=false mocha", + "test:mocha": "NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false mocha", + "test:mocha:dev": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false mocha", "test:perf": "tsx test-perf/index.ts", "test:portman": "TEST_DONT_RESTART_WORKERS=1 start-test 'npm run start:test' http://localhost:3000/health 'npm run test:portman:create && npm run test:portman:run' 2> /dev/null || E=$?; rm -rf tmp; exit $E;", "test:portman:create": "mkdir -p tmp && redocly bundle public/v1/spec.yaml > tmp/spec.yaml && portman --cliOptionsFile test/tests/contract/portman-cli.json", "test:portman:run": "newman run tmp/converted/globalpingApi.json -e test/tests/contract/newman-env.json --ignore-redirects", "test:e2e": "start-test 'npm run start:test' http://localhost:3000/health 'npm run test:e2e:cases'", - "test:e2e:cases": "echo e2e_tests" + "test:e2e:cases": "NODE_ENV=test mocha --config test-e2e/.mocharc.json" }, "lint-staged": { "*.{cjs,js,json,ts}": "eslint --cache --fix" diff --git a/test-e2e/.mocharc.json b/test-e2e/.mocharc.json new file mode 100644 index 00000000..c44706e0 --- /dev/null +++ b/test-e2e/.mocharc.json @@ -0,0 +1,42 @@ +{ + "exit": true, + "timeout": 10000, + "check-leaks": true, + "file": [ + "test-e2e/setup.ts" + ], + "spec": [ + "test-e2e/**/*.test.ts" + ], + "node-option": [ + "experimental-specifier-resolution=node", + "loader=ts-node/esm", + "loader=testdouble" + ], + "globals": [ + "__extends", + "__assign", + "__rest", + "__decorate", + "__param", + "__metadata", + "__awaiter", + "__generator", + "__exportStar", + "__createBinding", + "__values", + "__read", + "__spread", + "__spreadArrays", + "__spreadArray", + "__await", + "__asyncGenerator", + "__asyncDelegator", + "__asyncValues", + "__makeTemplateObject", + "__importStar", + "__importDefault", + "__classPrivateFieldGet", + "__classPrivateFieldSet" + ] +} diff --git a/test-e2e/index.test.ts b/test-e2e/index.test.ts new file mode 100644 index 00000000..f99c5ea4 --- /dev/null +++ b/test-e2e/index.test.ts @@ -0,0 +1,5 @@ +describe('e2e tests', () => { + it('case 1', () => { + console.log('case 1 body'); + }); +}); diff --git a/test-e2e/setup.ts b/test-e2e/setup.ts new file mode 100644 index 00000000..0599f561 --- /dev/null +++ b/test-e2e/setup.ts @@ -0,0 +1,3 @@ +before(async () => { + console.log('run docker container'); +}); From aa5bc34b6a4a664b77c25f4a54d8c066277edcc3 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Mon, 4 Mar 2024 14:19:14 +0100 Subject: [PATCH 04/43] feat: run probe container from e2e setup --- package-lock.json | 149 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 + test-e2e/setup.ts | 37 +++++++++++- 3 files changed, 187 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 4a4b4a88..8ce91236 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,7 @@ "@types/chai": "^4.3.11", "@types/chai-subset": "^1.3.5", "@types/config": "^3.3.3", + "@types/dockerode": "^3.3.24", "@types/http-errors": "^2.0.4", "@types/koa": "^2.14.0", "@types/koa__router": "^12.0.4", @@ -92,6 +93,7 @@ "better-ajv-errors": "^1.2.0", "c8": "^9.0.0", "chai": "^4.4.0", + "dockerode": "^4.0.2", "eslint": "^8.56.0", "husky": "^8.0.3", "lint-staged": "^15.2.0", @@ -2348,6 +2350,12 @@ "node": ">=6.9.0" } }, + "node_modules/@balena/dockerignore": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", + "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", + "dev": true + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -5964,6 +5972,26 @@ "@types/node": "*" } }, + "node_modules/@types/docker-modem": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", + "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/ssh2": "*" + } + }, + "node_modules/@types/dockerode": { + "version": "3.3.24", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.24.tgz", + "integrity": "sha512-679y69OYusf7Fr2HtdjXPUF6hnHxSA9K4EsuagsMuPno/XpJHjXxCOy2I5YL8POnWbzjsQAi0pyKIYM9HSpQog==", + "dev": true, + "dependencies": { + "@types/docker-modem": "*", + "@types/node": "*" + } + }, "node_modules/@types/es-aggregate-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/es-aggregate-error/-/es-aggregate-error-1.0.3.tgz", @@ -6312,6 +6340,15 @@ "integrity": "sha512-4g+2YyWe0Ve+LBh+WUm1697PD0Kdi6coG1eU0YjQbwx61AZ8XbEpL1zIT6WjuUKrCMCROpEaYQPDjBnDouBVAQ==", "dev": true }, + "node_modules/@types/ssh2": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.11.19.tgz", + "integrity": "sha512-ydbQAqEcdNVy2t1w7dMh6eWMr+iOgtEkqM/3K9RMijMaok/ER7L8GW6PwsOypHCN++M+c8S/UR9SgMqNIFstbA==", + "dev": true, + "dependencies": { + "@types/node": "^18.11.18" + } + }, "node_modules/@types/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", @@ -7917,6 +7954,16 @@ "node": ">=6.14.2" } }, + "node_modules/buildcheck": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", + "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/builtin-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-2.0.0.tgz", @@ -8269,6 +8316,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -8832,6 +8885,21 @@ "resolved": "https://registry.npmjs.org/countries-list/-/countries-list-3.0.6.tgz", "integrity": "sha512-BCJODHTSRMIxS0W80NZw8bC7x6/WS8Tf4FdtFrEbW0FONBbqTAzOrKNG06UEMgLGxOZpOJddiVEdCztKLpmPxA==" }, + "node_modules/cpu-features": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.9.tgz", + "integrity": "sha512-AKjgn2rP2yJyfbepsmLfiYcmtNn/2eUvocUyM/09yB0YDiz39HteK/5/T4Onf0pmdYDMgkBoGvRLvEguzyL7wQ==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.17.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -9524,6 +9592,35 @@ "node": ">=8" } }, + "node_modules/docker-modem": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.3.tgz", + "integrity": "sha512-89zhop5YVhcPEt5FpUFGr3cDyceGhq/F9J+ZndQ4KfqNvfbJpPMfgeixFgUj5OjCYAboElqODxY5Z1EBsSa6sg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "readable-stream": "^3.5.0", + "split-ca": "^1.0.1", + "ssh2": "^1.15.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/dockerode": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.2.tgz", + "integrity": "sha512-9wM1BVpVMFr2Pw3eJNXrYYt6DT9k0xMcsSCjtPvyQ+xa1iPg/Mo3T/gUcwI0B2cczqCeCYRPF8yFYDwtFXT0+w==", + "dev": true, + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "docker-modem": "^5.0.3", + "tar-fs": "~2.0.1" + }, + "engines": { + "node": ">= 8.0" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -14466,6 +14563,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, "node_modules/mmdb-lib": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-2.1.0.tgz", @@ -17920,6 +18023,12 @@ "node": "*" } }, + "node_modules/split-ca": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", + "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", + "dev": true + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -17947,6 +18056,24 @@ "aws-sdk": "^2.1271.0" } }, + "node_modules/ssh2": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.15.0.tgz", + "integrity": "sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.9", + "nan": "^2.18.0" + } + }, "node_modules/sshpk": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", @@ -18657,6 +18784,28 @@ "node": ">=6" } }, + "node_modules/tar-fs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", + "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "node_modules/tar-fs/node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", diff --git a/package.json b/package.json index 2013075e..5d8ae057 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@types/chai": "^4.3.11", "@types/chai-subset": "^1.3.5", "@types/config": "^3.3.3", + "@types/dockerode": "^3.3.24", "@types/http-errors": "^2.0.4", "@types/koa": "^2.14.0", "@types/koa__router": "^12.0.4", @@ -89,6 +90,7 @@ "better-ajv-errors": "^1.2.0", "c8": "^9.0.0", "chai": "^4.4.0", + "dockerode": "^4.0.2", "eslint": "^8.56.0", "husky": "^8.0.3", "lint-staged": "^15.2.0", diff --git a/test-e2e/setup.ts b/test-e2e/setup.ts index 0599f561..8bbba4cb 100644 --- a/test-e2e/setup.ts +++ b/test-e2e/setup.ts @@ -1,3 +1,38 @@ +import Docker from 'dockerode'; +import { setTimeout } from 'timers/promises'; + +let container: Docker.Container; + before(async () => { - console.log('run docker container'); + const docker = new Docker(); + + container = await docker.createContainer({ + Image: 'ghcr.io/jsdelivr/globalping-probe', + name: 'globalping-probe-e2e', + Env: [ + 'API_HOST=ws://host.docker.internal:3000', + ], + HostConfig: { + LogConfig: { + Type: 'local', + Config: {}, + }, + NetworkMode: 'host', + }, + }); + + await container.start({}); + + const stream = await container.logs({ + follow: true, + stdout: true, + stderr: true, + }); + container.modem.demuxStream(stream, process.stdout, process.stderr); + + await setTimeout(5000); +}); + +after(async () => { + await container.remove({ force: true }); }); From 877f95b58021042fd44f5cfe0b7211f5e0e3d53d Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Mon, 4 Mar 2024 14:20:52 +0100 Subject: [PATCH 05/43] ci: trigger e2e --- .github/workflows/ci.yml | 6 +++++- test-e2e/setup.ts | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0c5e4b7..392d67c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,11 @@ jobs: run: | npm ci npm run build - - name: Test + - name: Test e2e + run: | + docker pull ghcr.io/jsdelivr/globalping-probe + npm run test:e2e + - name: Test unit run: | npm run lint npm run coverage diff --git a/test-e2e/setup.ts b/test-e2e/setup.ts index 8bbba4cb..2b6d4270 100644 --- a/test-e2e/setup.ts +++ b/test-e2e/setup.ts @@ -6,11 +6,17 @@ let container: Docker.Container; before(async () => { const docker = new Docker(); + const versionInfo = await docker.version(); + console.log('versionInfo', versionInfo); + const platformName = versionInfo.Platform.Name.toLowerCase(); + console.log('platformName', platformName); + const isLinuxHost = platformName.includes('engine'); + container = await docker.createContainer({ Image: 'ghcr.io/jsdelivr/globalping-probe', name: 'globalping-probe-e2e', Env: [ - 'API_HOST=ws://host.docker.internal:3000', + `API_HOST=ws://${isLinuxHost ? 'localhost' : 'host.docker.internal'}:3000`, // https://github.com/moby/moby/pull/40007 ], HostConfig: { LogConfig: { From bf434dc698288ab863bb1e9562981ce1878fb094 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Mon, 4 Mar 2024 17:25:55 +0100 Subject: [PATCH 06/43] feat: wait until probe connects --- test-e2e/setup.ts | 48 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/test-e2e/setup.ts b/test-e2e/setup.ts index 2b6d4270..3f8e2144 100644 --- a/test-e2e/setup.ts +++ b/test-e2e/setup.ts @@ -1,22 +1,49 @@ import Docker from 'dockerode'; +import got from 'got'; import { setTimeout } from 'timers/promises'; -let container: Docker.Container; +import type { Probe } from '../src/probe/types.js'; -before(async () => { - const docker = new Docker(); +let container: Docker.Container; +const isLinuxHost = async (docker: Docker) => { const versionInfo = await docker.version(); - console.log('versionInfo', versionInfo); const platformName = versionInfo.Platform.Name.toLowerCase(); - console.log('platformName', platformName); const isLinuxHost = platformName.includes('engine'); + return isLinuxHost; +}; + +const attachLogs = async (container: Docker.Container) => { + const stream = await container.logs({ + follow: true, + stdout: true, + stderr: true, + }); + container.modem.demuxStream(stream, process.stdout, process.stderr); +}; + +const waitForProbeToConnect = async () => { + for (;;) { + const probes = await got('http://localhost:3000/v1/probes').json(); + + if (probes.length > 0) { + return; + } + + await setTimeout(500); + } +}; + +before(async () => { + const docker = new Docker(); + + const isLinux = await isLinuxHost(docker); container = await docker.createContainer({ Image: 'ghcr.io/jsdelivr/globalping-probe', name: 'globalping-probe-e2e', Env: [ - `API_HOST=ws://${isLinuxHost ? 'localhost' : 'host.docker.internal'}:3000`, // https://github.com/moby/moby/pull/40007 + `API_HOST=ws://${isLinux ? 'localhost' : 'host.docker.internal'}:3000`, // https://github.com/moby/moby/pull/40007 ], HostConfig: { LogConfig: { @@ -29,14 +56,9 @@ before(async () => { await container.start({}); - const stream = await container.logs({ - follow: true, - stdout: true, - stderr: true, - }); - container.modem.demuxStream(stream, process.stdout, process.stderr); + await attachLogs(container); - await setTimeout(5000); + await waitForProbeToConnect(); }); after(async () => { From 6c695756dbdf9a52d106053e0ef42e1dfa96efdd Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Mon, 4 Mar 2024 18:08:49 +0100 Subject: [PATCH 07/43] feat: add failing usage of matchApiSchema --- .github/workflows/ci.yml | 4 ++-- test-e2e/.mocharc.json | 2 +- test-e2e/index.test.ts | 5 ----- test-e2e/probes.test.ts | 25 +++++++++++++++++++++++++ test-e2e/setup.ts | 14 ++++++++++++-- 5 files changed, 40 insertions(+), 10 deletions(-) delete mode 100644 test-e2e/index.test.ts create mode 100644 test-e2e/probes.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 392d67c4..575b9226 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,11 +48,11 @@ jobs: run: | npm ci npm run build - - name: Test e2e + - name: Test E2E run: | docker pull ghcr.io/jsdelivr/globalping-probe npm run test:e2e - - name: Test unit + - name: Test Unit run: | npm run lint npm run coverage diff --git a/test-e2e/.mocharc.json b/test-e2e/.mocharc.json index c44706e0..712e9c6e 100644 --- a/test-e2e/.mocharc.json +++ b/test-e2e/.mocharc.json @@ -1,6 +1,6 @@ { "exit": true, - "timeout": 10000, + "timeout": 20000, "check-leaks": true, "file": [ "test-e2e/setup.ts" diff --git a/test-e2e/index.test.ts b/test-e2e/index.test.ts deleted file mode 100644 index f99c5ea4..00000000 --- a/test-e2e/index.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -describe('e2e tests', () => { - it('case 1', () => { - console.log('case 1 body'); - }); -}); diff --git a/test-e2e/probes.test.ts b/test-e2e/probes.test.ts new file mode 100644 index 00000000..42918590 --- /dev/null +++ b/test-e2e/probes.test.ts @@ -0,0 +1,25 @@ +import got from 'got'; +import { expect } from 'chai'; + +import type { Probe } from '../src/probe/types.js'; + +describe('/probes endpoint', () => { + it('should return an array of probes', async () => { + const probes = await got('http://127.0.0.1:3000/v1/probes').json(); + // const response = { + // req: { + // path: '/v1/probes', + // method: 'GET', + // }, + // statusCode: 200, + // type: 'OK', + // headers: { + // 'content-type': 'application/json', + // }, + // body: probes, + // }; + + expect(probes.length).to.equal(1); + // expect(response).to.matchApiSchema(); + }); +}); diff --git a/test-e2e/setup.ts b/test-e2e/setup.ts index 3f8e2144..befc6e29 100644 --- a/test-e2e/setup.ts +++ b/test-e2e/setup.ts @@ -1,7 +1,11 @@ import Docker from 'dockerode'; import got from 'got'; import { setTimeout } from 'timers/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import chai from 'chai'; +import chaiOas from '../test/plugins/oas/index.js'; import type { Probe } from '../src/probe/types.js'; let container: Docker.Container; @@ -24,17 +28,23 @@ const attachLogs = async (container: Docker.Container) => { const waitForProbeToConnect = async () => { for (;;) { - const probes = await got('http://localhost:3000/v1/probes').json(); + const response = await got('http://localhost:3000/v1/probes'); + console.log('response.statusCode', response.statusCode); + console.log('response.body', response.body); + const probes = JSON.parse(response.body) as Probe[]; + console.log('probes', probes); if (probes.length > 0) { return; } - await setTimeout(500); + await setTimeout(1000); } }; before(async () => { + chai.use(await chaiOas({ specPath: path.join(fileURLToPath(new URL('.', import.meta.url)), '../public/v1/spec.yaml') })); + const docker = new Docker(); const isLinux = await isLinuxHost(docker); From fd86edd9f5fc1d852714fd573659287252c4a8c8 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Tue, 5 Mar 2024 15:10:53 +0100 Subject: [PATCH 08/43] fix: matchApiSchema method --- test-e2e/probes.test.ts | 23 ++++++----------------- test-e2e/setup.ts | 3 --- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/test-e2e/probes.test.ts b/test-e2e/probes.test.ts index 42918590..1c904c77 100644 --- a/test-e2e/probes.test.ts +++ b/test-e2e/probes.test.ts @@ -1,25 +1,14 @@ import got from 'got'; import { expect } from 'chai'; -import type { Probe } from '../src/probe/types.js'; - describe('/probes endpoint', () => { it('should return an array of probes', async () => { - const probes = await got('http://127.0.0.1:3000/v1/probes').json(); - // const response = { - // req: { - // path: '/v1/probes', - // method: 'GET', - // }, - // statusCode: 200, - // type: 'OK', - // headers: { - // 'content-type': 'application/json', - // }, - // body: probes, - // }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const response = await got('http://127.0.0.1:3000/v1/probes') as any; + response.body = JSON.parse(response.body); + response.type = response.headers['content-type'] === 'application/json; charset=utf-8' ? 'application/json' : response.headers['content-type']; - expect(probes.length).to.equal(1); - // expect(response).to.matchApiSchema(); + expect(response.body.length).to.equal(1); + expect(response).to.matchApiSchema(); }); }); diff --git a/test-e2e/setup.ts b/test-e2e/setup.ts index befc6e29..75f0d4d5 100644 --- a/test-e2e/setup.ts +++ b/test-e2e/setup.ts @@ -29,10 +29,7 @@ const attachLogs = async (container: Docker.Container) => { const waitForProbeToConnect = async () => { for (;;) { const response = await got('http://localhost:3000/v1/probes'); - console.log('response.statusCode', response.statusCode); - console.log('response.body', response.body); const probes = JSON.parse(response.body) as Probe[]; - console.log('probes', probes); if (probes.length > 0) { return; From 1a05f0858798c9509e556c0ad628888b7c4b97fb Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Tue, 5 Mar 2024 16:30:16 +0100 Subject: [PATCH 09/43] fix: mocha throwing ERROR: null in case of ts compilation error --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5d8ae057..748769b8 100644 --- a/package.json +++ b/package.json @@ -130,14 +130,14 @@ "lint:types": "tsc --noEmit", "test": "npm run lint && npm run test:mocha && npm run test:portman", "test:dist": "node --test-reporter=spec test/dist.js", - "test:mocha": "NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false mocha", + "test:mocha": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false mocha", "test:mocha:dev": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false mocha", "test:perf": "tsx test-perf/index.ts", "test:portman": "TEST_DONT_RESTART_WORKERS=1 start-test 'npm run start:test' http://localhost:3000/health 'npm run test:portman:create && npm run test:portman:run' 2> /dev/null || E=$?; rm -rf tmp; exit $E;", "test:portman:create": "mkdir -p tmp && redocly bundle public/v1/spec.yaml > tmp/spec.yaml && portman --cliOptionsFile test/tests/contract/portman-cli.json", "test:portman:run": "newman run tmp/converted/globalpingApi.json -e test/tests/contract/newman-env.json --ignore-redirects", "test:e2e": "start-test 'npm run start:test' http://localhost:3000/health 'npm run test:e2e:cases'", - "test:e2e:cases": "NODE_ENV=test mocha --config test-e2e/.mocharc.json" + "test:e2e:cases": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha --config test-e2e/.mocharc.json" }, "lint-staged": { "*.{cjs,js,json,ts}": "eslint --cache --fix" From 9a1e134fc96ddf667b8762f28ecc3557249ba4db Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Tue, 5 Mar 2024 16:48:39 +0100 Subject: [PATCH 10/43] feat: add util parser --- test-e2e/probes.test.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/test-e2e/probes.test.ts b/test-e2e/probes.test.ts index 1c904c77..b7acd866 100644 --- a/test-e2e/probes.test.ts +++ b/test-e2e/probes.test.ts @@ -1,13 +1,20 @@ import got from 'got'; import { expect } from 'chai'; +import supertest from 'supertest'; + +const gotResponseToSupertestResponse = (gotResponse) => { + const supertestResponse = { ...gotResponse } as unknown as supertest.Response; + supertestResponse.body = JSON.parse(gotResponse.body); + supertestResponse.type = gotResponse.headers['content-type'] === 'application/json; charset=utf-8' ? 'application/json' : gotResponse.headers['content-type']; + return supertestResponse; +}; describe('/probes endpoint', () => { it('should return an array of probes', async () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const response = await got('http://127.0.0.1:3000/v1/probes') as any; - response.body = JSON.parse(response.body); - response.type = response.headers['content-type'] === 'application/json; charset=utf-8' ? 'application/json' : response.headers['content-type']; + const gotResponse = await got('http://localhost:3000/v1/probes'); + const response = gotResponseToSupertestResponse(gotResponse); + expect(response.statusCode).to.equal(200); expect(response.body.length).to.equal(1); expect(response).to.matchApiSchema(); }); From 7dda7a4dc97e080b5735f59e6ace09c817456b3c Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Tue, 5 Mar 2024 16:53:45 +0100 Subject: [PATCH 11/43] feat: change runner --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 575b9226..498635c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: self-hosted env: NODE_ENV: test From c8f8ba241c221015eb0388fc7595bce7596c0ae1 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Tue, 5 Mar 2024 17:21:21 +0100 Subject: [PATCH 12/43] feat: use got response in mocha plugin --- test-e2e/probes.test.ts | 14 +++----------- test/plugins/oas/index.js | 9 +++++++++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/test-e2e/probes.test.ts b/test-e2e/probes.test.ts index b7acd866..88bee0a6 100644 --- a/test-e2e/probes.test.ts +++ b/test-e2e/probes.test.ts @@ -1,21 +1,13 @@ import got from 'got'; import { expect } from 'chai'; -import supertest from 'supertest'; - -const gotResponseToSupertestResponse = (gotResponse) => { - const supertestResponse = { ...gotResponse } as unknown as supertest.Response; - supertestResponse.body = JSON.parse(gotResponse.body); - supertestResponse.type = gotResponse.headers['content-type'] === 'application/json; charset=utf-8' ? 'application/json' : gotResponse.headers['content-type']; - return supertestResponse; -}; describe('/probes endpoint', () => { it('should return an array of probes', async () => { - const gotResponse = await got('http://localhost:3000/v1/probes'); - const response = gotResponseToSupertestResponse(gotResponse); + const response = await got('http://localhost:3000/v1/probes'); + const probes = JSON.parse(response.body); expect(response.statusCode).to.equal(200); - expect(response.body.length).to.equal(1); + expect(probes.length).to.equal(1); expect(response).to.matchApiSchema(); }); }); diff --git a/test/plugins/oas/index.js b/test/plugins/oas/index.js index 0de385bc..56085f25 100644 --- a/test/plugins/oas/index.js +++ b/test/plugins/oas/index.js @@ -63,6 +63,15 @@ export default async ({ specPath, ajvBodyOptions = {}, ajvHeadersOptions = {} }) chai.Assertion.addMethod('matchApiSchema', function () { let response = this._obj; + + if (typeof response.body === 'string') { + response.body = JSON.parse(response.body); + } + + if (!response.type) { + response.type = response.headers['content-type'] === 'application/json; charset=utf-8' ? 'application/json' : response.headers['content-type']; + } + let reqPath = new URL(response.req.path, 'http://localhost').pathname; let reqMethod = response.req.method.toLowerCase(); From 3655709904bead30d1061f521030d3ca6f1e1046 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Tue, 5 Mar 2024 17:33:14 +0100 Subject: [PATCH 13/43] test: add measurement e2e tests --- test-e2e/measurement.test.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 test-e2e/measurement.test.ts diff --git a/test-e2e/measurement.test.ts b/test-e2e/measurement.test.ts new file mode 100644 index 00000000..5ec84a92 --- /dev/null +++ b/test-e2e/measurement.test.ts @@ -0,0 +1,27 @@ +import got from 'got'; +import { expect } from 'chai'; + +describe('/measurements endpoint', () => { + it('should create measurement', async () => { + const response = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'jsdelivr.com', + type: 'ping', + } }); + + expect(response.statusCode).to.equal(202); + expect(response).to.matchApiSchema(); + }); + + it('should return measurement result', async () => { + const postResponse = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'jsdelivr.com', + type: 'ping', + } }); + + const { id } = JSON.parse(postResponse.body); + const getResponse = await got(`http://localhost:3000/v1/measurements/${id}`); + + expect(getResponse.statusCode).to.equal(200); + expect(getResponse).to.matchApiSchema(); + }); +}); From b5bb6b2569c5182351b3fb157694f623b28c81cb Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Wed, 6 Mar 2024 13:01:53 +0100 Subject: [PATCH 14/43] feat: add e2e wallaby configuration --- test-e2e/docker.ts | 57 ++++++++++++++++++++++++++++++++++++++++++++++ test-e2e/setup.ts | 57 +++++++++++----------------------------------- wallaby.e2e.js | 31 +++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 44 deletions(-) create mode 100644 test-e2e/docker.ts create mode 100644 wallaby.e2e.js diff --git a/test-e2e/docker.ts b/test-e2e/docker.ts new file mode 100644 index 00000000..0e45b553 --- /dev/null +++ b/test-e2e/docker.ts @@ -0,0 +1,57 @@ +import Docker from 'dockerode'; + +const isLinuxHost = async (docker: Docker) => { + const versionInfo = await docker.version(); + const platformName = versionInfo.Platform.Name.toLowerCase(); + const isLinuxHost = platformName.includes('engine'); + return isLinuxHost; +}; + +const attachLogs = async (container: Docker.Container) => { + const stream = await container.logs({ + follow: true, + stdout: true, + stderr: true, + }); + container.modem.demuxStream(stream, process.stdout, process.stderr); +}; + +export const startProbeContainer = async () => { + const docker = new Docker(); + + const isLinux = await isLinuxHost(docker); + + const container = await docker.createContainer({ + Image: 'ghcr.io/jsdelivr/globalping-probe', + name: 'globalping-probe-e2e', + Env: [ + `API_HOST=ws://${isLinux ? 'localhost' : 'host.docker.internal'}:3000`, // https://github.com/moby/moby/pull/40007 + ], + HostConfig: { + LogConfig: { + Type: 'local', + Config: {}, + }, + NetworkMode: 'host', + }, + }); + + await container.start({}); + + await attachLogs(container); +}; + +export const removeProbeContainer = async () => { + const docker = new Docker(); + const containers = await docker.listContainers({ all: true }); + const containerInfo = containers.find(c => c.Names.includes('/globalping-probe-e2e')); + + if (!containerInfo) { + console.log('Container not found:'); + return; + } + + const container = docker.getContainer(containerInfo.Id); + + await container.remove({ force: true }); +}; diff --git a/test-e2e/setup.ts b/test-e2e/setup.ts index 75f0d4d5..df6c2b67 100644 --- a/test-e2e/setup.ts +++ b/test-e2e/setup.ts @@ -1,4 +1,3 @@ -import Docker from 'dockerode'; import got from 'got'; import { setTimeout } from 'timers/promises'; import path from 'node:path'; @@ -7,28 +6,19 @@ import chai from 'chai'; import chaiOas from '../test/plugins/oas/index.js'; import type { Probe } from '../src/probe/types.js'; +import { removeProbeContainer, startProbeContainer } from './docker.js'; -let container: Docker.Container; +const waitProbeToConnect = async () => { + let response; -const isLinuxHost = async (docker: Docker) => { - const versionInfo = await docker.version(); - const platformName = versionInfo.Platform.Name.toLowerCase(); - const isLinuxHost = platformName.includes('engine'); - return isLinuxHost; -}; - -const attachLogs = async (container: Docker.Container) => { - const stream = await container.logs({ - follow: true, - stdout: true, - stderr: true, - }); - container.modem.demuxStream(stream, process.stdout, process.stderr); -}; - -const waitForProbeToConnect = async () => { for (;;) { - const response = await got('http://localhost:3000/v1/probes'); + try { + response = await got('http://localhost:3000/v1/probes'); + } catch (err) { + console.log(err.code); + throw err; + } + const probes = JSON.parse(response.body) as Probe[]; if (probes.length > 0) { @@ -42,32 +32,11 @@ const waitForProbeToConnect = async () => { before(async () => { chai.use(await chaiOas({ specPath: path.join(fileURLToPath(new URL('.', import.meta.url)), '../public/v1/spec.yaml') })); - const docker = new Docker(); - - const isLinux = await isLinuxHost(docker); - - container = await docker.createContainer({ - Image: 'ghcr.io/jsdelivr/globalping-probe', - name: 'globalping-probe-e2e', - Env: [ - `API_HOST=ws://${isLinux ? 'localhost' : 'host.docker.internal'}:3000`, // https://github.com/moby/moby/pull/40007 - ], - HostConfig: { - LogConfig: { - Type: 'local', - Config: {}, - }, - NetworkMode: 'host', - }, - }); - - await container.start({}); - - await attachLogs(container); + await startProbeContainer(); - await waitForProbeToConnect(); + await waitProbeToConnect(); }); after(async () => { - await container.remove({ force: true }); + await removeProbeContainer(); }); diff --git a/wallaby.e2e.js b/wallaby.e2e.js new file mode 100644 index 00000000..5ccba273 --- /dev/null +++ b/wallaby.e2e.js @@ -0,0 +1,31 @@ +export default function wallaby () { + return { + testFramework: 'mocha', + files: [ + 'public/v1/*', + 'public/**/*.yaml', + 'test/plugins/**/*', + 'test-e2e/setup.ts', + 'test-e2e/docker.ts', + 'package.json', + ], + tests: [ + 'test-e2e/**/*.test.ts', + ], + + setup (w) { + const path = require('path'); + w.testFramework.addFile(path.resolve(process.cwd(), 'test-e2e/setup.js')); + w.testFramework.timeout(20000); + }, + + env: { + type: 'node', + params: { + env: 'NODE_ENV=test', + }, + }, + workers: { restart: true, initial: 1, regular: 1 }, + runMode: 'onsave', + }; +} From d8559f66a995cbbce7f5be67b8d77ece72de481b Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Wed, 6 Mar 2024 13:05:02 +0100 Subject: [PATCH 15/43] feat: move e2e .mocharc --- test-e2e/.mocharc.json => .mocharc.e2e.json | 0 package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename test-e2e/.mocharc.json => .mocharc.e2e.json (100%) diff --git a/test-e2e/.mocharc.json b/.mocharc.e2e.json similarity index 100% rename from test-e2e/.mocharc.json rename to .mocharc.e2e.json diff --git a/package.json b/package.json index 748769b8..97602a65 100644 --- a/package.json +++ b/package.json @@ -137,7 +137,7 @@ "test:portman:create": "mkdir -p tmp && redocly bundle public/v1/spec.yaml > tmp/spec.yaml && portman --cliOptionsFile test/tests/contract/portman-cli.json", "test:portman:run": "newman run tmp/converted/globalpingApi.json -e test/tests/contract/newman-env.json --ignore-redirects", "test:e2e": "start-test 'npm run start:test' http://localhost:3000/health 'npm run test:e2e:cases'", - "test:e2e:cases": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha --config test-e2e/.mocharc.json" + "test:e2e:cases": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha --config .mocharc.e2e.json" }, "lint-staged": { "*.{cjs,js,json,ts}": "eslint --cache --fix" From 1889319bb700bdf992cfa7c0497944b730ca43ec Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Wed, 6 Mar 2024 13:26:55 +0100 Subject: [PATCH 16/43] feat: add e2e folder to tsconfig --- tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 96994325..5b8d4e00 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,8 @@ }, "include": [ "src/**/*", - "test/**/*.ts" + "test/**/*.ts", + "test-e2e/**/*.ts" ], "exclude": [ "dist", From eadb8d2f294abcd5597a2a8348889c87098a999e Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Wed, 6 Mar 2024 14:47:40 +0100 Subject: [PATCH 17/43] feat: add e2e cases for all measurement types --- test-e2e/adoption-code.test.ts | 26 ++++++++++++++++++++++++ test-e2e/dns.test.ts | 37 ++++++++++++++++++++++++++++++++++ test-e2e/http.test.ts | 18 +++++++++++++++++ test-e2e/measurement.test.ts | 12 ++++++----- test-e2e/mtr.test.ts | 18 +++++++++++++++++ test-e2e/ping.test.ts | 18 +++++++++++++++++ test-e2e/setup.ts | 2 ++ test-e2e/traceroute.test.ts | 18 +++++++++++++++++ test-e2e/utils.ts | 15 ++++++++++++++ wallaby.e2e.js | 1 + 10 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 test-e2e/adoption-code.test.ts create mode 100644 test-e2e/dns.test.ts create mode 100644 test-e2e/http.test.ts create mode 100644 test-e2e/mtr.test.ts create mode 100644 test-e2e/ping.test.ts create mode 100644 test-e2e/traceroute.test.ts create mode 100644 test-e2e/utils.ts diff --git a/test-e2e/adoption-code.test.ts b/test-e2e/adoption-code.test.ts new file mode 100644 index 00000000..1917d6e7 --- /dev/null +++ b/test-e2e/adoption-code.test.ts @@ -0,0 +1,26 @@ +import got from 'got'; +import { expect } from 'chai'; + +describe('/adoption-code endpoint', () => { + it('should send code to the probe', async () => { + const response = await got.post('http://localhost:3000/v1/adoption-code?systemkey=system', { json: { + ip: '51.158.22.211', + code: '123456', + } }); + const body = JSON.parse(response.body); + + expect(response.statusCode).to.equal(200); + + expect(body).to.deep.include({ + asn: 12876, + city: 'Paris', + country: 'FR', + hardwareDevice: null, + latitude: 48.8534, + longitude: 2.3488, + network: 'SCALEWAY S.A.S.', + state: null, + status: 'ready', + }); + }); +}); diff --git a/test-e2e/dns.test.ts b/test-e2e/dns.test.ts new file mode 100644 index 00000000..4aa7ec07 --- /dev/null +++ b/test-e2e/dns.test.ts @@ -0,0 +1,37 @@ +/* eslint-disable no-unused-expressions */ +import got from 'got'; +import { expect } from 'chai'; +import { waitMesurementFinish } from './utils.js'; + +describe('dns mesurement', () => { + it('should finish successfully', async () => { + const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'jsdelivr.com', + type: 'dns', + } }).json(); + + const { response, body } = await waitMesurementFinish(id); + + expect(body.status).to.equal('finished'); + expect(body.results[0].result.status).to.equal('finished'); + expect(body.results[0].result.hops).to.not.exist; + expect(response).to.matchApiSchema(); + }); + + it('should finish successfully in case of "traced": true', async () => { + const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'jsdelivr.com', + type: 'dns', + measurementOptions: { + trace: true, + }, + } }).json(); + + const { response, body } = await waitMesurementFinish(id); + + expect(body.status).to.equal('finished'); + expect(body.results[0].result.status).to.equal('finished'); + expect(body.results[0].result.hops.length > 0).to.be.true; + expect(response).to.matchApiSchema(); + }); +}); diff --git a/test-e2e/http.test.ts b/test-e2e/http.test.ts new file mode 100644 index 00000000..feb59595 --- /dev/null +++ b/test-e2e/http.test.ts @@ -0,0 +1,18 @@ +import got from 'got'; +import { expect } from 'chai'; +import { waitMesurementFinish } from './utils.js'; + +describe('http mesurement', () => { + it('should finish successfully', async () => { + const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'jsdelivr.com', + type: 'http', + } }).json(); + + const { response, body } = await waitMesurementFinish(id); + + expect(body.status).to.equal('finished'); + expect(body.results[0].result.status).to.equal('finished'); + expect(response).to.matchApiSchema(); + }); +}); diff --git a/test-e2e/measurement.test.ts b/test-e2e/measurement.test.ts index 5ec84a92..095aafda 100644 --- a/test-e2e/measurement.test.ts +++ b/test-e2e/measurement.test.ts @@ -13,15 +13,17 @@ describe('/measurements endpoint', () => { }); it('should return measurement result', async () => { - const postResponse = await got.post('http://localhost:3000/v1/measurements', { json: { + const responseOfCreate = await got.post('http://localhost:3000/v1/measurements', { json: { target: 'jsdelivr.com', type: 'ping', } }); - const { id } = JSON.parse(postResponse.body); - const getResponse = await got(`http://localhost:3000/v1/measurements/${id}`); + const { id } = JSON.parse(responseOfCreate.body); + const responseOfRead = await got(`http://localhost:3000/v1/measurements/${id}`); + const body = JSON.parse(responseOfRead.body); - expect(getResponse.statusCode).to.equal(200); - expect(getResponse).to.matchApiSchema(); + expect(body.probesCount).to.equal(1); + expect(responseOfRead.statusCode).to.equal(200); + expect(responseOfRead).to.matchApiSchema(); }); }); diff --git a/test-e2e/mtr.test.ts b/test-e2e/mtr.test.ts new file mode 100644 index 00000000..4059f295 --- /dev/null +++ b/test-e2e/mtr.test.ts @@ -0,0 +1,18 @@ +import got from 'got'; +import { expect } from 'chai'; +import { waitMesurementFinish } from './utils.js'; + +describe('mtr mesurement', () => { + it('should finish successfully', async () => { + const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'jsdelivr.com', + type: 'mtr', + } }).json(); + + const { response, body } = await waitMesurementFinish(id); + + expect(body.status).to.equal('finished'); + expect(body.results[0].result.status).to.equal('finished'); + expect(response).to.matchApiSchema(); + }); +}); diff --git a/test-e2e/ping.test.ts b/test-e2e/ping.test.ts new file mode 100644 index 00000000..e42c7889 --- /dev/null +++ b/test-e2e/ping.test.ts @@ -0,0 +1,18 @@ +import got from 'got'; +import { expect } from 'chai'; +import { waitMesurementFinish } from './utils.js'; + +describe('ping mesurement', () => { + it('should finish successfully', async () => { + const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'jsdelivr.com', + type: 'ping', + } }).json(); + + const { response, body } = await waitMesurementFinish(id); + + expect(body.status).to.equal('finished'); + expect(body.results[0].result.status).to.equal('finished'); + expect(response).to.matchApiSchema(); + }); +}); diff --git a/test-e2e/setup.ts b/test-e2e/setup.ts index df6c2b67..8b82eeee 100644 --- a/test-e2e/setup.ts +++ b/test-e2e/setup.ts @@ -32,6 +32,8 @@ const waitProbeToConnect = async () => { before(async () => { chai.use(await chaiOas({ specPath: path.join(fileURLToPath(new URL('.', import.meta.url)), '../public/v1/spec.yaml') })); + await removeProbeContainer(); + await startProbeContainer(); await waitProbeToConnect(); diff --git a/test-e2e/traceroute.test.ts b/test-e2e/traceroute.test.ts new file mode 100644 index 00000000..2c41df4c --- /dev/null +++ b/test-e2e/traceroute.test.ts @@ -0,0 +1,18 @@ +import got from 'got'; +import { expect } from 'chai'; +import { waitMesurementFinish } from './utils.js'; + +describe('traceroute mesurement', () => { + it('should finish successfully', async () => { + const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'jsdelivr.com', + type: 'traceroute', + } }).json(); + + const { response, body } = await waitMesurementFinish(id); + + expect(body.status).to.equal('finished'); + expect(body.results[0].result.status).to.equal('finished'); + expect(response).to.matchApiSchema(); + }); +}); diff --git a/test-e2e/utils.ts b/test-e2e/utils.ts new file mode 100644 index 00000000..6a10f246 --- /dev/null +++ b/test-e2e/utils.ts @@ -0,0 +1,15 @@ +import { setTimeout } from 'timers/promises'; +import got from 'got'; + +export const waitMesurementFinish = async (id: string) => { + for (;;) { + const response = await got(`http://localhost:3000/v1/measurements/${id}`); + const body = JSON.parse(response.body); + + if (body.status !== 'in-progress') { + return { response, body }; + } + + await setTimeout(500); + } +}; diff --git a/wallaby.e2e.js b/wallaby.e2e.js index 5ccba273..aa3a0224 100644 --- a/wallaby.e2e.js +++ b/wallaby.e2e.js @@ -6,6 +6,7 @@ export default function wallaby () { 'public/**/*.yaml', 'test/plugins/**/*', 'test-e2e/setup.ts', + 'test-e2e/utils.ts', 'test-e2e/docker.ts', 'package.json', ], From e6623d30119f66feee109483162300e214a41190 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Wed, 6 Mar 2024 16:52:51 +0100 Subject: [PATCH 18/43] fix: http schema --- .eslintrc | 6 ++++-- public/v1/components/schemas.yaml | 6 ++++-- test-e2e/dns.test.ts | 5 ++--- test-e2e/http.test.ts | 22 +++++++++++++++++++++- test-e2e/measurement.test.ts | 4 ++-- test-e2e/mtr.test.ts | 2 +- test-e2e/ping.test.ts | 2 +- test-e2e/setup.ts | 4 ++-- test-e2e/traceroute.test.ts | 2 +- 9 files changed, 38 insertions(+), 15 deletions(-) diff --git a/.eslintrc b/.eslintrc index 5bee3300..4e705b59 100644 --- a/.eslintrc +++ b/.eslintrc @@ -39,11 +39,13 @@ }, { "files": [ - "test/**" + "test/**", + "test-e2e/**" ], "rules": { "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-non-null-assertion": "off" + "@typescript-eslint/no-non-null-assertion": "off", + "no-unused-expressions": "off" } } ] diff --git a/public/v1/components/schemas.yaml b/public/v1/components/schemas.yaml index a38d6f09..3491d7f7 100644 --- a/public/v1/components/schemas.yaml +++ b/public/v1/components/schemas.yaml @@ -1117,9 +1117,11 @@ components: type: string description: The raw HTTP response headers. rawBody: - type: string + type: + - string + - 'null' description: | - The raw HTTP response body. + The raw HTTP response body or `null` if there was no body in response. Note that only the first 10 kb are returned. truncated: type: boolean diff --git a/test-e2e/dns.test.ts b/test-e2e/dns.test.ts index 4aa7ec07..f00e92c4 100644 --- a/test-e2e/dns.test.ts +++ b/test-e2e/dns.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-unused-expressions */ import got from 'got'; import { expect } from 'chai'; import { waitMesurementFinish } from './utils.js'; @@ -6,7 +5,7 @@ import { waitMesurementFinish } from './utils.js'; describe('dns mesurement', () => { it('should finish successfully', async () => { const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { - target: 'jsdelivr.com', + target: 'www.jsdelivr.com', type: 'dns', } }).json(); @@ -20,7 +19,7 @@ describe('dns mesurement', () => { it('should finish successfully in case of "traced": true', async () => { const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { - target: 'jsdelivr.com', + target: 'www.jsdelivr.com', type: 'dns', measurementOptions: { trace: true, diff --git a/test-e2e/http.test.ts b/test-e2e/http.test.ts index feb59595..faa98d1f 100644 --- a/test-e2e/http.test.ts +++ b/test-e2e/http.test.ts @@ -5,7 +5,7 @@ import { waitMesurementFinish } from './utils.js'; describe('http mesurement', () => { it('should finish successfully', async () => { const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { - target: 'jsdelivr.com', + target: 'www.jsdelivr.com', type: 'http', } }).json(); @@ -13,6 +13,26 @@ describe('http mesurement', () => { expect(body.status).to.equal('finished'); expect(body.results[0].result.status).to.equal('finished'); + expect(body.results[0].result.rawBody).to.equal(null); + expect(response).to.matchApiSchema(); + }); + + it('should finish successfully in case of GET request', async () => { + const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'www.jsdelivr.com', + type: 'http', + measurementOptions: { + request: { + method: 'GET', + }, + }, + } }).json(); + + const { response, body } = await waitMesurementFinish(id); + + expect(body.status).to.equal('finished'); + expect(body.results[0].result.status).to.equal('finished'); + expect(body.results[0].result.rawBody.length > 0).to.be.true; expect(response).to.matchApiSchema(); }); }); diff --git a/test-e2e/measurement.test.ts b/test-e2e/measurement.test.ts index 095aafda..a5c78d4d 100644 --- a/test-e2e/measurement.test.ts +++ b/test-e2e/measurement.test.ts @@ -4,7 +4,7 @@ import { expect } from 'chai'; describe('/measurements endpoint', () => { it('should create measurement', async () => { const response = await got.post('http://localhost:3000/v1/measurements', { json: { - target: 'jsdelivr.com', + target: 'www.jsdelivr.com', type: 'ping', } }); @@ -14,7 +14,7 @@ describe('/measurements endpoint', () => { it('should return measurement result', async () => { const responseOfCreate = await got.post('http://localhost:3000/v1/measurements', { json: { - target: 'jsdelivr.com', + target: 'www.jsdelivr.com', type: 'ping', } }); diff --git a/test-e2e/mtr.test.ts b/test-e2e/mtr.test.ts index 4059f295..e943ab77 100644 --- a/test-e2e/mtr.test.ts +++ b/test-e2e/mtr.test.ts @@ -5,7 +5,7 @@ import { waitMesurementFinish } from './utils.js'; describe('mtr mesurement', () => { it('should finish successfully', async () => { const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { - target: 'jsdelivr.com', + target: 'www.jsdelivr.com', type: 'mtr', } }).json(); diff --git a/test-e2e/ping.test.ts b/test-e2e/ping.test.ts index e42c7889..91e5d3d2 100644 --- a/test-e2e/ping.test.ts +++ b/test-e2e/ping.test.ts @@ -5,7 +5,7 @@ import { waitMesurementFinish } from './utils.js'; describe('ping mesurement', () => { it('should finish successfully', async () => { const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { - target: 'jsdelivr.com', + target: 'www.jsdelivr.com', type: 'ping', } }).json(); diff --git a/test-e2e/setup.ts b/test-e2e/setup.ts index 8b82eeee..4dbf2697 100644 --- a/test-e2e/setup.ts +++ b/test-e2e/setup.ts @@ -1,4 +1,4 @@ -import got from 'got'; +import got, { RequestError } from 'got'; import { setTimeout } from 'timers/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -15,7 +15,7 @@ const waitProbeToConnect = async () => { try { response = await got('http://localhost:3000/v1/probes'); } catch (err) { - console.log(err.code); + console.log((err as RequestError).code); throw err; } diff --git a/test-e2e/traceroute.test.ts b/test-e2e/traceroute.test.ts index 2c41df4c..e335b209 100644 --- a/test-e2e/traceroute.test.ts +++ b/test-e2e/traceroute.test.ts @@ -5,7 +5,7 @@ import { waitMesurementFinish } from './utils.js'; describe('traceroute mesurement', () => { it('should finish successfully', async () => { const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { - target: 'jsdelivr.com', + target: 'www.jsdelivr.com', type: 'traceroute', } }).json(); From 14bc1853b86a7c52e781475938d1d52c5c538ed3 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Wed, 6 Mar 2024 16:54:38 +0100 Subject: [PATCH 19/43] refactor: move files --- test-e2e/{ => tests}/adoption-code.test.ts | 0 test-e2e/{ => tests}/dns.test.ts | 2 +- test-e2e/{ => tests}/http.test.ts | 2 +- test-e2e/{ => tests}/measurement.test.ts | 0 test-e2e/{ => tests}/mtr.test.ts | 2 +- test-e2e/{ => tests}/ping.test.ts | 2 +- test-e2e/{ => tests}/probes.test.ts | 0 test-e2e/{ => tests}/traceroute.test.ts | 2 +- 8 files changed, 5 insertions(+), 5 deletions(-) rename test-e2e/{ => tests}/adoption-code.test.ts (100%) rename test-e2e/{ => tests}/dns.test.ts (95%) rename test-e2e/{ => tests}/http.test.ts (95%) rename test-e2e/{ => tests}/measurement.test.ts (100%) rename test-e2e/{ => tests}/mtr.test.ts (90%) rename test-e2e/{ => tests}/ping.test.ts (90%) rename test-e2e/{ => tests}/probes.test.ts (100%) rename test-e2e/{ => tests}/traceroute.test.ts (90%) diff --git a/test-e2e/adoption-code.test.ts b/test-e2e/tests/adoption-code.test.ts similarity index 100% rename from test-e2e/adoption-code.test.ts rename to test-e2e/tests/adoption-code.test.ts diff --git a/test-e2e/dns.test.ts b/test-e2e/tests/dns.test.ts similarity index 95% rename from test-e2e/dns.test.ts rename to test-e2e/tests/dns.test.ts index f00e92c4..569d9e6f 100644 --- a/test-e2e/dns.test.ts +++ b/test-e2e/tests/dns.test.ts @@ -1,6 +1,6 @@ import got from 'got'; import { expect } from 'chai'; -import { waitMesurementFinish } from './utils.js'; +import { waitMesurementFinish } from '../utils.js'; describe('dns mesurement', () => { it('should finish successfully', async () => { diff --git a/test-e2e/http.test.ts b/test-e2e/tests/http.test.ts similarity index 95% rename from test-e2e/http.test.ts rename to test-e2e/tests/http.test.ts index faa98d1f..11822b15 100644 --- a/test-e2e/http.test.ts +++ b/test-e2e/tests/http.test.ts @@ -1,6 +1,6 @@ import got from 'got'; import { expect } from 'chai'; -import { waitMesurementFinish } from './utils.js'; +import { waitMesurementFinish } from '../utils.js'; describe('http mesurement', () => { it('should finish successfully', async () => { diff --git a/test-e2e/measurement.test.ts b/test-e2e/tests/measurement.test.ts similarity index 100% rename from test-e2e/measurement.test.ts rename to test-e2e/tests/measurement.test.ts diff --git a/test-e2e/mtr.test.ts b/test-e2e/tests/mtr.test.ts similarity index 90% rename from test-e2e/mtr.test.ts rename to test-e2e/tests/mtr.test.ts index e943ab77..3925ad54 100644 --- a/test-e2e/mtr.test.ts +++ b/test-e2e/tests/mtr.test.ts @@ -1,6 +1,6 @@ import got from 'got'; import { expect } from 'chai'; -import { waitMesurementFinish } from './utils.js'; +import { waitMesurementFinish } from '../utils.js'; describe('mtr mesurement', () => { it('should finish successfully', async () => { diff --git a/test-e2e/ping.test.ts b/test-e2e/tests/ping.test.ts similarity index 90% rename from test-e2e/ping.test.ts rename to test-e2e/tests/ping.test.ts index 91e5d3d2..7d8da325 100644 --- a/test-e2e/ping.test.ts +++ b/test-e2e/tests/ping.test.ts @@ -1,6 +1,6 @@ import got from 'got'; import { expect } from 'chai'; -import { waitMesurementFinish } from './utils.js'; +import { waitMesurementFinish } from '../utils.js'; describe('ping mesurement', () => { it('should finish successfully', async () => { diff --git a/test-e2e/probes.test.ts b/test-e2e/tests/probes.test.ts similarity index 100% rename from test-e2e/probes.test.ts rename to test-e2e/tests/probes.test.ts diff --git a/test-e2e/traceroute.test.ts b/test-e2e/tests/traceroute.test.ts similarity index 90% rename from test-e2e/traceroute.test.ts rename to test-e2e/tests/traceroute.test.ts index e335b209..8d3fb54e 100644 --- a/test-e2e/traceroute.test.ts +++ b/test-e2e/tests/traceroute.test.ts @@ -1,6 +1,6 @@ import got from 'got'; import { expect } from 'chai'; -import { waitMesurementFinish } from './utils.js'; +import { waitMesurementFinish } from '../utils.js'; describe('traceroute mesurement', () => { it('should finish successfully', async () => { From 962a447488eeec3da753790c72c488286e78b485 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Thu, 7 Mar 2024 12:08:02 +0100 Subject: [PATCH 20/43] feat: add locations e2e --- test-e2e/tests/location.test.ts | 86 ++++++++++++++++++++++++++++++ test-e2e/tests/measurement.test.ts | 29 ---------- 2 files changed, 86 insertions(+), 29 deletions(-) create mode 100644 test-e2e/tests/location.test.ts delete mode 100644 test-e2e/tests/measurement.test.ts diff --git a/test-e2e/tests/location.test.ts b/test-e2e/tests/location.test.ts new file mode 100644 index 00000000..82eda70d --- /dev/null +++ b/test-e2e/tests/location.test.ts @@ -0,0 +1,86 @@ +import got from 'got'; +import { expect } from 'chai'; + +describe('locations filter', () => { + it('should create measurement without location', async () => { + const response = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'www.jsdelivr.com', + type: 'ping', + } }); + + expect(response.statusCode).to.equal(202); + }); + + it('should create measurement by valid "city" value', async () => { + const response = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'www.jsdelivr.com', + type: 'ping', + locations: [{ + city: 'Paris', + }], + } }); + + expect(response.statusCode).to.equal(202); + }); + + it('should not create measurement by invalid "city" value', async () => { + const response = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'www.jsdelivr.com', + type: 'ping', + locations: [{ + city: 'Ouagadougou', + }], + }, throwHttpErrors: false }); + + expect(response.statusCode).to.equal(422); + }); + + it('should create measurement by valid "magic" value', async () => { + const response = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'www.jsdelivr.com', + type: 'ping', + locations: [{ + magic: 'Paris', + }], + } }); + + expect(response.statusCode).to.equal(202); + }); + + it('should not create measurement by invalid "magic" value', async () => { + const response = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'www.jsdelivr.com', + type: 'ping', + locations: [{ + magic: 'Ouagadougou', + }], + }, throwHttpErrors: false }); + + expect(response.statusCode).to.equal(422); + }); + + it('should create measurement by id of another measurement', async () => { + const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'www.jsdelivr.com', + type: 'ping', + } }).json(); + + const response = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'www.jsdelivr.com', + type: 'ping', + locations: id, + } }); + + expect(response.statusCode).to.equal(202); + }); + + it('should not create measurement wrong id of another measurement', async () => { + const response = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'www.jsdelivr.com', + type: 'ping', + locations: 'wrongIdValue', + }, throwHttpErrors: false }); + + expect(response.statusCode).to.equal(422); + }); +}); diff --git a/test-e2e/tests/measurement.test.ts b/test-e2e/tests/measurement.test.ts deleted file mode 100644 index a5c78d4d..00000000 --- a/test-e2e/tests/measurement.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import got from 'got'; -import { expect } from 'chai'; - -describe('/measurements endpoint', () => { - it('should create measurement', async () => { - const response = await got.post('http://localhost:3000/v1/measurements', { json: { - target: 'www.jsdelivr.com', - type: 'ping', - } }); - - expect(response.statusCode).to.equal(202); - expect(response).to.matchApiSchema(); - }); - - it('should return measurement result', async () => { - const responseOfCreate = await got.post('http://localhost:3000/v1/measurements', { json: { - target: 'www.jsdelivr.com', - type: 'ping', - } }); - - const { id } = JSON.parse(responseOfCreate.body); - const responseOfRead = await got(`http://localhost:3000/v1/measurements/${id}`); - const body = JSON.parse(responseOfRead.body); - - expect(body.probesCount).to.equal(1); - expect(responseOfRead.statusCode).to.equal(200); - expect(responseOfRead).to.matchApiSchema(); - }); -}); From 9ebbaabe6a6f9f0c610bb9726916ab1ad204ff84 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Thu, 7 Mar 2024 13:03:50 +0100 Subject: [PATCH 21/43] fix: add missing nulls to schema --- public/v1/components/schemas.yaml | 8 ++++++-- test-e2e/tests/health.test.ts | 11 +++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 test-e2e/tests/health.test.ts diff --git a/public/v1/components/schemas.yaml b/public/v1/components/schemas.yaml index 3491d7f7..3b6dc2d6 100644 --- a/public/v1/components/schemas.yaml +++ b/public/v1/components/schemas.yaml @@ -682,7 +682,9 @@ components: - Micronesia - Polynesia ResolvedAddress: - type: string + type: + - string + - 'null' description: The resolved IP address of the `target`. ResolvedHostname: type: @@ -1193,7 +1195,9 @@ components: - $ref: 'schemas.yaml#/components/schemas/BaseTestStatus' - const: offline TimingPacketRtt: - type: number + type: + - number + - 'null' description: The round-trip time for this packet. TimingPacketTtl: type: number diff --git a/test-e2e/tests/health.test.ts b/test-e2e/tests/health.test.ts new file mode 100644 index 00000000..76c77c93 --- /dev/null +++ b/test-e2e/tests/health.test.ts @@ -0,0 +1,11 @@ +import got from 'got'; +import { expect } from 'chai'; + +describe('/probes endpoint', () => { + it('should return an array of probes', async () => { + const response = await got('http://localhost:3000/health'); + + expect(response.statusCode).to.equal(200); + expect(response.body).to.equal('Alive'); + }); +}); From bead9c740a0556a3abb477b2e7ee2f9b784a06a4 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Thu, 7 Mar 2024 16:00:33 +0100 Subject: [PATCH 22/43] test: add offline probes tests --- test-e2e/docker.ts | 36 ++++++++++++++++-- test-e2e/setup.ts | 29 ++------------ test-e2e/tests/location.test.ts | 2 +- test-e2e/tests/offline-probes.test.ts | 54 +++++++++++++++++++++++++++ test-e2e/utils.ts | 25 ++++++++++++- 5 files changed, 114 insertions(+), 32 deletions(-) create mode 100644 test-e2e/tests/offline-probes.test.ts diff --git a/test-e2e/docker.ts b/test-e2e/docker.ts index 0e45b553..b13f6453 100644 --- a/test-e2e/docker.ts +++ b/test-e2e/docker.ts @@ -16,7 +16,7 @@ const attachLogs = async (container: Docker.Container) => { container.modem.demuxStream(stream, process.stdout, process.stderr); }; -export const startProbeContainer = async () => { +export const createProbeContainer = async () => { const docker = new Docker(); const isLinux = await isLinuxHost(docker); @@ -41,17 +41,45 @@ export const startProbeContainer = async () => { await attachLogs(container); }; -export const removeProbeContainer = async () => { +const getProbeContainer = async () => { const docker = new Docker(); const containers = await docker.listContainers({ all: true }); const containerInfo = containers.find(c => c.Names.includes('/globalping-probe-e2e')); if (!containerInfo) { console.log('Container not found:'); - return; + return { container: null, state: null }; } - const container = docker.getContainer(containerInfo.Id); + return { container: docker.getContainer(containerInfo.Id), state: containerInfo.State }; +}; + +export const removeProbeContainer = async () => { + const { container } = await getProbeContainer(); + + if (!container) { + return; + } await container.remove({ force: true }); }; + +export const stopProbeContainer = async () => { + const { container, state } = await getProbeContainer(); + + if (!container || state === 'exited') { + return; + } + + await container.stop(); +}; + +export const startProbeContainer = async () => { + const { container, state } = await getProbeContainer(); + + if (!container || state === 'running') { + return; + } + + await container.start({}); +}; diff --git a/test-e2e/setup.ts b/test-e2e/setup.ts index 4dbf2697..3ff49edd 100644 --- a/test-e2e/setup.ts +++ b/test-e2e/setup.ts @@ -1,40 +1,17 @@ -import got, { RequestError } from 'got'; -import { setTimeout } from 'timers/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import chai from 'chai'; import chaiOas from '../test/plugins/oas/index.js'; -import type { Probe } from '../src/probe/types.js'; -import { removeProbeContainer, startProbeContainer } from './docker.js'; - -const waitProbeToConnect = async () => { - let response; - - for (;;) { - try { - response = await got('http://localhost:3000/v1/probes'); - } catch (err) { - console.log((err as RequestError).code); - throw err; - } - - const probes = JSON.parse(response.body) as Probe[]; - - if (probes.length > 0) { - return; - } - - await setTimeout(1000); - } -}; +import { removeProbeContainer, createProbeContainer } from './docker.js'; +import { waitProbeToConnect } from './utils.js'; before(async () => { chai.use(await chaiOas({ specPath: path.join(fileURLToPath(new URL('.', import.meta.url)), '../public/v1/spec.yaml') })); await removeProbeContainer(); - await startProbeContainer(); + await createProbeContainer(); await waitProbeToConnect(); }); diff --git a/test-e2e/tests/location.test.ts b/test-e2e/tests/location.test.ts index 82eda70d..d2ecf91d 100644 --- a/test-e2e/tests/location.test.ts +++ b/test-e2e/tests/location.test.ts @@ -74,7 +74,7 @@ describe('locations filter', () => { expect(response.statusCode).to.equal(202); }); - it('should not create measurement wrong id of another measurement', async () => { + it('should not create measurement by wrong id', async () => { const response = await got.post('http://localhost:3000/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', diff --git a/test-e2e/tests/offline-probes.test.ts b/test-e2e/tests/offline-probes.test.ts new file mode 100644 index 00000000..95413379 --- /dev/null +++ b/test-e2e/tests/offline-probes.test.ts @@ -0,0 +1,54 @@ +import got from 'got'; +import { expect } from 'chai'; + +import { waitMesurementFinish, waitProbeToConnect } from '../utils.js'; +import { startProbeContainer, stopProbeContainer } from '../docker.js'; + +describe('api', () => { + beforeEach(async () => { + await startProbeContainer(); + await waitProbeToConnect(); + }); + + after(async () => { + await startProbeContainer(); + await waitProbeToConnect(); + }); + + it('should create measurement with "offline" result if requested probe is not connected', async () => { + const { id: locationId } = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'www.jsdelivr.com', + type: 'ping', + } }).json(); + + await stopProbeContainer(); + + const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'www.jsdelivr.com', + type: 'ping', + locations: locationId, + } }).json(); + + const { response, body } = await waitMesurementFinish(id); + + expect(body.status).to.equal('finished'); + expect(body.results[0].result.status).to.equal('offline'); + expect(response).to.matchApiSchema(); + }); + + it('should create measurement with "failed" result if probe failed to send result', async () => { + const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'www.jsdelivr.com', + type: 'ping', + } }).json(); + + await stopProbeContainer(); + + const { response, body } = await waitMesurementFinish(id); + + expect(body.status).to.equal('finished'); + expect(body.results[0].result.status).to.equal('failed'); + expect(body.results[0].result.rawOutput).to.equal('\n\nThe measurement timed out'); + expect(response).to.matchApiSchema(); + }).timeout(40000); +}); diff --git a/test-e2e/utils.ts b/test-e2e/utils.ts index 6a10f246..858e657a 100644 --- a/test-e2e/utils.ts +++ b/test-e2e/utils.ts @@ -1,5 +1,28 @@ +import got, { type RequestError } from 'got'; import { setTimeout } from 'timers/promises'; -import got from 'got'; + +import type { Probe } from '../src/probe/types.js'; + +export const waitProbeToConnect = async () => { + let response; + + for (;;) { + try { + response = await got('http://localhost:3000/v1/probes'); + } catch (err) { + console.log((err as RequestError).code); + throw err; + } + + const probes = JSON.parse(response.body) as Probe[]; + + if (probes.length > 0) { + return; + } + + await setTimeout(1000); + } +}; export const waitMesurementFinish = async (id: string) => { for (;;) { From ef004dbe37f04555a0ae3cd7c48c1fc80c2ebc8f Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Thu, 7 Mar 2024 17:26:20 +0100 Subject: [PATCH 23/43] test: add adopted probes tests e2e --- test-e2e/tests/adopted-probes.test.ts | 74 +++++++++++++++++++++++++++ test-e2e/utils.ts | 21 ++++++++ wallaby.e2e.js | 3 ++ 3 files changed, 98 insertions(+) create mode 100644 test-e2e/tests/adopted-probes.test.ts diff --git a/test-e2e/tests/adopted-probes.test.ts b/test-e2e/tests/adopted-probes.test.ts new file mode 100644 index 00000000..9f4605a6 --- /dev/null +++ b/test-e2e/tests/adopted-probes.test.ts @@ -0,0 +1,74 @@ +import got from 'got'; +import { expect } from 'chai'; +import { client } from '../../src/lib/sql/client.js'; +import { ADOPTED_PROBES_TABLE } from '../../src/lib/adopted-probes.js'; +import { waitProbeInCity } from '../utils.js'; + +describe('adopted probe', () => { + before(async function () { + this.timeout(80000); + + await client(ADOPTED_PROBES_TABLE).insert({ + userId: '89da69bd-a236-4ab7-9c5d-b5f52ce09959', + lastSyncDate: new Date(), + ip: '51.158.22.211', + uuid: '1-1-1-1-1', + isCustomCity: 1, + tags: '[{"prefix":"jimaek","value":"dashboardtag1"}]', + status: 'ready', + version: '0.28.0', + hardwareDevice: null, + country: 'FR', + countryOfCustomCity: 'FR', + city: 'Marseille', + latitude: '43.29695', + longitude: '5.38107', + network: 'InterBS S.R.L. (BAEHOST)', + asn: 61004, + }); + + await waitProbeInCity('Marseille'); + }); + + after(async function () { + this.timeout(80000); + await client(ADOPTED_PROBES_TABLE).where({ city: 'Marseille' }).delete(); + await waitProbeInCity('Paris'); + }); + + it('should create measurement by its new location', async () => { + const response = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'www.jsdelivr.com', + type: 'ping', + locations: [{ + city: 'Marseille', + }], + } }); + + expect(response.statusCode).to.equal(202); + }); + + it('should create measurement by user tag', async () => { + const response = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'www.jsdelivr.com', + type: 'ping', + locations: [{ + tags: [ 'u-jimaek-dashboardtag1' ], + }], + } }); + + expect(response.statusCode).to.equal(202); + }); + + it('should not create measurement by its old location', async () => { + const response = await got.post('http://localhost:3000/v1/measurements', { json: { + target: 'www.jsdelivr.com', + type: 'ping', + locations: [{ + city: 'Paris', + }], + }, throwHttpErrors: false }); + + expect(response.statusCode).to.equal(422); + }); +}); diff --git a/test-e2e/utils.ts b/test-e2e/utils.ts index 858e657a..f3fa8020 100644 --- a/test-e2e/utils.ts +++ b/test-e2e/utils.ts @@ -24,6 +24,27 @@ export const waitProbeToConnect = async () => { } }; +export const waitProbeInCity = async (city: string) => { + let response; + + for (;;) { + try { + response = await got('http://localhost:3000/v1/probes'); + } catch (err) { + console.log((err as RequestError).code); + throw err; + } + + const probes = JSON.parse(response.body) as Probe[]; + + if (probes.length > 0 && probes[0]!.location.city === city) { + return; + } + + await setTimeout(1000); + } +}; + export const waitMesurementFinish = async (id: string) => { for (;;) { const response = await got(`http://localhost:3000/v1/measurements/${id}`); diff --git a/wallaby.e2e.js b/wallaby.e2e.js index aa3a0224..3a5a29d9 100644 --- a/wallaby.e2e.js +++ b/wallaby.e2e.js @@ -2,12 +2,15 @@ export default function wallaby () { return { testFramework: 'mocha', files: [ + 'config/*', 'public/v1/*', 'public/**/*.yaml', 'test/plugins/**/*', 'test-e2e/setup.ts', 'test-e2e/utils.ts', 'test-e2e/docker.ts', + 'src/**/*.ts', + 'knexfile.js', 'package.json', ], tests: [ From 88f91d42584a615ba38996486731a4ff15de8b0c Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Fri, 8 Mar 2024 13:26:54 +0100 Subject: [PATCH 24/43] fix: e2e mocharc --- .github/workflows/ci.yml | 8 ++++---- .mocharc.e2e.json | 3 +-- test-e2e/tests/adopted-probes.test.ts | 7 +++++++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 498635c6..501c0cfb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,10 +48,6 @@ jobs: run: | npm ci npm run build - - name: Test E2E - run: | - docker pull ghcr.io/jsdelivr/globalping-probe - npm run test:e2e - name: Test Unit run: | npm run lint @@ -62,3 +58,7 @@ jobs: rm -rf node_modules npm ci --omit=dev npm run test:dist + - name: Test E2E + run: | + docker pull ghcr.io/jsdelivr/globalping-probe + npm run test:e2e diff --git a/.mocharc.e2e.json b/.mocharc.e2e.json index 712e9c6e..6be452d1 100644 --- a/.mocharc.e2e.json +++ b/.mocharc.e2e.json @@ -10,8 +10,7 @@ ], "node-option": [ "experimental-specifier-resolution=node", - "loader=ts-node/esm", - "loader=testdouble" + "loader=ts-node/esm" ], "globals": [ "__extends", diff --git a/test-e2e/tests/adopted-probes.test.ts b/test-e2e/tests/adopted-probes.test.ts index 9f4605a6..ffef0d53 100644 --- a/test-e2e/tests/adopted-probes.test.ts +++ b/test-e2e/tests/adopted-probes.test.ts @@ -3,6 +3,7 @@ import { expect } from 'chai'; import { client } from '../../src/lib/sql/client.js'; import { ADOPTED_PROBES_TABLE } from '../../src/lib/adopted-probes.js'; import { waitProbeInCity } from '../utils.js'; +import type { Probe } from '../../src/probe/types.js'; describe('adopted probe', () => { before(async function () { @@ -36,6 +37,12 @@ describe('adopted probe', () => { await waitProbeInCity('Paris'); }); + it('should return probe list with updated city', async () => { + const probes = await got('http://localhost:3000/v1/probes').json(); + + expect(probes[0]!.location.city).to.equal('Marseille'); + }); + it('should create measurement by its new location', async () => { const response = await got.post('http://localhost:3000/v1/measurements', { json: { target: 'www.jsdelivr.com', From f7cb9c0ee29fb33adbe89829901bcf2dbf8372cf Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Fri, 8 Mar 2024 13:39:41 +0100 Subject: [PATCH 25/43] fix: tests order --- .github/workflows/ci.yml | 8 ++++---- test-e2e/tests/adoption-code.test.ts | 4 ---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 501c0cfb..bad45c79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,12 +53,12 @@ jobs: npm run lint npm run coverage npm run test:portman + - name: Test E2E + run: | + docker pull ghcr.io/jsdelivr/globalping-probe + npm run test:e2e - name: Test Dist run: | rm -rf node_modules npm ci --omit=dev npm run test:dist - - name: Test E2E - run: | - docker pull ghcr.io/jsdelivr/globalping-probe - npm run test:e2e diff --git a/test-e2e/tests/adoption-code.test.ts b/test-e2e/tests/adoption-code.test.ts index 1917d6e7..18900d4f 100644 --- a/test-e2e/tests/adoption-code.test.ts +++ b/test-e2e/tests/adoption-code.test.ts @@ -12,13 +12,9 @@ describe('/adoption-code endpoint', () => { expect(response.statusCode).to.equal(200); expect(body).to.deep.include({ - asn: 12876, city: 'Paris', country: 'FR', hardwareDevice: null, - latitude: 48.8534, - longitude: 2.3488, - network: 'SCALEWAY S.A.S.', state: null, status: 'ready', }); From 0d6f95a33c14cb67b6b46374253d6196c85fd6e3 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Fri, 8 Mar 2024 16:30:04 +0100 Subject: [PATCH 26/43] feat: run api as docker container --- package.json | 4 ++-- src/index.ts | 1 + test-e2e/docker.ts | 56 +++++++++++++++++++++++++++++++++++++++++----- test-e2e/setup.ts | 8 +++++-- test-e2e/utils.ts | 3 ++- 5 files changed, 61 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 97602a65..a51794b6 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "download:files": "tsx src/lib/download-files.ts", "prepare": "npm run download:files; husky install || echo 'Failed to install husky'", "stats": "NEW_RELIC_ENABLED=false tsx probes-stats/known.ts", - "start": "NODE_ENV=production node --max_old_space_size=3584 --max-semi-space-size=128 --experimental-loader newrelic/esm-loader.mjs -r newrelic dist/src/index.js", + "start": "node --max_old_space_size=3584 --max-semi-space-size=128 --experimental-loader newrelic/esm-loader.mjs -r newrelic dist/src/index.js", "start:dev": "NODE_ENV=development FAKE_PROBE_IP=1 NEW_RELIC_ENABLED=false tsx src/index.ts", "start:test": "NODE_ENV=test knex migrate:latest && NODE_ENV=test TEST_MODE=e2e NEW_RELIC_ENABLED=false tsx src/index.ts", "lint": "npm run lint:js && npm run lint:types && npm run lint:docs", @@ -136,7 +136,7 @@ "test:portman": "TEST_DONT_RESTART_WORKERS=1 start-test 'npm run start:test' http://localhost:3000/health 'npm run test:portman:create && npm run test:portman:run' 2> /dev/null || E=$?; rm -rf tmp; exit $E;", "test:portman:create": "mkdir -p tmp && redocly bundle public/v1/spec.yaml > tmp/spec.yaml && portman --cliOptionsFile test/tests/contract/portman-cli.json", "test:portman:run": "newman run tmp/converted/globalpingApi.json -e test/tests/contract/newman-env.json --ignore-redirects", - "test:e2e": "start-test 'npm run start:test' http://localhost:3000/health 'npm run test:e2e:cases'", + "test:e2e": "docker build -t globalping-api-e2e . && npm run test:e2e:cases", "test:e2e:cases": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha --config .mocharc.e2e.json" }, "lint-staged": { diff --git a/src/index.ts b/src/index.ts index 0d38177a..6031cae6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ import { flushRedisCache } from './lib/flush-redis-cache.js'; const logger = scopedLogger('index'); const port = process.env['PORT'] ?? config.get('server.port'); const workerCount = config.get('server.processes'); +console.log('process.env[\'NODE_ENV\']', process.env['NODE_ENV']); const workerFn = async () => { const server = await createServer(); diff --git a/test-e2e/docker.ts b/test-e2e/docker.ts index b13f6453..1773efc4 100644 --- a/test-e2e/docker.ts +++ b/test-e2e/docker.ts @@ -3,6 +3,7 @@ import Docker from 'dockerode'; const isLinuxHost = async (docker: Docker) => { const versionInfo = await docker.version(); const platformName = versionInfo.Platform.Name.toLowerCase(); + console.log('platformName', platformName); const isLinuxHost = platformName.includes('engine'); return isLinuxHost; }; @@ -16,22 +17,55 @@ const attachLogs = async (container: Docker.Container) => { container.modem.demuxStream(stream, process.stdout, process.stderr); }; +export const createApiContainer = async () => { + const docker = new Docker(); + + const isLinux = await isLinuxHost(docker); + console.log('isLinux', isLinux); + + const container = await docker.createContainer({ + Image: 'globalping-api-e2e', + name: 'globalping-api-e2e', + Env: [ + 'NODE_ENV=test', + 'TEST_MODE=e2e', + 'NEW_RELIC_ENABLED=false', + // `REDIS_URL=redis://${isLinux ? 'localhost' : 'host.docker.internal'}:6379`, + // `DB_CONNECTION_HOST=${isLinux ? 'localhost' : 'host.docker.internal'}`, + `REDIS_URL=redis://localhost:6379`, + `DB_CONNECTION_HOST=localhost`, + ], + HostConfig: { + PortBindings: { + '80/tcp': [{ HostPort: '3000' }], + }, + NetworkMode: 'host', + }, + }); + + await container.start({}); + + await attachLogs(container); +}; + export const createProbeContainer = async () => { const docker = new Docker(); const isLinux = await isLinuxHost(docker); + console.log('isLinux', isLinux); const container = await docker.createContainer({ Image: 'ghcr.io/jsdelivr/globalping-probe', name: 'globalping-probe-e2e', Env: [ - `API_HOST=ws://${isLinux ? 'localhost' : 'host.docker.internal'}:3000`, // https://github.com/moby/moby/pull/40007 + `API_HOST=ws://localhost:3000`, ], HostConfig: { LogConfig: { Type: 'local', Config: {}, }, + // NetworkMode: isLinux ? 'host' : 'bridge', NetworkMode: 'host', }, }); @@ -41,10 +75,10 @@ export const createProbeContainer = async () => { await attachLogs(container); }; -const getProbeContainer = async () => { +const getContainer = async (name: string) => { const docker = new Docker(); const containers = await docker.listContainers({ all: true }); - const containerInfo = containers.find(c => c.Names.includes('/globalping-probe-e2e')); + const containerInfo = containers.find(c => c.Names.includes(`/${name}`)); if (!containerInfo) { console.log('Container not found:'); @@ -55,7 +89,17 @@ const getProbeContainer = async () => { }; export const removeProbeContainer = async () => { - const { container } = await getProbeContainer(); + const { container } = await getContainer('globalping-probe-e2e'); + + if (!container) { + return; + } + + await container.remove({ force: true }); +}; + +export const removeApiContainer = async () => { + const { container } = await getContainer('globalping-api-e2e'); if (!container) { return; @@ -65,7 +109,7 @@ export const removeProbeContainer = async () => { }; export const stopProbeContainer = async () => { - const { container, state } = await getProbeContainer(); + const { container, state } = await getContainer('globalping-probe-e2e'); if (!container || state === 'exited') { return; @@ -75,7 +119,7 @@ export const stopProbeContainer = async () => { }; export const startProbeContainer = async () => { - const { container, state } = await getProbeContainer(); + const { container, state } = await getContainer('globalping-probe-e2e'); if (!container || state === 'running') { return; diff --git a/test-e2e/setup.ts b/test-e2e/setup.ts index 3ff49edd..63baeb16 100644 --- a/test-e2e/setup.ts +++ b/test-e2e/setup.ts @@ -3,14 +3,16 @@ import { fileURLToPath } from 'node:url'; import chai from 'chai'; import chaiOas from '../test/plugins/oas/index.js'; -import { removeProbeContainer, createProbeContainer } from './docker.js'; +import { removeProbeContainer, createProbeContainer, removeApiContainer, createApiContainer } from './docker.js'; import { waitProbeToConnect } from './utils.js'; before(async () => { chai.use(await chaiOas({ specPath: path.join(fileURLToPath(new URL('.', import.meta.url)), '../public/v1/spec.yaml') })); - await removeProbeContainer(); + await removeApiContainer(); + await createApiContainer(); + await removeProbeContainer(); await createProbeContainer(); await waitProbeToConnect(); @@ -18,4 +20,6 @@ before(async () => { after(async () => { await removeProbeContainer(); + + await removeApiContainer(); }); diff --git a/test-e2e/utils.ts b/test-e2e/utils.ts index f3fa8020..a689bc97 100644 --- a/test-e2e/utils.ts +++ b/test-e2e/utils.ts @@ -11,7 +11,8 @@ export const waitProbeToConnect = async () => { response = await got('http://localhost:3000/v1/probes'); } catch (err) { console.log((err as RequestError).code); - throw err; + await setTimeout(1000); + continue; } const probes = JSON.parse(response.body) as Probe[]; From 21ae107f99f60ae8d8834244e8501bfa43f7b07e Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Fri, 8 Mar 2024 17:30:29 +0100 Subject: [PATCH 27/43] fix: run api on port 3000 --- test-e2e/docker.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/test-e2e/docker.ts b/test-e2e/docker.ts index 1773efc4..2cd90b88 100644 --- a/test-e2e/docker.ts +++ b/test-e2e/docker.ts @@ -30,16 +30,18 @@ export const createApiContainer = async () => { 'NODE_ENV=test', 'TEST_MODE=e2e', 'NEW_RELIC_ENABLED=false', - // `REDIS_URL=redis://${isLinux ? 'localhost' : 'host.docker.internal'}:6379`, - // `DB_CONNECTION_HOST=${isLinux ? 'localhost' : 'host.docker.internal'}`, - `REDIS_URL=redis://localhost:6379`, - `DB_CONNECTION_HOST=localhost`, + 'PORT=3000', + `REDIS_URL=redis://${isLinux ? 'localhost' : 'host.docker.internal'}:6379`, + `DB_CONNECTION_HOST=${isLinux ? 'localhost' : 'host.docker.internal'}`, + // `REDIS_URL=redis://localhost:6379`, + // `DB_CONNECTION_HOST=localhost`, ], HostConfig: { PortBindings: { - '80/tcp': [{ HostPort: '3000' }], + '3000/tcp': [{ HostPort: '3000' }], }, - NetworkMode: 'host', + NetworkMode: isLinux ? 'host' : 'bridge', + // NetworkMode: 'host', }, }); @@ -58,15 +60,16 @@ export const createProbeContainer = async () => { Image: 'ghcr.io/jsdelivr/globalping-probe', name: 'globalping-probe-e2e', Env: [ - `API_HOST=ws://localhost:3000`, + `API_HOST=ws://${isLinux ? 'localhost' : 'host.docker.internal'}:3000`, + // `API_HOST=ws://localhost:3000`, ], HostConfig: { LogConfig: { Type: 'local', Config: {}, }, - // NetworkMode: isLinux ? 'host' : 'bridge', - NetworkMode: 'host', + NetworkMode: isLinux ? 'host' : 'bridge', + // NetworkMode: 'host', }, }); @@ -127,3 +130,6 @@ export const startProbeContainer = async () => { await container.start({}); }; + +await createApiContainer(); +await createProbeContainer(); From d5802c44b85ba399fc699281c08e23cbcc6fafaf Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Fri, 8 Mar 2024 18:00:31 +0100 Subject: [PATCH 28/43] fix: local run --- test-e2e/docker.ts | 13 ++----------- test-e2e/tests/adopted-probes.test.ts | 8 ++++---- test-e2e/tests/adoption-code.test.ts | 2 +- test-e2e/tests/dns.test.ts | 4 ++-- test-e2e/tests/health.test.ts | 2 +- test-e2e/tests/http.test.ts | 4 ++-- test-e2e/tests/location.test.ts | 16 ++++++++-------- test-e2e/tests/mtr.test.ts | 2 +- test-e2e/tests/offline-probes.test.ts | 6 +++--- test-e2e/tests/ping.test.ts | 2 +- test-e2e/tests/probes.test.ts | 2 +- test-e2e/tests/traceroute.test.ts | 2 +- test-e2e/utils.ts | 6 +++--- 13 files changed, 30 insertions(+), 39 deletions(-) diff --git a/test-e2e/docker.ts b/test-e2e/docker.ts index 2cd90b88..b3cb3bb7 100644 --- a/test-e2e/docker.ts +++ b/test-e2e/docker.ts @@ -30,18 +30,14 @@ export const createApiContainer = async () => { 'NODE_ENV=test', 'TEST_MODE=e2e', 'NEW_RELIC_ENABLED=false', - 'PORT=3000', `REDIS_URL=redis://${isLinux ? 'localhost' : 'host.docker.internal'}:6379`, `DB_CONNECTION_HOST=${isLinux ? 'localhost' : 'host.docker.internal'}`, - // `REDIS_URL=redis://localhost:6379`, - // `DB_CONNECTION_HOST=localhost`, ], HostConfig: { PortBindings: { - '3000/tcp': [{ HostPort: '3000' }], + '80/tcp': [{ HostPort: '80' }], }, NetworkMode: isLinux ? 'host' : 'bridge', - // NetworkMode: 'host', }, }); @@ -60,8 +56,7 @@ export const createProbeContainer = async () => { Image: 'ghcr.io/jsdelivr/globalping-probe', name: 'globalping-probe-e2e', Env: [ - `API_HOST=ws://${isLinux ? 'localhost' : 'host.docker.internal'}:3000`, - // `API_HOST=ws://localhost:3000`, + `API_HOST=ws://${isLinux ? 'localhost' : 'host.docker.internal'}:80`, ], HostConfig: { LogConfig: { @@ -69,7 +64,6 @@ export const createProbeContainer = async () => { Config: {}, }, NetworkMode: isLinux ? 'host' : 'bridge', - // NetworkMode: 'host', }, }); @@ -130,6 +124,3 @@ export const startProbeContainer = async () => { await container.start({}); }; - -await createApiContainer(); -await createProbeContainer(); diff --git a/test-e2e/tests/adopted-probes.test.ts b/test-e2e/tests/adopted-probes.test.ts index ffef0d53..f111bc58 100644 --- a/test-e2e/tests/adopted-probes.test.ts +++ b/test-e2e/tests/adopted-probes.test.ts @@ -38,13 +38,13 @@ describe('adopted probe', () => { }); it('should return probe list with updated city', async () => { - const probes = await got('http://localhost:3000/v1/probes').json(); + const probes = await got('http://localhost:80/v1/probes').json(); expect(probes[0]!.location.city).to.equal('Marseille'); }); it('should create measurement by its new location', async () => { - const response = await got.post('http://localhost:3000/v1/measurements', { json: { + const response = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', locations: [{ @@ -56,7 +56,7 @@ describe('adopted probe', () => { }); it('should create measurement by user tag', async () => { - const response = await got.post('http://localhost:3000/v1/measurements', { json: { + const response = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', locations: [{ @@ -68,7 +68,7 @@ describe('adopted probe', () => { }); it('should not create measurement by its old location', async () => { - const response = await got.post('http://localhost:3000/v1/measurements', { json: { + const response = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', locations: [{ diff --git a/test-e2e/tests/adoption-code.test.ts b/test-e2e/tests/adoption-code.test.ts index 18900d4f..e9b3370b 100644 --- a/test-e2e/tests/adoption-code.test.ts +++ b/test-e2e/tests/adoption-code.test.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; describe('/adoption-code endpoint', () => { it('should send code to the probe', async () => { - const response = await got.post('http://localhost:3000/v1/adoption-code?systemkey=system', { json: { + const response = await got.post('http://localhost:80/v1/adoption-code?systemkey=system', { json: { ip: '51.158.22.211', code: '123456', } }); diff --git a/test-e2e/tests/dns.test.ts b/test-e2e/tests/dns.test.ts index 569d9e6f..95e4887c 100644 --- a/test-e2e/tests/dns.test.ts +++ b/test-e2e/tests/dns.test.ts @@ -4,7 +4,7 @@ import { waitMesurementFinish } from '../utils.js'; describe('dns mesurement', () => { it('should finish successfully', async () => { - const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + const { id } = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'dns', } }).json(); @@ -18,7 +18,7 @@ describe('dns mesurement', () => { }); it('should finish successfully in case of "traced": true', async () => { - const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + const { id } = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'dns', measurementOptions: { diff --git a/test-e2e/tests/health.test.ts b/test-e2e/tests/health.test.ts index 76c77c93..ed1d1efb 100644 --- a/test-e2e/tests/health.test.ts +++ b/test-e2e/tests/health.test.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; describe('/probes endpoint', () => { it('should return an array of probes', async () => { - const response = await got('http://localhost:3000/health'); + const response = await got('http://localhost:80/health'); expect(response.statusCode).to.equal(200); expect(response.body).to.equal('Alive'); diff --git a/test-e2e/tests/http.test.ts b/test-e2e/tests/http.test.ts index 11822b15..efb87d08 100644 --- a/test-e2e/tests/http.test.ts +++ b/test-e2e/tests/http.test.ts @@ -4,7 +4,7 @@ import { waitMesurementFinish } from '../utils.js'; describe('http mesurement', () => { it('should finish successfully', async () => { - const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + const { id } = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'http', } }).json(); @@ -18,7 +18,7 @@ describe('http mesurement', () => { }); it('should finish successfully in case of GET request', async () => { - const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + const { id } = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'http', measurementOptions: { diff --git a/test-e2e/tests/location.test.ts b/test-e2e/tests/location.test.ts index d2ecf91d..5448ee80 100644 --- a/test-e2e/tests/location.test.ts +++ b/test-e2e/tests/location.test.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; describe('locations filter', () => { it('should create measurement without location', async () => { - const response = await got.post('http://localhost:3000/v1/measurements', { json: { + const response = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', } }); @@ -12,7 +12,7 @@ describe('locations filter', () => { }); it('should create measurement by valid "city" value', async () => { - const response = await got.post('http://localhost:3000/v1/measurements', { json: { + const response = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', locations: [{ @@ -24,7 +24,7 @@ describe('locations filter', () => { }); it('should not create measurement by invalid "city" value', async () => { - const response = await got.post('http://localhost:3000/v1/measurements', { json: { + const response = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', locations: [{ @@ -36,7 +36,7 @@ describe('locations filter', () => { }); it('should create measurement by valid "magic" value', async () => { - const response = await got.post('http://localhost:3000/v1/measurements', { json: { + const response = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', locations: [{ @@ -48,7 +48,7 @@ describe('locations filter', () => { }); it('should not create measurement by invalid "magic" value', async () => { - const response = await got.post('http://localhost:3000/v1/measurements', { json: { + const response = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', locations: [{ @@ -60,12 +60,12 @@ describe('locations filter', () => { }); it('should create measurement by id of another measurement', async () => { - const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + const { id } = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', } }).json(); - const response = await got.post('http://localhost:3000/v1/measurements', { json: { + const response = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', locations: id, @@ -75,7 +75,7 @@ describe('locations filter', () => { }); it('should not create measurement by wrong id', async () => { - const response = await got.post('http://localhost:3000/v1/measurements', { json: { + const response = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', locations: 'wrongIdValue', diff --git a/test-e2e/tests/mtr.test.ts b/test-e2e/tests/mtr.test.ts index 3925ad54..da85b315 100644 --- a/test-e2e/tests/mtr.test.ts +++ b/test-e2e/tests/mtr.test.ts @@ -4,7 +4,7 @@ import { waitMesurementFinish } from '../utils.js'; describe('mtr mesurement', () => { it('should finish successfully', async () => { - const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + const { id } = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'mtr', } }).json(); diff --git a/test-e2e/tests/offline-probes.test.ts b/test-e2e/tests/offline-probes.test.ts index 95413379..7cf1b469 100644 --- a/test-e2e/tests/offline-probes.test.ts +++ b/test-e2e/tests/offline-probes.test.ts @@ -16,14 +16,14 @@ describe('api', () => { }); it('should create measurement with "offline" result if requested probe is not connected', async () => { - const { id: locationId } = await got.post('http://localhost:3000/v1/measurements', { json: { + const { id: locationId } = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', } }).json(); await stopProbeContainer(); - const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + const { id } = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', locations: locationId, @@ -37,7 +37,7 @@ describe('api', () => { }); it('should create measurement with "failed" result if probe failed to send result', async () => { - const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + const { id } = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', } }).json(); diff --git a/test-e2e/tests/ping.test.ts b/test-e2e/tests/ping.test.ts index 7d8da325..612fec1c 100644 --- a/test-e2e/tests/ping.test.ts +++ b/test-e2e/tests/ping.test.ts @@ -4,7 +4,7 @@ import { waitMesurementFinish } from '../utils.js'; describe('ping mesurement', () => { it('should finish successfully', async () => { - const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + const { id } = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'ping', } }).json(); diff --git a/test-e2e/tests/probes.test.ts b/test-e2e/tests/probes.test.ts index 88bee0a6..1a70811c 100644 --- a/test-e2e/tests/probes.test.ts +++ b/test-e2e/tests/probes.test.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; describe('/probes endpoint', () => { it('should return an array of probes', async () => { - const response = await got('http://localhost:3000/v1/probes'); + const response = await got('http://localhost:80/v1/probes'); const probes = JSON.parse(response.body); expect(response.statusCode).to.equal(200); diff --git a/test-e2e/tests/traceroute.test.ts b/test-e2e/tests/traceroute.test.ts index 8d3fb54e..d1db8f18 100644 --- a/test-e2e/tests/traceroute.test.ts +++ b/test-e2e/tests/traceroute.test.ts @@ -4,7 +4,7 @@ import { waitMesurementFinish } from '../utils.js'; describe('traceroute mesurement', () => { it('should finish successfully', async () => { - const { id } = await got.post('http://localhost:3000/v1/measurements', { json: { + const { id } = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', type: 'traceroute', } }).json(); diff --git a/test-e2e/utils.ts b/test-e2e/utils.ts index a689bc97..d2bf5977 100644 --- a/test-e2e/utils.ts +++ b/test-e2e/utils.ts @@ -8,7 +8,7 @@ export const waitProbeToConnect = async () => { for (;;) { try { - response = await got('http://localhost:3000/v1/probes'); + response = await got('http://localhost:80/v1/probes'); } catch (err) { console.log((err as RequestError).code); await setTimeout(1000); @@ -30,7 +30,7 @@ export const waitProbeInCity = async (city: string) => { for (;;) { try { - response = await got('http://localhost:3000/v1/probes'); + response = await got('http://localhost:80/v1/probes'); } catch (err) { console.log((err as RequestError).code); throw err; @@ -48,7 +48,7 @@ export const waitProbeInCity = async (city: string) => { export const waitMesurementFinish = async (id: string) => { for (;;) { - const response = await got(`http://localhost:3000/v1/measurements/${id}`); + const response = await got(`http://localhost:80/v1/measurements/${id}`); const body = JSON.parse(response.body); if (body.status !== 'in-progress') { From 999a0b6eeafb54e1e2dda4808281dc91e65c3330 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Mon, 11 Mar 2024 14:12:05 +0100 Subject: [PATCH 29/43] refactor: docker manager --- .github/workflows/ci.yml | 1 - package.json | 3 +- src/index.ts | 1 - test-e2e/docker.ts | 200 +++++++++++++------------- test-e2e/setup.ts | 37 +++-- test-e2e/tests/offline-probes.test.ts | 10 +- test-e2e/utils.ts | 7 +- test/setup.ts | 3 + 8 files changed, 144 insertions(+), 118 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bad45c79..a95c2e45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,6 @@ jobs: npm run test:portman - name: Test E2E run: | - docker pull ghcr.io/jsdelivr/globalping-probe npm run test:e2e - name: Test Dist run: | diff --git a/package.json b/package.json index a51794b6..0e9f75df 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,8 @@ "test:portman": "TEST_DONT_RESTART_WORKERS=1 start-test 'npm run start:test' http://localhost:3000/health 'npm run test:portman:create && npm run test:portman:run' 2> /dev/null || E=$?; rm -rf tmp; exit $E;", "test:portman:create": "mkdir -p tmp && redocly bundle public/v1/spec.yaml > tmp/spec.yaml && portman --cliOptionsFile test/tests/contract/portman-cli.json", "test:portman:run": "newman run tmp/converted/globalpingApi.json -e test/tests/contract/newman-env.json --ignore-redirects", - "test:e2e": "docker build -t globalping-api-e2e . && npm run test:e2e:cases", + "test:e2e": "npm run test:e2e:docker && npm run test:e2e:cases", + "test:e2e:docker": "docker build -t globalping-api-e2e . && docker pull ghcr.io/jsdelivr/globalping-probe", "test:e2e:cases": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha --config .mocharc.e2e.json" }, "lint-staged": { diff --git a/src/index.ts b/src/index.ts index 6031cae6..0d38177a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,6 @@ import { flushRedisCache } from './lib/flush-redis-cache.js'; const logger = scopedLogger('index'); const port = process.env['PORT'] ?? config.get('server.port'); const workerCount = config.get('server.processes'); -console.log('process.env[\'NODE_ENV\']', process.env['NODE_ENV']); const workerFn = async () => { const server = await createServer(); diff --git a/test-e2e/docker.ts b/test-e2e/docker.ts index b3cb3bb7..8e84a208 100644 --- a/test-e2e/docker.ts +++ b/test-e2e/docker.ts @@ -1,126 +1,126 @@ import Docker from 'dockerode'; +import { scopedLogger } from '../src/lib/logger.js'; -const isLinuxHost = async (docker: Docker) => { - const versionInfo = await docker.version(); - const platformName = versionInfo.Platform.Name.toLowerCase(); - console.log('platformName', platformName); - const isLinuxHost = platformName.includes('engine'); - return isLinuxHost; -}; - -const attachLogs = async (container: Docker.Container) => { - const stream = await container.logs({ - follow: true, - stdout: true, - stderr: true, - }); - container.modem.demuxStream(stream, process.stdout, process.stderr); -}; - -export const createApiContainer = async () => { - const docker = new Docker(); - - const isLinux = await isLinuxHost(docker); - console.log('isLinux', isLinux); - - const container = await docker.createContainer({ - Image: 'globalping-api-e2e', - name: 'globalping-api-e2e', - Env: [ - 'NODE_ENV=test', - 'TEST_MODE=e2e', - 'NEW_RELIC_ENABLED=false', - `REDIS_URL=redis://${isLinux ? 'localhost' : 'host.docker.internal'}:6379`, - `DB_CONNECTION_HOST=${isLinux ? 'localhost' : 'host.docker.internal'}`, - ], - HostConfig: { - PortBindings: { - '80/tcp': [{ HostPort: '80' }], +const logger = scopedLogger('docker-manager'); + +class DockerManager { + docker: Docker; + + constructor () { + this.docker = new Docker(); + } + + public async createApiContainer () { + const isLinux = await this.isLinuxHost(); + const container = await this.docker.createContainer({ + Image: 'globalping-api-e2e', + name: 'globalping-api-e2e', + Env: [ + 'NODE_ENV=test', + 'TEST_MODE=e2e', + 'NEW_RELIC_ENABLED=false', + `REDIS_URL=redis://${isLinux ? 'localhost' : 'host.docker.internal'}:6379`, + `DB_CONNECTION_HOST=${isLinux ? 'localhost' : 'host.docker.internal'}`, + ], + HostConfig: { + PortBindings: { + '80/tcp': [{ HostPort: '80' }], + }, + NetworkMode: isLinux ? 'host' : 'bridge', }, - NetworkMode: isLinux ? 'host' : 'bridge', - }, - }); - - await container.start({}); - - await attachLogs(container); -}; - -export const createProbeContainer = async () => { - const docker = new Docker(); - - const isLinux = await isLinuxHost(docker); - console.log('isLinux', isLinux); - - const container = await docker.createContainer({ - Image: 'ghcr.io/jsdelivr/globalping-probe', - name: 'globalping-probe-e2e', - Env: [ - `API_HOST=ws://${isLinux ? 'localhost' : 'host.docker.internal'}:80`, - ], - HostConfig: { - LogConfig: { - Type: 'local', - Config: {}, + }); + + await container.start({}); + await this.attachLogs(container); + } + + public async createProbeContainer () { + const isLinux = await this.isLinuxHost(); + const container = await this.docker.createContainer({ + Image: 'ghcr.io/jsdelivr/globalping-probe', + name: 'globalping-probe-e2e', + Env: [ + `API_HOST=ws://${isLinux ? 'localhost' : 'host.docker.internal'}:80`, + ], + HostConfig: { + LogConfig: { + Type: 'local', + Config: {}, + }, + NetworkMode: isLinux ? 'host' : 'bridge', }, - NetworkMode: isLinux ? 'host' : 'bridge', - }, - }); + }); - await container.start({}); + await container.start({}); + await this.attachLogs(container); + } - await attachLogs(container); -}; + public async removeProbeContainer () { + const { container } = await this.getContainer('globalping-probe-e2e'); -const getContainer = async (name: string) => { - const docker = new Docker(); - const containers = await docker.listContainers({ all: true }); - const containerInfo = containers.find(c => c.Names.includes(`/${name}`)); + if (!container) { + return; + } - if (!containerInfo) { - console.log('Container not found:'); - return { container: null, state: null }; + await container.remove({ force: true }); } - return { container: docker.getContainer(containerInfo.Id), state: containerInfo.State }; -}; + public async removeApiContainer () { + const { container } = await this.getContainer('globalping-api-e2e'); -export const removeProbeContainer = async () => { - const { container } = await getContainer('globalping-probe-e2e'); + if (!container) { + return; + } - if (!container) { - return; + await container.remove({ force: true }); } - await container.remove({ force: true }); -}; + public async stopProbeContainer () { + const { container, state } = await this.getContainer('globalping-probe-e2e'); -export const removeApiContainer = async () => { - const { container } = await getContainer('globalping-api-e2e'); + if (!container || state === 'exited') { + return; + } - if (!container) { - return; + await container.stop(); } - await container.remove({ force: true }); -}; + public async startProbeContainer () { + const { container, state } = await this.getContainer('globalping-probe-e2e'); + + if (!container || state === 'running') { + return; + } -export const stopProbeContainer = async () => { - const { container, state } = await getContainer('globalping-probe-e2e'); + await container.start({}); + } + + private async isLinuxHost (): Promise { + const versionInfo = await this.docker.version(); + const platformName = versionInfo.Platform.Name.toLowerCase(); + return platformName.includes('engine'); + } - if (!container || state === 'exited') { - return; + private async attachLogs (container: Docker.Container) { + const stream = await container.logs({ + follow: true, + stdout: true, + stderr: true, + }); + container.modem.demuxStream(stream, process.stdout, process.stderr); } - await container.stop(); -}; + private async getContainer (name: string): Promise<{ container: Docker.Container | null, state: string | null }> { + const containers = await this.docker.listContainers({ all: true }); + const containerInfo = containers.find(c => c.Names.includes(`/${name}`)); -export const startProbeContainer = async () => { - const { container, state } = await getContainer('globalping-probe-e2e'); + if (!containerInfo) { + logger.warn('Container not found:'); + return { container: null, state: null }; + } - if (!container || state === 'running') { - return; + return { container: this.docker.getContainer(containerInfo.Id), state: containerInfo.State }; } +} - await container.start({}); -}; +export const docker = new DockerManager(); diff --git a/test-e2e/setup.ts b/test-e2e/setup.ts index 63baeb16..aff0ce6d 100644 --- a/test-e2e/setup.ts +++ b/test-e2e/setup.ts @@ -1,25 +1,46 @@ import path from 'node:path'; +import Bluebird from 'bluebird'; import { fileURLToPath } from 'node:url'; import chai from 'chai'; +import type { Knex } from 'knex'; import chaiOas from '../test/plugins/oas/index.js'; -import { removeProbeContainer, createProbeContainer, removeApiContainer, createApiContainer } from './docker.js'; +import { docker } from './docker.js'; import { waitProbeToConnect } from './utils.js'; +import { client as sql } from '../src/lib/sql/client.js'; +import { initRedisClient } from '../src/lib/redis/client.js'; +import { initPersistentRedisClient } from '../src/lib/redis/persistent-client.js'; +import { initMeasurementRedisClient } from '../src/lib/redis/measurement-client.js'; before(async () => { chai.use(await chaiOas({ specPath: path.join(fileURLToPath(new URL('.', import.meta.url)), '../public/v1/spec.yaml') })); - await removeApiContainer(); - await createApiContainer(); + await Promise.all([ docker.removeApiContainer(), docker.removeProbeContainer() ]); - await removeProbeContainer(); - await createProbeContainer(); + const redisClient = await initRedisClient(); + await redisClient.flushDb(); + const persistentRedisClient = await initPersistentRedisClient(); + await persistentRedisClient.flushDb(); + const measurementRedisClient = await initMeasurementRedisClient(); + await measurementRedisClient.flushDb(); + + await dropAllTables(sql); + await sql.migrate.latest(); + await sql.seed.run(); + + await Promise.all([ docker.createApiContainer(), docker.createProbeContainer() ]); await waitProbeToConnect(); }); after(async () => { - await removeProbeContainer(); - - await removeApiContainer(); + await Promise.all([ docker.removeApiContainer(), docker.removeProbeContainer() ]); }); + +const dropAllTables = async (sql: Knex) => { + const allTables = (await sql('information_schema.tables') + .whereRaw(`table_schema = database()`) + .select(`table_name as table`) + ).map(({ table }: { table: string }) => table); + await Bluebird.map(allTables, table => sql.schema.raw(`drop table \`${table}\``)); +}; diff --git a/test-e2e/tests/offline-probes.test.ts b/test-e2e/tests/offline-probes.test.ts index 7cf1b469..0118d788 100644 --- a/test-e2e/tests/offline-probes.test.ts +++ b/test-e2e/tests/offline-probes.test.ts @@ -2,16 +2,16 @@ import got from 'got'; import { expect } from 'chai'; import { waitMesurementFinish, waitProbeToConnect } from '../utils.js'; -import { startProbeContainer, stopProbeContainer } from '../docker.js'; +import { docker } from '../docker.js'; describe('api', () => { beforeEach(async () => { - await startProbeContainer(); + await docker.startProbeContainer(); await waitProbeToConnect(); }); after(async () => { - await startProbeContainer(); + await docker.startProbeContainer(); await waitProbeToConnect(); }); @@ -21,7 +21,7 @@ describe('api', () => { type: 'ping', } }).json(); - await stopProbeContainer(); + await docker.stopProbeContainer(); const { id } = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', @@ -42,7 +42,7 @@ describe('api', () => { type: 'ping', } }).json(); - await stopProbeContainer(); + await docker.stopProbeContainer(); const { response, body } = await waitMesurementFinish(id); diff --git a/test-e2e/utils.ts b/test-e2e/utils.ts index d2bf5977..4e34ba2d 100644 --- a/test-e2e/utils.ts +++ b/test-e2e/utils.ts @@ -1,8 +1,11 @@ import got, { type RequestError } from 'got'; import { setTimeout } from 'timers/promises'; +import { scopedLogger } from '../src/lib/logger.js'; import type { Probe } from '../src/probe/types.js'; +const logger = scopedLogger('e2e-utils'); + export const waitProbeToConnect = async () => { let response; @@ -10,7 +13,7 @@ export const waitProbeToConnect = async () => { try { response = await got('http://localhost:80/v1/probes'); } catch (err) { - console.log((err as RequestError).code); + logger.info((err as RequestError).code); await setTimeout(1000); continue; } @@ -32,7 +35,7 @@ export const waitProbeInCity = async (city: string) => { try { response = await got('http://localhost:80/v1/probes'); } catch (err) { - console.log((err as RequestError).code); + logger.info((err as RequestError).code); throw err; } diff --git a/test/setup.ts b/test/setup.ts index 6fec5ae3..e9090ee8 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -16,6 +16,7 @@ import { import chaiOas from './plugins/oas/index.js'; import { initRedisClient } from '../src/lib/redis/client.js'; import { initPersistentRedisClient } from '../src/lib/redis/persistent-client.js'; +import { initMeasurementRedisClient } from '../src/lib/redis/measurement-client.js'; import { client as sql } from '../src/lib/sql/client.js'; const dbConfig = config.get<{ connection: { database: string, host: string } }>('db'); @@ -31,6 +32,8 @@ before(async () => { await redisClient.flushDb(); const persistentRedisClient = await initPersistentRedisClient(); await persistentRedisClient.flushDb(); + const measurementRedisClient = await initMeasurementRedisClient(); + await measurementRedisClient.flushDb(); await dropAllTables(sql); await sql.migrate.latest(); From f87c313709c58b0ad1aa9a74f703882ab52d166c Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Mon, 11 Mar 2024 14:40:08 +0100 Subject: [PATCH 30/43] fix: e2e wallaby --- .mocharc.e2e.json | 3 ++- package.json | 1 + test-e2e/docker.ts | 6 ++---- test-e2e/{setup.ts => setup-api.ts} | 15 +++------------ test-e2e/setup-probe.ts | 20 ++++++++++++++++++++ wallaby.e2e.js | 4 ++-- 6 files changed, 30 insertions(+), 19 deletions(-) rename test-e2e/{setup.ts => setup-api.ts} (65%) create mode 100644 test-e2e/setup-probe.ts diff --git a/.mocharc.e2e.json b/.mocharc.e2e.json index 6be452d1..d2db7ec2 100644 --- a/.mocharc.e2e.json +++ b/.mocharc.e2e.json @@ -3,7 +3,8 @@ "timeout": 20000, "check-leaks": true, "file": [ - "test-e2e/setup.ts" + "test-e2e/setup-api.ts", + "test-e2e/setup-probe.ts" ], "spec": [ "test-e2e/**/*.test.ts" diff --git a/package.json b/package.json index 0e9f75df..ed6c3db3 100644 --- a/package.json +++ b/package.json @@ -137,6 +137,7 @@ "test:portman:create": "mkdir -p tmp && redocly bundle public/v1/spec.yaml > tmp/spec.yaml && portman --cliOptionsFile test/tests/contract/portman-cli.json", "test:portman:run": "newman run tmp/converted/globalpingApi.json -e test/tests/contract/newman-env.json --ignore-redirects", "test:e2e": "npm run test:e2e:docker && npm run test:e2e:cases", + "test:e2e:api": "NODE_ENV=test knex migrate:latest && PORT=80 NODE_ENV=test TEST_MODE=e2e NEW_RELIC_ENABLED=false tsx src/index.ts", "test:e2e:docker": "docker build -t globalping-api-e2e . && docker pull ghcr.io/jsdelivr/globalping-probe", "test:e2e:cases": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha --config .mocharc.e2e.json" }, diff --git a/test-e2e/docker.ts b/test-e2e/docker.ts index 8e84a208..72f6b192 100644 --- a/test-e2e/docker.ts +++ b/test-e2e/docker.ts @@ -12,6 +12,7 @@ class DockerManager { public async createApiContainer () { const isLinux = await this.isLinuxHost(); + // docker run -e NODE_ENV=test -e TEST_MODE=e2e -e NEW_RELIC_ENABLED=false -e REDIS_URL=redis://host.docker.internal:6379 -e DB_CONNECTION_HOST=host.docker.internal --name globalping-api-e2e globalping-api-e2e const container = await this.docker.createContainer({ Image: 'globalping-api-e2e', name: 'globalping-api-e2e', @@ -36,6 +37,7 @@ class DockerManager { public async createProbeContainer () { const isLinux = await this.isLinuxHost(); + // docker run -e API_HOST=ws://host.docker.internal:80 --name globalping-probe-e2e ghcr.io/jsdelivr/globalping-probe const container = await this.docker.createContainer({ Image: 'ghcr.io/jsdelivr/globalping-probe', name: 'globalping-probe-e2e', @@ -43,10 +45,6 @@ class DockerManager { `API_HOST=ws://${isLinux ? 'localhost' : 'host.docker.internal'}:80`, ], HostConfig: { - LogConfig: { - Type: 'local', - Config: {}, - }, NetworkMode: isLinux ? 'host' : 'bridge', }, }); diff --git a/test-e2e/setup.ts b/test-e2e/setup-api.ts similarity index 65% rename from test-e2e/setup.ts rename to test-e2e/setup-api.ts index aff0ce6d..e6a6a8c5 100644 --- a/test-e2e/setup.ts +++ b/test-e2e/setup-api.ts @@ -1,21 +1,14 @@ -import path from 'node:path'; import Bluebird from 'bluebird'; -import { fileURLToPath } from 'node:url'; -import chai from 'chai'; import type { Knex } from 'knex'; -import chaiOas from '../test/plugins/oas/index.js'; import { docker } from './docker.js'; -import { waitProbeToConnect } from './utils.js'; import { client as sql } from '../src/lib/sql/client.js'; import { initRedisClient } from '../src/lib/redis/client.js'; import { initPersistentRedisClient } from '../src/lib/redis/persistent-client.js'; import { initMeasurementRedisClient } from '../src/lib/redis/measurement-client.js'; before(async () => { - chai.use(await chaiOas({ specPath: path.join(fileURLToPath(new URL('.', import.meta.url)), '../public/v1/spec.yaml') })); - - await Promise.all([ docker.removeApiContainer(), docker.removeProbeContainer() ]); + docker.removeApiContainer(); const redisClient = await initRedisClient(); await redisClient.flushDb(); @@ -24,17 +17,15 @@ before(async () => { const measurementRedisClient = await initMeasurementRedisClient(); await measurementRedisClient.flushDb(); - await dropAllTables(sql); await sql.migrate.latest(); await sql.seed.run(); - await Promise.all([ docker.createApiContainer(), docker.createProbeContainer() ]); - await waitProbeToConnect(); + await docker.createApiContainer(); }); after(async () => { - await Promise.all([ docker.removeApiContainer(), docker.removeProbeContainer() ]); + docker.removeApiContainer(); }); const dropAllTables = async (sql: Knex) => { diff --git a/test-e2e/setup-probe.ts b/test-e2e/setup-probe.ts new file mode 100644 index 00000000..1913341b --- /dev/null +++ b/test-e2e/setup-probe.ts @@ -0,0 +1,20 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import chai from 'chai'; + +import chaiOas from '../test/plugins/oas/index.js'; +import { docker } from './docker.js'; +import { waitProbeToConnect } from './utils.js'; + +before(async () => { + chai.use(await chaiOas({ specPath: path.join(fileURLToPath(new URL('.', import.meta.url)), '../public/v1/spec.yaml') })); + + await docker.removeProbeContainer(); + + await docker.createProbeContainer(); + await waitProbeToConnect(); +}); + +after(async () => { + await docker.removeProbeContainer(); +}); diff --git a/wallaby.e2e.js b/wallaby.e2e.js index 3a5a29d9..f17a900c 100644 --- a/wallaby.e2e.js +++ b/wallaby.e2e.js @@ -6,7 +6,7 @@ export default function wallaby () { 'public/v1/*', 'public/**/*.yaml', 'test/plugins/**/*', - 'test-e2e/setup.ts', + 'test-e2e/setup-probe.ts', 'test-e2e/utils.ts', 'test-e2e/docker.ts', 'src/**/*.ts', @@ -19,7 +19,7 @@ export default function wallaby () { setup (w) { const path = require('path'); - w.testFramework.addFile(path.resolve(process.cwd(), 'test-e2e/setup.js')); + w.testFramework.addFile(path.resolve(process.cwd(), 'test-e2e/setup-probe.js')); w.testFramework.timeout(20000); }, From d7cd98057e05fb7fe7c6f8cdd4fb9d19cfb32edc Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Mon, 11 Mar 2024 14:54:45 +0100 Subject: [PATCH 31/43] fix: schema hostname format --- public/v1/components/schemas.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/public/v1/components/schemas.yaml b/public/v1/components/schemas.yaml index 3b6dc2d6..186c9dea 100644 --- a/public/v1/components/schemas.yaml +++ b/public/v1/components/schemas.yaml @@ -690,7 +690,6 @@ components: type: - string - 'null' - format: hostname description: The resolved hostname of the `target`. StateCode: type: From 61861fec175b35996caa3edb5b018e95d79d1fae Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Mon, 11 Mar 2024 15:16:30 +0100 Subject: [PATCH 32/43] fix: remove unused env var --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index ed6c3db3..70092d33 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "stats": "NEW_RELIC_ENABLED=false tsx probes-stats/known.ts", "start": "node --max_old_space_size=3584 --max-semi-space-size=128 --experimental-loader newrelic/esm-loader.mjs -r newrelic dist/src/index.js", "start:dev": "NODE_ENV=development FAKE_PROBE_IP=1 NEW_RELIC_ENABLED=false tsx src/index.ts", - "start:test": "NODE_ENV=test knex migrate:latest && NODE_ENV=test TEST_MODE=e2e NEW_RELIC_ENABLED=false tsx src/index.ts", + "start:test": "NODE_ENV=test knex migrate:latest && NODE_ENV=test NEW_RELIC_ENABLED=false tsx src/index.ts", "lint": "npm run lint:js && npm run lint:types && npm run lint:docs", "lint:fix": "npm run lint:js:fix && npm run lint:types && npm run lint:docs", "lint:docs": "spectral lint public/**/spec.yaml", @@ -131,7 +131,6 @@ "test": "npm run lint && npm run test:mocha && npm run test:portman", "test:dist": "node --test-reporter=spec test/dist.js", "test:mocha": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false mocha", - "test:mocha:dev": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false mocha", "test:perf": "tsx test-perf/index.ts", "test:portman": "TEST_DONT_RESTART_WORKERS=1 start-test 'npm run start:test' http://localhost:3000/health 'npm run test:portman:create && npm run test:portman:run' 2> /dev/null || E=$?; rm -rf tmp; exit $E;", "test:portman:create": "mkdir -p tmp && redocly bundle public/v1/spec.yaml > tmp/spec.yaml && portman --cliOptionsFile test/tests/contract/portman-cli.json", From 906c1c0032f87445e0ab58aa4d543b984c10ca98 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Mon, 11 Mar 2024 17:12:16 +0100 Subject: [PATCH 33/43] fix: add missing awaits --- test-e2e/setup-api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-e2e/setup-api.ts b/test-e2e/setup-api.ts index e6a6a8c5..1f182618 100644 --- a/test-e2e/setup-api.ts +++ b/test-e2e/setup-api.ts @@ -8,7 +8,7 @@ import { initPersistentRedisClient } from '../src/lib/redis/persistent-client.js import { initMeasurementRedisClient } from '../src/lib/redis/measurement-client.js'; before(async () => { - docker.removeApiContainer(); + await docker.removeApiContainer(); const redisClient = await initRedisClient(); await redisClient.flushDb(); @@ -25,7 +25,7 @@ before(async () => { }); after(async () => { - docker.removeApiContainer(); + await docker.removeApiContainer(); }); const dropAllTables = async (sql: Knex) => { From 98098f04d5d333c7956acd447ea98011d08edd82 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Thu, 14 Mar 2024 13:58:52 +0100 Subject: [PATCH 34/43] fix: remove manual json.parse --- package.json | 3 ++- test-e2e/tests/adopted-probes.test.ts | 5 ++--- test-e2e/tests/adoption-code.test.ts | 14 ++++++++------ test-e2e/tests/dns.test.ts | 16 ++++++++-------- test-e2e/tests/http.test.ts | 16 ++++++++-------- test-e2e/tests/mtr.test.ts | 6 +++--- test-e2e/tests/offline-probes.test.ts | 14 +++++++------- test-e2e/tests/ping.test.ts | 6 +++--- test-e2e/tests/probes.test.ts | 5 ++--- test-e2e/tests/traceroute.test.ts | 6 +++--- test-e2e/utils.ts | 21 +++++++-------------- test/plugins/oas/index.js | 4 ---- 12 files changed, 53 insertions(+), 63 deletions(-) diff --git a/package.json b/package.json index 70092d33..d8946dfd 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,8 @@ "lint:types": "tsc --noEmit", "test": "npm run lint && npm run test:mocha && npm run test:portman", "test:dist": "node --test-reporter=spec test/dist.js", - "test:mocha": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false mocha", + "test:mocha": "NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false mocha", + "test:mocha:dev": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test TEST_MODE=unit NEW_RELIC_ENABLED=false mocha", "test:perf": "tsx test-perf/index.ts", "test:portman": "TEST_DONT_RESTART_WORKERS=1 start-test 'npm run start:test' http://localhost:3000/health 'npm run test:portman:create && npm run test:portman:run' 2> /dev/null || E=$?; rm -rf tmp; exit $E;", "test:portman:create": "mkdir -p tmp && redocly bundle public/v1/spec.yaml > tmp/spec.yaml && portman --cliOptionsFile test/tests/contract/portman-cli.json", diff --git a/test-e2e/tests/adopted-probes.test.ts b/test-e2e/tests/adopted-probes.test.ts index f111bc58..1da48799 100644 --- a/test-e2e/tests/adopted-probes.test.ts +++ b/test-e2e/tests/adopted-probes.test.ts @@ -3,7 +3,6 @@ import { expect } from 'chai'; import { client } from '../../src/lib/sql/client.js'; import { ADOPTED_PROBES_TABLE } from '../../src/lib/adopted-probes.js'; import { waitProbeInCity } from '../utils.js'; -import type { Probe } from '../../src/probe/types.js'; describe('adopted probe', () => { before(async function () { @@ -38,9 +37,9 @@ describe('adopted probe', () => { }); it('should return probe list with updated city', async () => { - const probes = await got('http://localhost:80/v1/probes').json(); + const probes = await got('http://localhost:80/v1/probes').json(); - expect(probes[0]!.location.city).to.equal('Marseille'); + expect(probes[0].location.city).to.equal('Marseille'); }); it('should create measurement by its new location', async () => { diff --git a/test-e2e/tests/adoption-code.test.ts b/test-e2e/tests/adoption-code.test.ts index e9b3370b..8c2bdf49 100644 --- a/test-e2e/tests/adoption-code.test.ts +++ b/test-e2e/tests/adoption-code.test.ts @@ -3,15 +3,17 @@ import { expect } from 'chai'; describe('/adoption-code endpoint', () => { it('should send code to the probe', async () => { - const response = await got.post('http://localhost:80/v1/adoption-code?systemkey=system', { json: { - ip: '51.158.22.211', - code: '123456', - } }); - const body = JSON.parse(response.body); + const response = await got.post('http://localhost:80/v1/adoption-code?systemkey=system', { + json: { + ip: '51.158.22.211', + code: '123456', + }, + responseType: 'json', + }); expect(response.statusCode).to.equal(200); - expect(body).to.deep.include({ + expect(response.body).to.deep.include({ city: 'Paris', country: 'FR', hardwareDevice: null, diff --git a/test-e2e/tests/dns.test.ts b/test-e2e/tests/dns.test.ts index 95e4887c..ea733701 100644 --- a/test-e2e/tests/dns.test.ts +++ b/test-e2e/tests/dns.test.ts @@ -9,11 +9,11 @@ describe('dns mesurement', () => { type: 'dns', } }).json(); - const { response, body } = await waitMesurementFinish(id); + const response = await waitMesurementFinish(id); - expect(body.status).to.equal('finished'); - expect(body.results[0].result.status).to.equal('finished'); - expect(body.results[0].result.hops).to.not.exist; + expect(response.body.status).to.equal('finished'); + expect(response.body.results[0].result.status).to.equal('finished'); + expect(response.body.results[0].result.hops).to.not.exist; expect(response).to.matchApiSchema(); }); @@ -26,11 +26,11 @@ describe('dns mesurement', () => { }, } }).json(); - const { response, body } = await waitMesurementFinish(id); + const response = await waitMesurementFinish(id); - expect(body.status).to.equal('finished'); - expect(body.results[0].result.status).to.equal('finished'); - expect(body.results[0].result.hops.length > 0).to.be.true; + expect(response.body.status).to.equal('finished'); + expect(response.body.results[0].result.status).to.equal('finished'); + expect(response.body.results[0].result.hops.length > 0).to.be.true; expect(response).to.matchApiSchema(); }); }); diff --git a/test-e2e/tests/http.test.ts b/test-e2e/tests/http.test.ts index efb87d08..1954f933 100644 --- a/test-e2e/tests/http.test.ts +++ b/test-e2e/tests/http.test.ts @@ -9,11 +9,11 @@ describe('http mesurement', () => { type: 'http', } }).json(); - const { response, body } = await waitMesurementFinish(id); + const response = await waitMesurementFinish(id); - expect(body.status).to.equal('finished'); - expect(body.results[0].result.status).to.equal('finished'); - expect(body.results[0].result.rawBody).to.equal(null); + expect(response.body.status).to.equal('finished'); + expect(response.body.results[0].result.status).to.equal('finished'); + expect(response.body.results[0].result.rawBody).to.equal(null); expect(response).to.matchApiSchema(); }); @@ -28,11 +28,11 @@ describe('http mesurement', () => { }, } }).json(); - const { response, body } = await waitMesurementFinish(id); + const response = await waitMesurementFinish(id); - expect(body.status).to.equal('finished'); - expect(body.results[0].result.status).to.equal('finished'); - expect(body.results[0].result.rawBody.length > 0).to.be.true; + expect(response.body.status).to.equal('finished'); + expect(response.body.results[0].result.status).to.equal('finished'); + expect(response.body.results[0].result.rawBody.length > 0).to.be.true; expect(response).to.matchApiSchema(); }); }); diff --git a/test-e2e/tests/mtr.test.ts b/test-e2e/tests/mtr.test.ts index da85b315..156afba2 100644 --- a/test-e2e/tests/mtr.test.ts +++ b/test-e2e/tests/mtr.test.ts @@ -9,10 +9,10 @@ describe('mtr mesurement', () => { type: 'mtr', } }).json(); - const { response, body } = await waitMesurementFinish(id); + const response = await waitMesurementFinish(id); - expect(body.status).to.equal('finished'); - expect(body.results[0].result.status).to.equal('finished'); + expect(response.body.status).to.equal('finished'); + expect(response.body.results[0].result.status).to.equal('finished'); expect(response).to.matchApiSchema(); }); }); diff --git a/test-e2e/tests/offline-probes.test.ts b/test-e2e/tests/offline-probes.test.ts index 0118d788..ebb7ad47 100644 --- a/test-e2e/tests/offline-probes.test.ts +++ b/test-e2e/tests/offline-probes.test.ts @@ -29,10 +29,10 @@ describe('api', () => { locations: locationId, } }).json(); - const { response, body } = await waitMesurementFinish(id); + const response = await waitMesurementFinish(id); - expect(body.status).to.equal('finished'); - expect(body.results[0].result.status).to.equal('offline'); + expect(response.body.status).to.equal('finished'); + expect(response.body.results[0].result.status).to.equal('offline'); expect(response).to.matchApiSchema(); }); @@ -44,11 +44,11 @@ describe('api', () => { await docker.stopProbeContainer(); - const { response, body } = await waitMesurementFinish(id); + const response = await waitMesurementFinish(id); - expect(body.status).to.equal('finished'); - expect(body.results[0].result.status).to.equal('failed'); - expect(body.results[0].result.rawOutput).to.equal('\n\nThe measurement timed out'); + expect(response.body.status).to.equal('finished'); + expect(response.body.results[0].result.status).to.equal('failed'); + expect(response.body.results[0].result.rawOutput).to.equal('\n\nThe measurement timed out'); expect(response).to.matchApiSchema(); }).timeout(40000); }); diff --git a/test-e2e/tests/ping.test.ts b/test-e2e/tests/ping.test.ts index 612fec1c..9e075458 100644 --- a/test-e2e/tests/ping.test.ts +++ b/test-e2e/tests/ping.test.ts @@ -9,10 +9,10 @@ describe('ping mesurement', () => { type: 'ping', } }).json(); - const { response, body } = await waitMesurementFinish(id); + const response = await waitMesurementFinish(id); - expect(body.status).to.equal('finished'); - expect(body.results[0].result.status).to.equal('finished'); + expect(response.body.status).to.equal('finished'); + expect(response.body.results[0].result.status).to.equal('finished'); expect(response).to.matchApiSchema(); }); }); diff --git a/test-e2e/tests/probes.test.ts b/test-e2e/tests/probes.test.ts index 1a70811c..4a498a49 100644 --- a/test-e2e/tests/probes.test.ts +++ b/test-e2e/tests/probes.test.ts @@ -3,11 +3,10 @@ import { expect } from 'chai'; describe('/probes endpoint', () => { it('should return an array of probes', async () => { - const response = await got('http://localhost:80/v1/probes'); - const probes = JSON.parse(response.body); + const response = await got('http://localhost:80/v1/probes', { responseType: 'json' }); expect(response.statusCode).to.equal(200); - expect(probes.length).to.equal(1); + expect(response.body.length).to.equal(1); expect(response).to.matchApiSchema(); }); }); diff --git a/test-e2e/tests/traceroute.test.ts b/test-e2e/tests/traceroute.test.ts index d1db8f18..6f602e51 100644 --- a/test-e2e/tests/traceroute.test.ts +++ b/test-e2e/tests/traceroute.test.ts @@ -9,10 +9,10 @@ describe('traceroute mesurement', () => { type: 'traceroute', } }).json(); - const { response, body } = await waitMesurementFinish(id); + const response = await waitMesurementFinish(id); - expect(body.status).to.equal('finished'); - expect(body.results[0].result.status).to.equal('finished'); + expect(response.body.status).to.equal('finished'); + expect(response.body.results[0].result.status).to.equal('finished'); expect(response).to.matchApiSchema(); }); }); diff --git a/test-e2e/utils.ts b/test-e2e/utils.ts index 4e34ba2d..fb07ec94 100644 --- a/test-e2e/utils.ts +++ b/test-e2e/utils.ts @@ -2,8 +2,6 @@ import got, { type RequestError } from 'got'; import { setTimeout } from 'timers/promises'; import { scopedLogger } from '../src/lib/logger.js'; -import type { Probe } from '../src/probe/types.js'; - const logger = scopedLogger('e2e-utils'); export const waitProbeToConnect = async () => { @@ -11,16 +9,14 @@ export const waitProbeToConnect = async () => { for (;;) { try { - response = await got('http://localhost:80/v1/probes'); + response = await got('http://localhost:80/v1/probes', { responseType: 'json' }); } catch (err) { logger.info((err as RequestError).code); await setTimeout(1000); continue; } - const probes = JSON.parse(response.body) as Probe[]; - - if (probes.length > 0) { + if (response.body.length > 0) { return; } @@ -33,15 +29,13 @@ export const waitProbeInCity = async (city: string) => { for (;;) { try { - response = await got('http://localhost:80/v1/probes'); + response = await got('http://localhost:80/v1/probes', { responseType: 'json' }); } catch (err) { logger.info((err as RequestError).code); throw err; } - const probes = JSON.parse(response.body) as Probe[]; - - if (probes.length > 0 && probes[0]!.location.city === city) { + if (response.body.length > 0 && response.body[0].location.city === city) { return; } @@ -51,11 +45,10 @@ export const waitProbeInCity = async (city: string) => { export const waitMesurementFinish = async (id: string) => { for (;;) { - const response = await got(`http://localhost:80/v1/measurements/${id}`); - const body = JSON.parse(response.body); + const response = await got(`http://localhost:80/v1/measurements/${id}`, { responseType: 'json' }); - if (body.status !== 'in-progress') { - return { response, body }; + if (response.body.status !== 'in-progress') { + return response; } await setTimeout(500); diff --git a/test/plugins/oas/index.js b/test/plugins/oas/index.js index 56085f25..2d51c665 100644 --- a/test/plugins/oas/index.js +++ b/test/plugins/oas/index.js @@ -64,10 +64,6 @@ export default async ({ specPath, ajvBodyOptions = {}, ajvHeadersOptions = {} }) chai.Assertion.addMethod('matchApiSchema', function () { let response = this._obj; - if (typeof response.body === 'string') { - response.body = JSON.parse(response.body); - } - if (!response.type) { response.type = response.headers['content-type'] === 'application/json; charset=utf-8' ? 'application/json' : response.headers['content-type']; } From 9f5777b91fc8205b283725a61de13136e0aeaa07 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Thu, 14 Mar 2024 15:02:41 +0100 Subject: [PATCH 35/43] fix: log measurement status --- test-e2e/tests/offline-probes.test.ts | 3 +++ test-e2e/utils.ts | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/test-e2e/tests/offline-probes.test.ts b/test-e2e/tests/offline-probes.test.ts index ebb7ad47..97251a13 100644 --- a/test-e2e/tests/offline-probes.test.ts +++ b/test-e2e/tests/offline-probes.test.ts @@ -29,8 +29,11 @@ describe('api', () => { locations: locationId, } }).json(); + console.log('measurement id:', id, new Date()); const response = await waitMesurementFinish(id); + console.log(1, new Date()); + console.log('response.body', response.body, new Date()); expect(response.body.status).to.equal('finished'); expect(response.body.results[0].result.status).to.equal('offline'); expect(response).to.matchApiSchema(); diff --git a/test-e2e/utils.ts b/test-e2e/utils.ts index fb07ec94..37e98573 100644 --- a/test-e2e/utils.ts +++ b/test-e2e/utils.ts @@ -47,7 +47,11 @@ export const waitMesurementFinish = async (id: string) => { for (;;) { const response = await got(`http://localhost:80/v1/measurements/${id}`, { responseType: 'json' }); + console.log('id', id, new Date()); + console.log('response.body.status', response.body.status, new Date()); + if (response.body.status !== 'in-progress') { + logger.info('return'); return response; } From a31071d07d626260f57446d1004654b8bf533dd0 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Thu, 14 Mar 2024 16:24:32 +0100 Subject: [PATCH 36/43] feat: disable rtt null --- public/v1/components/schemas.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/public/v1/components/schemas.yaml b/public/v1/components/schemas.yaml index 186c9dea..2cfd9067 100644 --- a/public/v1/components/schemas.yaml +++ b/public/v1/components/schemas.yaml @@ -1194,9 +1194,7 @@ components: - $ref: 'schemas.yaml#/components/schemas/BaseTestStatus' - const: offline TimingPacketRtt: - type: - - number - - 'null' + type: number description: The round-trip time for this packet. TimingPacketTtl: type: number From f946baecf9b6b0e0340b0de18c88f5f2de390478 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Thu, 14 Mar 2024 16:46:12 +0100 Subject: [PATCH 37/43] fix: add wait for disconnect --- test-e2e/tests/mtr.test.ts | 3 +++ test-e2e/tests/offline-probes.test.ts | 6 ++---- test-e2e/utils.ts | 23 ++++++++++++++++++++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/test-e2e/tests/mtr.test.ts b/test-e2e/tests/mtr.test.ts index 156afba2..5a7e7144 100644 --- a/test-e2e/tests/mtr.test.ts +++ b/test-e2e/tests/mtr.test.ts @@ -10,6 +10,9 @@ describe('mtr mesurement', () => { } }).json(); const response = await waitMesurementFinish(id); + console.log('response'); + console.log(response); + console.log(response.body.results[0].result); expect(response.body.status).to.equal('finished'); expect(response.body.results[0].result.status).to.equal('finished'); diff --git a/test-e2e/tests/offline-probes.test.ts b/test-e2e/tests/offline-probes.test.ts index 97251a13..8fa9fd89 100644 --- a/test-e2e/tests/offline-probes.test.ts +++ b/test-e2e/tests/offline-probes.test.ts @@ -1,7 +1,7 @@ import got from 'got'; import { expect } from 'chai'; -import { waitMesurementFinish, waitProbeToConnect } from '../utils.js'; +import { waitMesurementFinish, waitProbeToConnect, waitProbeToDisconnect } from '../utils.js'; import { docker } from '../docker.js'; describe('api', () => { @@ -22,6 +22,7 @@ describe('api', () => { } }).json(); await docker.stopProbeContainer(); + await waitProbeToDisconnect(); const { id } = await got.post('http://localhost:80/v1/measurements', { json: { target: 'www.jsdelivr.com', @@ -29,11 +30,8 @@ describe('api', () => { locations: locationId, } }).json(); - console.log('measurement id:', id, new Date()); const response = await waitMesurementFinish(id); - console.log(1, new Date()); - console.log('response.body', response.body, new Date()); expect(response.body.status).to.equal('finished'); expect(response.body.results[0].result.status).to.equal('offline'); expect(response).to.matchApiSchema(); diff --git a/test-e2e/utils.ts b/test-e2e/utils.ts index 37e98573..3c8a0a45 100644 --- a/test-e2e/utils.ts +++ b/test-e2e/utils.ts @@ -4,6 +4,26 @@ import { scopedLogger } from '../src/lib/logger.js'; const logger = scopedLogger('e2e-utils'); +export const waitProbeToDisconnect = async () => { + let response; + + for (;;) { + try { + response = await got('http://localhost:80/v1/probes', { responseType: 'json' }); + } catch (err) { + logger.info((err as RequestError).code); + await setTimeout(1000); + continue; + } + + if (response.body.length === 0) { + return; + } + + await setTimeout(1000); + } +}; + export const waitProbeToConnect = async () => { let response; @@ -47,9 +67,6 @@ export const waitMesurementFinish = async (id: string) => { for (;;) { const response = await got(`http://localhost:80/v1/measurements/${id}`, { responseType: 'json' }); - console.log('id', id, new Date()); - console.log('response.body.status', response.body.status, new Date()); - if (response.body.status !== 'in-progress') { logger.info('return'); return response; From 39a7a270d9e03cd7206bfd565f955970193a9a1e Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Thu, 14 Mar 2024 16:55:54 +0100 Subject: [PATCH 38/43] ci: trigger From 1756fea0fbe2fe3b5bec341de755ca7dd6c9e378 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Thu, 14 Mar 2024 17:00:39 +0100 Subject: [PATCH 39/43] fix: retrun rtt null back --- public/v1/components/schemas.yaml | 4 +++- test-e2e/tests/mtr.test.ts | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/public/v1/components/schemas.yaml b/public/v1/components/schemas.yaml index 2cfd9067..186c9dea 100644 --- a/public/v1/components/schemas.yaml +++ b/public/v1/components/schemas.yaml @@ -1194,7 +1194,9 @@ components: - $ref: 'schemas.yaml#/components/schemas/BaseTestStatus' - const: offline TimingPacketRtt: - type: number + type: + - number + - 'null' description: The round-trip time for this packet. TimingPacketTtl: type: number diff --git a/test-e2e/tests/mtr.test.ts b/test-e2e/tests/mtr.test.ts index 5a7e7144..156afba2 100644 --- a/test-e2e/tests/mtr.test.ts +++ b/test-e2e/tests/mtr.test.ts @@ -10,9 +10,6 @@ describe('mtr mesurement', () => { } }).json(); const response = await waitMesurementFinish(id); - console.log('response'); - console.log(response); - console.log(response.body.results[0].result); expect(response.body.status).to.equal('finished'); expect(response.body.results[0].result.status).to.equal('finished'); From e0163babeb2da61671585457c49f4d98880f09a2 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Fri, 15 Mar 2024 12:59:49 +0100 Subject: [PATCH 40/43] refactor: move e2e to test folder --- .eslintrc | 4 +--- .mocharc.e2e.json | 6 +++--- .mocharc.json | 3 ++- .../tests/e2e/cases}/adopted-probes.test.ts | 4 ++-- .../tests/e2e/cases}/adoption-code.test.ts | 0 {test-e2e/tests => test/tests/e2e/cases}/dns.test.ts | 2 +- .../tests => test/tests/e2e/cases}/health.test.ts | 0 {test-e2e/tests => test/tests/e2e/cases}/http.test.ts | 2 +- .../tests => test/tests/e2e/cases}/location.test.ts | 0 {test-e2e/tests => test/tests/e2e/cases}/mtr.test.ts | 0 .../tests/e2e/cases}/offline-probes.test.ts | 0 {test-e2e/tests => test/tests/e2e/cases}/ping.test.ts | 0 .../tests => test/tests/e2e/cases}/probes.test.ts | 0 .../tests => test/tests/e2e/cases}/traceroute.test.ts | 0 {test-e2e => test/tests/e2e}/docker.ts | 2 +- {test-e2e => test/tests/e2e}/setup-api.ts | 8 ++++---- {test-e2e => test/tests/e2e}/setup-probe.ts | 4 ++-- {test-e2e => test/tests/e2e}/utils.ts | 2 +- tsconfig.json | 3 +-- wallaby.e2e.js | 10 +++++----- wallaby.js | 3 ++- 21 files changed, 26 insertions(+), 27 deletions(-) rename {test-e2e/tests => test/tests/e2e/cases}/adopted-probes.test.ts (93%) rename {test-e2e/tests => test/tests/e2e/cases}/adoption-code.test.ts (100%) rename {test-e2e/tests => test/tests/e2e/cases}/dns.test.ts (94%) rename {test-e2e/tests => test/tests/e2e/cases}/health.test.ts (100%) rename {test-e2e/tests => test/tests/e2e/cases}/http.test.ts (93%) rename {test-e2e/tests => test/tests/e2e/cases}/location.test.ts (100%) rename {test-e2e/tests => test/tests/e2e/cases}/mtr.test.ts (100%) rename {test-e2e/tests => test/tests/e2e/cases}/offline-probes.test.ts (100%) rename {test-e2e/tests => test/tests/e2e/cases}/ping.test.ts (100%) rename {test-e2e/tests => test/tests/e2e/cases}/probes.test.ts (100%) rename {test-e2e/tests => test/tests/e2e/cases}/traceroute.test.ts (100%) rename {test-e2e => test/tests/e2e}/docker.ts (98%) rename {test-e2e => test/tests/e2e}/setup-api.ts (75%) rename {test-e2e => test/tests/e2e}/setup-probe.ts (79%) rename {test-e2e => test/tests/e2e}/utils.ts (96%) diff --git a/.eslintrc b/.eslintrc index 0562bdac..3c30eb96 100644 --- a/.eslintrc +++ b/.eslintrc @@ -39,13 +39,11 @@ }, { "files": [ - "test/**", - "test-e2e/**" + "test/**" ], "rules": { "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-non-null-assertion": "off", - "no-unused-expressions": "off", "no-restricted-properties": [ "error", { diff --git a/.mocharc.e2e.json b/.mocharc.e2e.json index d2db7ec2..7dcb7f6a 100644 --- a/.mocharc.e2e.json +++ b/.mocharc.e2e.json @@ -3,11 +3,11 @@ "timeout": 20000, "check-leaks": true, "file": [ - "test-e2e/setup-api.ts", - "test-e2e/setup-probe.ts" + "test/tests/e2e/setup-api.ts", + "test/tests/e2e/setup-probe.ts" ], "spec": [ - "test-e2e/**/*.test.ts" + "test/tests/e2e/**/*.test.ts" ], "node-option": [ "experimental-specifier-resolution=node", diff --git a/.mocharc.json b/.mocharc.json index 97417d54..7a71c324 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -6,7 +6,8 @@ "test/setup.ts" ], "spec": [ - "test/**/*.test.ts" + "test/tests/integration/**/*.test.ts", + "test/tests/unit/**/*.test.ts" ], "node-option": [ "experimental-specifier-resolution=node", diff --git a/test-e2e/tests/adopted-probes.test.ts b/test/tests/e2e/cases/adopted-probes.test.ts similarity index 93% rename from test-e2e/tests/adopted-probes.test.ts rename to test/tests/e2e/cases/adopted-probes.test.ts index 1da48799..7db13786 100644 --- a/test-e2e/tests/adopted-probes.test.ts +++ b/test/tests/e2e/cases/adopted-probes.test.ts @@ -1,7 +1,7 @@ import got from 'got'; import { expect } from 'chai'; -import { client } from '../../src/lib/sql/client.js'; -import { ADOPTED_PROBES_TABLE } from '../../src/lib/adopted-probes.js'; +import { client } from '../../../../src/lib/sql/client.js'; +import { ADOPTED_PROBES_TABLE } from '../../../../src/lib/adopted-probes.js'; import { waitProbeInCity } from '../utils.js'; describe('adopted probe', () => { diff --git a/test-e2e/tests/adoption-code.test.ts b/test/tests/e2e/cases/adoption-code.test.ts similarity index 100% rename from test-e2e/tests/adoption-code.test.ts rename to test/tests/e2e/cases/adoption-code.test.ts diff --git a/test-e2e/tests/dns.test.ts b/test/tests/e2e/cases/dns.test.ts similarity index 94% rename from test-e2e/tests/dns.test.ts rename to test/tests/e2e/cases/dns.test.ts index ea733701..b4dec539 100644 --- a/test-e2e/tests/dns.test.ts +++ b/test/tests/e2e/cases/dns.test.ts @@ -30,7 +30,7 @@ describe('dns mesurement', () => { expect(response.body.status).to.equal('finished'); expect(response.body.results[0].result.status).to.equal('finished'); - expect(response.body.results[0].result.hops.length > 0).to.be.true; + expect(response.body.results[0].result.hops.length).to.be.above(0); expect(response).to.matchApiSchema(); }); }); diff --git a/test-e2e/tests/health.test.ts b/test/tests/e2e/cases/health.test.ts similarity index 100% rename from test-e2e/tests/health.test.ts rename to test/tests/e2e/cases/health.test.ts diff --git a/test-e2e/tests/http.test.ts b/test/tests/e2e/cases/http.test.ts similarity index 93% rename from test-e2e/tests/http.test.ts rename to test/tests/e2e/cases/http.test.ts index 1954f933..33dbe49b 100644 --- a/test-e2e/tests/http.test.ts +++ b/test/tests/e2e/cases/http.test.ts @@ -32,7 +32,7 @@ describe('http mesurement', () => { expect(response.body.status).to.equal('finished'); expect(response.body.results[0].result.status).to.equal('finished'); - expect(response.body.results[0].result.rawBody.length > 0).to.be.true; + expect(response.body.results[0].result.rawBody.length).to.be.above(0); expect(response).to.matchApiSchema(); }); }); diff --git a/test-e2e/tests/location.test.ts b/test/tests/e2e/cases/location.test.ts similarity index 100% rename from test-e2e/tests/location.test.ts rename to test/tests/e2e/cases/location.test.ts diff --git a/test-e2e/tests/mtr.test.ts b/test/tests/e2e/cases/mtr.test.ts similarity index 100% rename from test-e2e/tests/mtr.test.ts rename to test/tests/e2e/cases/mtr.test.ts diff --git a/test-e2e/tests/offline-probes.test.ts b/test/tests/e2e/cases/offline-probes.test.ts similarity index 100% rename from test-e2e/tests/offline-probes.test.ts rename to test/tests/e2e/cases/offline-probes.test.ts diff --git a/test-e2e/tests/ping.test.ts b/test/tests/e2e/cases/ping.test.ts similarity index 100% rename from test-e2e/tests/ping.test.ts rename to test/tests/e2e/cases/ping.test.ts diff --git a/test-e2e/tests/probes.test.ts b/test/tests/e2e/cases/probes.test.ts similarity index 100% rename from test-e2e/tests/probes.test.ts rename to test/tests/e2e/cases/probes.test.ts diff --git a/test-e2e/tests/traceroute.test.ts b/test/tests/e2e/cases/traceroute.test.ts similarity index 100% rename from test-e2e/tests/traceroute.test.ts rename to test/tests/e2e/cases/traceroute.test.ts diff --git a/test-e2e/docker.ts b/test/tests/e2e/docker.ts similarity index 98% rename from test-e2e/docker.ts rename to test/tests/e2e/docker.ts index 72f6b192..ab53fc11 100644 --- a/test-e2e/docker.ts +++ b/test/tests/e2e/docker.ts @@ -1,5 +1,5 @@ import Docker from 'dockerode'; -import { scopedLogger } from '../src/lib/logger.js'; +import { scopedLogger } from '../../../src/lib/logger.js'; const logger = scopedLogger('docker-manager'); diff --git a/test-e2e/setup-api.ts b/test/tests/e2e/setup-api.ts similarity index 75% rename from test-e2e/setup-api.ts rename to test/tests/e2e/setup-api.ts index 1f182618..6469d93f 100644 --- a/test-e2e/setup-api.ts +++ b/test/tests/e2e/setup-api.ts @@ -2,10 +2,10 @@ import Bluebird from 'bluebird'; import type { Knex } from 'knex'; import { docker } from './docker.js'; -import { client as sql } from '../src/lib/sql/client.js'; -import { initRedisClient } from '../src/lib/redis/client.js'; -import { initPersistentRedisClient } from '../src/lib/redis/persistent-client.js'; -import { initMeasurementRedisClient } from '../src/lib/redis/measurement-client.js'; +import { client as sql } from '../../../src/lib/sql/client.js'; +import { initRedisClient } from '../../../src/lib/redis/client.js'; +import { initPersistentRedisClient } from '../../../src/lib/redis/persistent-client.js'; +import { initMeasurementRedisClient } from '../../../src/lib/redis/measurement-client.js'; before(async () => { await docker.removeApiContainer(); diff --git a/test-e2e/setup-probe.ts b/test/tests/e2e/setup-probe.ts similarity index 79% rename from test-e2e/setup-probe.ts rename to test/tests/e2e/setup-probe.ts index 1913341b..93c8b24c 100644 --- a/test-e2e/setup-probe.ts +++ b/test/tests/e2e/setup-probe.ts @@ -2,12 +2,12 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import chai from 'chai'; -import chaiOas from '../test/plugins/oas/index.js'; +import chaiOas from '../../plugins/oas/index.js'; import { docker } from './docker.js'; import { waitProbeToConnect } from './utils.js'; before(async () => { - chai.use(await chaiOas({ specPath: path.join(fileURLToPath(new URL('.', import.meta.url)), '../public/v1/spec.yaml') })); + chai.use(await chaiOas({ specPath: path.join(fileURLToPath(new URL('.', import.meta.url)), '../../../public/v1/spec.yaml') })); await docker.removeProbeContainer(); diff --git a/test-e2e/utils.ts b/test/tests/e2e/utils.ts similarity index 96% rename from test-e2e/utils.ts rename to test/tests/e2e/utils.ts index 3c8a0a45..380ea1c0 100644 --- a/test-e2e/utils.ts +++ b/test/tests/e2e/utils.ts @@ -1,6 +1,6 @@ import got, { type RequestError } from 'got'; import { setTimeout } from 'timers/promises'; -import { scopedLogger } from '../src/lib/logger.js'; +import { scopedLogger } from '../../../src/lib/logger.js'; const logger = scopedLogger('e2e-utils'); diff --git a/tsconfig.json b/tsconfig.json index 5b8d4e00..96994325 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,8 +18,7 @@ }, "include": [ "src/**/*", - "test/**/*.ts", - "test-e2e/**/*.ts" + "test/**/*.ts" ], "exclude": [ "dist", diff --git a/wallaby.e2e.js b/wallaby.e2e.js index f17a900c..cf864af4 100644 --- a/wallaby.e2e.js +++ b/wallaby.e2e.js @@ -6,20 +6,20 @@ export default function wallaby () { 'public/v1/*', 'public/**/*.yaml', 'test/plugins/**/*', - 'test-e2e/setup-probe.ts', - 'test-e2e/utils.ts', - 'test-e2e/docker.ts', + 'test/tests/e2e/setup-probe.ts', + 'test/tests/e2e/utils.ts', + 'test/tests/e2e/docker.ts', 'src/**/*.ts', 'knexfile.js', 'package.json', ], tests: [ - 'test-e2e/**/*.test.ts', + 'test/tests/e2e/**/*.test.ts', ], setup (w) { const path = require('path'); - w.testFramework.addFile(path.resolve(process.cwd(), 'test-e2e/setup-probe.js')); + w.testFramework.addFile(path.resolve(process.cwd(), 'test/tests/e2e/setup-probe.js')); w.testFramework.timeout(20000); }, diff --git a/wallaby.js b/wallaby.js index 9dbc6e7b..1618341c 100644 --- a/wallaby.js +++ b/wallaby.js @@ -29,7 +29,8 @@ export default function wallaby () { 'data/LAST_API_COMMIT_HASH.txt', ], tests: [ - 'test/tests/**/*.test.ts', + 'test/tests/integration/**/*.test.ts', + 'test/tests/unit/**/*.test.ts', ], setup (w) { From 7ecdfe094c4c9d1dddcb5e780358f9decd5e9e27 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Mon, 18 Mar 2024 11:25:20 +0100 Subject: [PATCH 41/43] fix: use db urls from config --- test/tests/e2e/docker.ts | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/test/tests/e2e/docker.ts b/test/tests/e2e/docker.ts index ab53fc11..1c7ec69e 100644 --- a/test/tests/e2e/docker.ts +++ b/test/tests/e2e/docker.ts @@ -1,4 +1,6 @@ import Docker from 'dockerode'; +import config from 'config'; + import { scopedLogger } from '../../../src/lib/logger.js'; const logger = scopedLogger('docker-manager'); @@ -11,7 +13,18 @@ class DockerManager { } public async createApiContainer () { + let networkMode = 'host'; + let redisUrl = config.get('redis.url'); + let dbConnectionHost = config.get('db.connection.host'); + const isLinux = await this.isLinuxHost(); + + if (!isLinux) { + networkMode = 'bridge'; + redisUrl = redisUrl.replace('localhost', 'host.docker.internal'); + dbConnectionHost = dbConnectionHost.replace('localhost', 'host.docker.internal'); + } + // docker run -e NODE_ENV=test -e TEST_MODE=e2e -e NEW_RELIC_ENABLED=false -e REDIS_URL=redis://host.docker.internal:6379 -e DB_CONNECTION_HOST=host.docker.internal --name globalping-api-e2e globalping-api-e2e const container = await this.docker.createContainer({ Image: 'globalping-api-e2e', @@ -20,14 +33,14 @@ class DockerManager { 'NODE_ENV=test', 'TEST_MODE=e2e', 'NEW_RELIC_ENABLED=false', - `REDIS_URL=redis://${isLinux ? 'localhost' : 'host.docker.internal'}:6379`, - `DB_CONNECTION_HOST=${isLinux ? 'localhost' : 'host.docker.internal'}`, + `REDIS_URL=${redisUrl}`, + `DB_CONNECTION_HOST=${dbConnectionHost}`, ], HostConfig: { PortBindings: { '80/tcp': [{ HostPort: '80' }], }, - NetworkMode: isLinux ? 'host' : 'bridge', + NetworkMode: networkMode, }, }); @@ -36,16 +49,25 @@ class DockerManager { } public async createProbeContainer () { + let networkMode = 'host'; + let apiHost = 'ws://localhost:80'; + const isLinux = await this.isLinuxHost(); + + if (!isLinux) { + networkMode = 'bridge'; + apiHost = apiHost.replace('localhost', 'host.docker.internal'); + } + // docker run -e API_HOST=ws://host.docker.internal:80 --name globalping-probe-e2e ghcr.io/jsdelivr/globalping-probe const container = await this.docker.createContainer({ Image: 'ghcr.io/jsdelivr/globalping-probe', name: 'globalping-probe-e2e', Env: [ - `API_HOST=ws://${isLinux ? 'localhost' : 'host.docker.internal'}:80`, + `API_HOST=${apiHost}`, ], HostConfig: { - NetworkMode: isLinux ? 'host' : 'bridge', + NetworkMode: networkMode, }, }); From 8782b89a7d3bcb71957b0e6f056947ebe7e9475a Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Mon, 18 Mar 2024 13:10:52 +0100 Subject: [PATCH 42/43] fix: build probe image from github master --- package.json | 2 +- test/tests/e2e/docker.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index e24b23c7..5528ab3b 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,7 @@ "test:portman:run": "newman run tmp/converted/globalpingApi.json -e test/tests/contract/newman-env.json --ignore-redirects", "test:e2e": "npm run test:e2e:docker && npm run test:e2e:cases", "test:e2e:api": "NODE_ENV=test knex migrate:latest && PORT=80 NODE_ENV=test TEST_MODE=e2e NEW_RELIC_ENABLED=false tsx src/index.ts", - "test:e2e:docker": "docker build -t globalping-api-e2e . && docker pull ghcr.io/jsdelivr/globalping-probe", + "test:e2e:docker": "docker build -t globalping-api-e2e . && docker build -t globalping-probe-e2e https://github.com/jsdelivr/globalping-probe.git", "test:e2e:cases": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha --config .mocharc.e2e.json" }, "lint-staged": { diff --git a/test/tests/e2e/docker.ts b/test/tests/e2e/docker.ts index 1c7ec69e..401e4d89 100644 --- a/test/tests/e2e/docker.ts +++ b/test/tests/e2e/docker.ts @@ -59,9 +59,9 @@ class DockerManager { apiHost = apiHost.replace('localhost', 'host.docker.internal'); } - // docker run -e API_HOST=ws://host.docker.internal:80 --name globalping-probe-e2e ghcr.io/jsdelivr/globalping-probe + // docker run -e API_HOST=ws://host.docker.internal:80 --name globalping-probe-e2e globalping-api-e2e const container = await this.docker.createContainer({ - Image: 'ghcr.io/jsdelivr/globalping-probe', + Image: 'globalping-probe-e2e', name: 'globalping-probe-e2e', Env: [ `API_HOST=${apiHost}`, From 102383d251c8557d4a27e720ea57df5aae84f533 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Mon, 18 Mar 2024 13:21:12 +0100 Subject: [PATCH 43/43] fix: get rtt schema back --- public/v1/components/schemas.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/public/v1/components/schemas.yaml b/public/v1/components/schemas.yaml index 186c9dea..2cfd9067 100644 --- a/public/v1/components/schemas.yaml +++ b/public/v1/components/schemas.yaml @@ -1194,9 +1194,7 @@ components: - $ref: 'schemas.yaml#/components/schemas/BaseTestStatus' - const: offline TimingPacketRtt: - type: - - number - - 'null' + type: number description: The round-trip time for this packet. TimingPacketTtl: type: number