From 8cbc05cc0b194fba1d290169713c824c5edf4b62 Mon Sep 17 00:00:00 2001 From: Bruno Henrique Date: Sun, 29 Dec 2024 20:12:10 -0300 Subject: [PATCH] makes `npm test` more robust with `async-retry` and `orchestrator.js` --- jest.config.js | 1 + package-lock.json | 147 ++++++++++++++++++ package.json | 4 +- .../integration/api/v1/migrations/get.spec.js | 8 +- .../api/v1/migrations/post.spec.js | 8 +- tests/integration/api/v1/status/get.spec.js | 6 + tests/orchestrator.js | 24 +++ 7 files changed, 189 insertions(+), 9 deletions(-) create mode 100644 tests/orchestrator.js diff --git a/jest.config.js b/jest.config.js index 0b30554..32048da 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,6 +11,7 @@ const createJestConfig = nextJest({ const jestConfig = createJestConfig({ moduleDirectories: ["node_modules", ""], + testTimeout: 60000, }); module.exports = jestConfig; diff --git a/package-lock.json b/package-lock.json index 9a35aba..14f4733 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "async-retry": "^1.3.3", "dotenv": "^16.4.4", "dotenv-expand": "^11.0.6", "next": "^13.1.6", @@ -18,6 +19,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { + "concurrently": "^8.2.2", "jest": "^29.6.2", "prettier": "^3.4.1" } @@ -467,6 +469,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", @@ -1391,6 +1406,15 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "dependencies": { + "retry": "0.13.1" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -1751,6 +1775,50 @@ "dev": true, "license": "MIT" }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1795,6 +1863,23 @@ "node": ">= 8" } }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -3107,6 +3192,13 @@ "node": ">=8" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3862,6 +3954,13 @@ "dev": true, "license": "MIT" }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3922,6 +4021,25 @@ "node": ">=10" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -3964,6 +4082,19 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -4018,6 +4149,12 @@ "source-map": "^0.6.0" } }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -4204,6 +4341,16 @@ "node": ">=8.0" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", diff --git a/package.json b/package.json index a80592c..a3d4219 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "services:down": "docker compose -f infra/compose.yaml down", "lint:check": "prettier --check .", "lint:fix": "prettier --write .", - "test": "jest --runInBand", + "test": " npm run services:up && concurrently -n next,jest --hide next -k -s command-jest \"next dev\" \"jest --runInBand --verbose\"", "test:watch": "jest --watchAll --runInBand", "migration:create": "node-pg-migrate -m infra/migrations create", "migration:up": "node-pg-migrate -m infra/migrations --envPath .env.development up", @@ -19,6 +19,7 @@ "author": "", "license": "MIT", "dependencies": { + "async-retry": "^1.3.3", "dotenv": "^16.4.4", "dotenv-expand": "^11.0.6", "next": "^13.1.6", @@ -28,6 +29,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { + "concurrently": "^8.2.2", "jest": "^29.6.2", "prettier": "^3.4.1" } diff --git a/tests/integration/api/v1/migrations/get.spec.js b/tests/integration/api/v1/migrations/get.spec.js index 3dba390..6177e85 100644 --- a/tests/integration/api/v1/migrations/get.spec.js +++ b/tests/integration/api/v1/migrations/get.spec.js @@ -1,10 +1,10 @@ import database from "infra/database.js"; +import orchestrator from "tests/orchestrator"; -beforeAll(cleanDatabase); - -async function cleanDatabase() { +beforeAll(async () => { + await orchestrator.waitForAllServices(); await database.query("drop schema public cascade; create schema public"); -} +}); test("GET to /api/v1/migrations should return 200", async () => { const response = await fetch("http://localhost:3000/api/v1/migrations"); diff --git a/tests/integration/api/v1/migrations/post.spec.js b/tests/integration/api/v1/migrations/post.spec.js index 40f822a..f940c7c 100644 --- a/tests/integration/api/v1/migrations/post.spec.js +++ b/tests/integration/api/v1/migrations/post.spec.js @@ -1,10 +1,10 @@ import database from "infra/database.js"; +import orchestrator from "tests/orchestrator"; -beforeAll(cleanDatabase); - -async function cleanDatabase() { +beforeAll(async () => { + await orchestrator.waitForAllServices(); await database.query("drop schema public cascade; create schema public"); -} +}); test("POST to /api/v1/migrations should return 200", async () => { const response1 = await fetch("http://localhost:3000/api/v1/migrations", { diff --git a/tests/integration/api/v1/status/get.spec.js b/tests/integration/api/v1/status/get.spec.js index 6ead8d1..dd52d2b 100644 --- a/tests/integration/api/v1/status/get.spec.js +++ b/tests/integration/api/v1/status/get.spec.js @@ -1,3 +1,9 @@ +import orchestrator from "tests/orchestrator"; + +beforeAll(async () => { + await orchestrator.waitForAllServices(); +}); + test("GET to /api/v1/status should return 200", async () => { const response = await fetch("http://localhost:3000/api/v1/status"); expect(response.status).toBe(200); diff --git a/tests/orchestrator.js b/tests/orchestrator.js new file mode 100644 index 0000000..53131ce --- /dev/null +++ b/tests/orchestrator.js @@ -0,0 +1,24 @@ +import retry from "async-retry"; + +async function waitForAllServices() { + await waitForWebServer(); + + async function waitForWebServer() { + return retry(fetchStatusPage, { + retries: 100, + minTimeout: 100, + maxTimeout: 1000, + }); + + async function fetchStatusPage() { + const response = await fetch("http://localhost:3000/api/v1/status"); + if (response.status !== 200) { + throw Error(); + } + } + } +} + +export default { + waitForAllServices, +};