diff --git a/.env.sample b/.env.sample index 0ff2341..a260ce1 100644 --- a/.env.sample +++ b/.env.sample @@ -5,6 +5,7 @@ APP_HOST_PORT=3042 APP_CONTAINER_PORT=3000 APP_PORT=3000 +MONGO_IMAGE=mongo:8 MONGO_CONTAINER_NAME=fastLazyBee-mongo MONGO_DB_NAME=sample_mflix MONGO_HOST_PORT=27027 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6910d96..a8163f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: sleep 1 done - - name: Run basic tests + - name: Run basic test (workshop scenario) run: | RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" ${{ env.TEST_ENDPOINT }}) if [ $RESPONSE_CODE -eq 200 ]; then @@ -50,6 +50,10 @@ jobs: exit 1 fi + - name: Tear down Docker Compose + run: | + docker-compose -f docker-compose.yml down + - name: Run unit/integration tests run: | npm ci @@ -60,7 +64,3 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} path-to-lcov: ./coverage/lcov.info - - - name: Tear down Docker Compose - run: | - docker-compose -f docker-compose.yml down diff --git a/.vscode/launch.json b/.vscode/launch.json index 678357a..205124c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,7 @@ "outFiles": ["${workspaceFolder}/dist/**/*.js"], "sourceMaps": true, "env": { - "NODE_ENV": "development" + "NODE_ENV": "test" } } ] diff --git a/package-lock.json b/package-lock.json index afce3c5..20421ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@babel/core": "^7.25.8", "@babel/preset-env": "^7.25.8", "@babel/preset-typescript": "^7.25.7", + "@testcontainers/mongodb": "^10.13.2", "@types/jest": "^29.5.13", "@types/node": "^22.7.7", "@typescript-eslint/eslint-plugin": "^8.10.0", @@ -2017,6 +2018,13 @@ "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, + "license": "Apache-2.0" + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -2173,6 +2181,16 @@ "resolved": "https://registry.npmjs.org/@fastify/autoload/-/autoload-6.0.2.tgz", "integrity": "sha512-efRwk2KKTPERxHEnzabK+w2pGxBExCCYixPYUMqtLd++Xsoh4h5Rrq8D2xZDq2BQ8hTFI1rzGLkHWsz40wgoWA==" }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/@fastify/caching": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/@fastify/caching/-/caching-9.0.1.tgz", @@ -2914,6 +2932,17 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pkgr/core": { "version": "0.1.1", "dev": true, @@ -2955,6 +2984,16 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@testcontainers/mongodb": { + "version": "10.13.2", + "resolved": "https://registry.npmjs.org/@testcontainers/mongodb/-/mongodb-10.13.2.tgz", + "integrity": "sha512-ib/QaGAhPECbf+r1JjR78tXdQdbwo5/HLw53KSkTuHOGOe000v03d0hVl+42gkWqTcmp0mcXxwoMyjVZiGuuzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "testcontainers": "^10.13.2" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -3028,6 +3067,29 @@ "@babel/types": "^7.20.7" } }, + "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, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/ssh2": "*" + } + }, + "node_modules/@types/dockerode": { + "version": "3.3.31", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.31.tgz", + "integrity": "sha512-42R9eoVqJDSvVspV89g7RwRqfNExgievLNWoHkg7NoWIqAmavIbgQBb4oc0qRtHkxE+I3Xxvqv7qVXFABKPBTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/docker-modem": "*", + "@types/node": "*", + "@types/ssh2": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -3090,6 +3152,43 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/ssh2": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.1.tgz", + "integrity": "sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18" + } + }, + "node_modules/@types/ssh2-streams": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@types/ssh2-streams/-/ssh2-streams-0.1.12.tgz", + "integrity": "sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ssh2/node_modules/@types/node": { + "version": "18.19.57", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.57.tgz", + "integrity": "sha512-I2ioBd/IPrYDMv9UNR5NlPElOZ68QB7yY5V2EsLtSrTO0LM0PnCEFF9biLWHf5k+sIy4ohueCV9t4gk1AEdlVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ssh2/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -3738,6 +3837,115 @@ "node": ">= 8" } }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/archiver-utils/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/archiver-utils/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver/node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -3858,6 +4066,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -3865,6 +4083,13 @@ "dev": true, "license": "MIT" }, + "node_modules/async-lock": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", + "dev": true, + "license": "MIT" + }, "node_modules/async-mutex": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", @@ -4101,6 +4326,49 @@ "license": "Apache-2.0", "optional": true }, + "node_modules/bare-fs": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", + "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", + "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.3.1.tgz", + "integrity": "sha512-Vm8kAeOcfzHPTH8sq0tHBnUqYrkXdroaBVVylqFT4cF5wnMfKEIxxy2jIGu2zKVNl9P8MAP9XBWwXJ9N2+jfEw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.20.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "funding": [ @@ -4119,6 +4387,68 @@ ], "license": "MIT" }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/brace-expansion": { "version": "2.0.1", "license": "MIT", @@ -4239,6 +4569,26 @@ "dev": true, "license": "MIT" }, + "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/byline": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", + "integrity": "sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/call-bind": { "version": "1.0.7", "dev": true, @@ -4321,6 +4671,13 @@ "node": ">=10" } }, + "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, + "license": "ISC" + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -4452,6 +4809,23 @@ "dev": true, "license": "MIT" }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/concat-map": { "version": "0.0.1", "dev": true, @@ -4496,6 +4870,55 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cpu-features": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.19.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", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -4707,6 +5130,110 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/docker-compose": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.8.tgz", + "integrity": "sha512-plizRs/Vf15H+GCVxq2EUvyPK7ei9b/cVesHvjnX4xaXjM9spHe2Ytq0BitndFgvTJ3E3NljPNUEl7BAN43iZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "yaml": "^2.2.2" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/docker-modem": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.8.tgz", + "integrity": "sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.1", + "readable-stream": "^3.5.0", + "split-ca": "^1.0.1", + "ssh2": "^1.11.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/docker-modem/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/dockerode": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.3.5.tgz", + "integrity": "sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "docker-modem": "^3.0.0", + "tar-fs": "~2.0.1" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/dockerode/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/dockerode/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, + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "node_modules/dockerode/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/doctrine": { "version": "3.0.0", "dev": true, @@ -6007,6 +6534,13 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, "node_modules/fs.realpath": { "version": "1.0.0", "dev": true, @@ -6108,6 +6642,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -7683,6 +8230,59 @@ "node": ">=6" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -7736,6 +8336,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -7893,6 +8500,26 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "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, + "license": "MIT" + }, "node_modules/mongodb": { "version": "6.9.0", "license": "Apache-2.0", @@ -8001,6 +8628,14 @@ "version": "2.1.3", "license": "MIT" }, + "node_modules/nan": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", + "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/natural-compare": { "version": "1.4.0", "dev": true, @@ -8549,6 +9184,13 @@ "node": ">= 0.6.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, "node_modules/process-warning": { "version": "4.0.0", "license": "MIT" @@ -8567,6 +9209,42 @@ "node": ">= 6" } }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/properties-reader": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/properties-reader/-/properties-reader-2.3.0.tgz", + "integrity": "sha512-z597WicA7nDZxK12kZqHr2TcvwNU1GCfA5UwfDY/HDp3hXPoPlb5rlEx9bwGTiJnc0OqbBTkU975jDToth8Gxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/properties?sponsor=1" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "license": "MIT", @@ -8671,6 +9349,29 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/real-require": { "version": "0.2.0", "license": "MIT", @@ -8870,6 +9571,16 @@ "node": ">=10" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "license": "MIT", @@ -8987,6 +9698,13 @@ "node": ">=10" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, "node_modules/secure-json-parse": { "version": "2.7.0", "license": "BSD-3-Clause" @@ -9135,6 +9853,13 @@ "memory-pager": "^1.0.2" } }, + "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, + "license": "ISC" + }, "node_modules/split2": { "version": "4.2.0", "license": "ISC", @@ -9149,6 +9874,46 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/ssh-remote-port-forward": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz", + "integrity": "sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ssh2": "^0.5.48", + "ssh2": "^1.4.0" + } + }, + "node_modules/ssh-remote-port-forward/node_modules/@types/ssh2": { + "version": "0.5.52", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.52.tgz", + "integrity": "sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/ssh2-streams": "*" + } + }, + "node_modules/ssh2": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz", + "integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.10", + "nan": "^2.20.0" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -9411,6 +10176,21 @@ "node": ">=6" } }, + "node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, "node_modules/tar-stream": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", @@ -9484,6 +10264,30 @@ "node": "*" } }, + "node_modules/testcontainers": { + "version": "10.13.2", + "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.13.2.tgz", + "integrity": "sha512-LfEll+AG/1Ks3n4+IA5lpyBHLiYh/hSfI4+ERa6urwfQscbDU+M2iW1qPQrHQi+xJXQRYy4whyK1IEHdmxWa3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "@types/dockerode": "^3.3.29", + "archiver": "^7.0.1", + "async-lock": "^1.4.1", + "byline": "^5.0.0", + "debug": "^4.3.5", + "docker-compose": "^0.24.8", + "dockerode": "^3.3.5", + "get-port": "^5.1.1", + "proper-lockfile": "^4.1.2", + "properties-reader": "^2.3.0", + "ssh-remote-port-forward": "^1.0.4", + "tar-fs": "^3.0.6", + "tmp": "^0.2.3", + "undici": "^5.28.4" + } + }, "node_modules/text-decoder": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.0.tgz", @@ -9506,6 +10310,16 @@ "real-require": "^0.2.0" } }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -9709,6 +10523,13 @@ "fsevents": "~2.3.3" } }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true, + "license": "Unlicense" + }, "node_modules/type-check": { "version": "0.4.0", "dev": true, @@ -9956,6 +10777,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/undici-types": { "version": "6.19.8", "dev": true, @@ -10043,6 +10877,13 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -10365,6 +11206,21 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } } } } diff --git a/package.json b/package.json index 5ce4705..db51ce8 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,12 @@ "build": "npm run clean && npx tsc -p tsconfig.json", "start": "node dist/src/server.js" }, - "keywords": ["fastify", "typescript", "mongodb", "restful-api"], + "keywords": [ + "fastify", + "typescript", + "mongodb", + "restful-api" + ], "author": "Riccardo Mura", "license": "MIT", "dependencies": { @@ -29,6 +34,7 @@ "@babel/core": "^7.25.8", "@babel/preset-env": "^7.25.8", "@babel/preset-typescript": "^7.25.7", + "@testcontainers/mongodb": "^10.13.2", "@types/jest": "^29.5.13", "@types/node": "^22.7.7", "@typescript-eslint/eslint-plugin": "^8.10.0", diff --git a/src/options/server-options.ts b/src/options/server-options.ts index f1b244e..acabfee 100644 --- a/src/options/server-options.ts +++ b/src/options/server-options.ts @@ -7,7 +7,8 @@ const serverOptions: FastifyServerOptions = { transport: { target: 'pino-pretty' } - } + }, + pluginTimeout: 100000 }; export { serverOptions }; diff --git a/src/plugins/cache.ts b/src/plugins/cache.ts index 10aedfa..d524d7e 100644 --- a/src/plugins/cache.ts +++ b/src/plugins/cache.ts @@ -1,8 +1,8 @@ import type { FastifyInstance, FastifyRequest, RouteOptions } from 'fastify'; import fp from 'fastify-plugin'; import * as CacheUtils from '../utils/cache-utils'; -import { RouteTags } from '../utils/constants'; -import { HttpStatusCodes } from '../utils/enums'; +import { RouteTags } from '../utils/constants/constants'; +import { HttpStatusCodes } from '../utils/constants/enums'; const isCacheable = (request: FastifyRequest): boolean => { const routeOptions = request.routeOptions as RouteOptions; @@ -12,46 +12,49 @@ const isCacheable = (request: FastifyRequest): boolean => { return routeOptions.schema.tags.includes(RouteTags.cache); }; -const modulePlugin = fp(async (fastify: FastifyInstance) => { - fastify.addHook('onRequest', async (request, reply) => { - if (!isCacheable(request)) { - return; - } - - const cacheKey = CacheUtils.genCacheKey(request); - fastify.cache.get(cacheKey, (err, value) => { - if (err != null) { - fastify.log.error(err); +const modulePlugin = fp( + async (fastify: FastifyInstance) => { + fastify.addHook('onRequest', async (request, reply) => { + if (!isCacheable(request)) { return; } - if (value != null) { - fastify.log.info(`Cache hit for key: ${cacheKey}`); - const payload = JSON.parse(value.item as string); - reply.send(payload); - } - }); - }); - fastify.addHook('onSend', async (request, reply, payload) => { - if (!isCacheable(request) || reply.statusCode !== HttpStatusCodes.OK) { - return; - } + const cacheKey = CacheUtils.genCacheKey(request); + fastify.cache.get(cacheKey, (err, value) => { + if (err != null) { + fastify.log.error(err); + return; + } + if (value != null) { + fastify.log.info(`Cache hit for key: ${cacheKey}`); + const payload = JSON.parse(value.item as string); + reply.send(payload); + } + }); + }); - const cacheKey = CacheUtils.genCacheKey(request); - fastify.cache.get(cacheKey, (err, value) => { - if (err != null) { - fastify.log.error(err); + fastify.addHook('onSend', async (request, reply, payload) => { + if (!isCacheable(request) || reply.statusCode !== HttpStatusCodes.OK) { return; } - if (value == null) { - fastify.cache.set(cacheKey, payload, fastify.config.CACHE_EXPIRATION, (err) => { - if (err != null) { - fastify.log.error(err); - } - }); - } + + const cacheKey = CacheUtils.genCacheKey(request); + fastify.cache.get(cacheKey, (err, value) => { + if (err != null) { + fastify.log.error(err); + return; + } + if (value == null) { + fastify.cache.set(cacheKey, payload, fastify.config.CACHE_EXPIRATION, (err) => { + if (err != null) { + fastify.log.error(err); + } + }); + } + }); }); - }); -}); + }, + { name: 'cache' } +); export default modulePlugin; diff --git a/src/plugins/error-handling.ts b/src/plugins/error-handling.ts index dc11dd7..3515d7b 100644 --- a/src/plugins/error-handling.ts +++ b/src/plugins/error-handling.ts @@ -1,6 +1,6 @@ import type { FastifyError, FastifyInstance } from 'fastify'; import fp from 'fastify-plugin'; -import { HttpStatusCodes } from '../utils/enums'; +import { HttpStatusCodes } from '../utils/constants/enums'; import type { ErrorSchemaType } from '../schemas/errors'; const mapFastifyErrorToErrorSchemaType = (error: FastifyError): ErrorSchemaType => { @@ -15,13 +15,16 @@ const mapFastifyErrorToErrorSchemaType = (error: FastifyError): ErrorSchemaType }; }; -const errorHandlingPlugin = fp(async (fastify: FastifyInstance) => { - fastify.setErrorHandler(async (error: FastifyError, request, reply) => { - fastify.log.error(error); - const replyError: ErrorSchemaType = mapFastifyErrorToErrorSchemaType(error); - reply.code(replyError.status).send(replyError); - }); -}); +const errorHandlingPlugin = fp( + async (fastify: FastifyInstance) => { + fastify.setErrorHandler(async (error: FastifyError, request, reply) => { + fastify.log.error(error); + const replyError: ErrorSchemaType = mapFastifyErrorToErrorSchemaType(error); + reply.code(replyError.status).send(replyError); + }); + }, + { name: 'error-handling' } +); export default errorHandlingPlugin; export { mapFastifyErrorToErrorSchemaType }; diff --git a/src/plugins/mongodb-data-store.ts b/src/plugins/mongodb-data-store.ts index ebe6b64..08c0dfe 100644 --- a/src/plugins/mongodb-data-store.ts +++ b/src/plugins/mongodb-data-store.ts @@ -1,15 +1,33 @@ import fp from 'fastify-plugin'; -import fastifyMongo from '@fastify/mongodb'; +import fastifyMongo, { type FastifyMongodbOptions } from '@fastify/mongodb'; import type { FastifyInstance } from 'fastify'; +import setupMongoTestcontainers from '../utils/testing/setup-mongo-testcontainers'; + +const getMongoOptions = async (fastify: FastifyInstance): Promise => { + const commonOptions: FastifyMongodbOptions = { + forceClose: true + }; + + if (fastify.config.NODE_ENV === 'test') { + const mongoOptions = await setupMongoTestcontainers(); + return { + ...commonOptions, + ...mongoOptions + }; + } + + return { + ...commonOptions, + url: fastify.config.MONGO_URL + }; +}; const mongoPlugin = fp( async (fastify: FastifyInstance) => { - await fastify.register(fastifyMongo, { - forceClose: true, - url: fastify.config.MONGO_URL - }); + const mongoOptions = await getMongoOptions(fastify); + await fastify.register(fastifyMongo, mongoOptions); }, - { dependencies: ['server-config'] } + { name: 'mongo', dependencies: ['server-config'] } ); export default mongoPlugin; diff --git a/src/plugins/swagger.ts b/src/plugins/swagger.ts index c314607..6c6b4d7 100644 --- a/src/plugins/swagger.ts +++ b/src/plugins/swagger.ts @@ -3,7 +3,7 @@ import fastifySwagger, { type FastifyDynamicSwaggerOptions } from '@fastify/swag import fastifySwaggerUi, { type FastifySwaggerUiOptions } from '@fastify/swagger-ui'; import type { FastifyInstance } from 'fastify'; import pkg from '../../package.json'; -import { RouteTags } from '../utils/constants'; +import { RouteTags } from '../utils/constants/constants'; const swaggerOptions: FastifyDynamicSwaggerOptions = { swagger: { @@ -42,7 +42,7 @@ const swaggerPlugin = fp( await fastify.register(fastifySwagger, swaggerOptions); await fastify.register(fastifySwaggerUi, swaggerUIOptions); }, - { dependencies: ['server-config'] } + { name: 'swagger', dependencies: ['server-config'] } ); export default swaggerPlugin; diff --git a/src/routes/health/routes.ts b/src/routes/health/routes.ts index 647fa32..7bba9fe 100644 --- a/src/routes/health/routes.ts +++ b/src/routes/health/routes.ts @@ -1,6 +1,6 @@ import type { FastifyInstance, FastifyReply, FastifyRequest, RouteOptions } from 'fastify'; -import { HttpMethods, HttpStatusCodes } from '../../utils/enums'; -import { RouteTags } from '../../utils/constants'; +import { HttpMethods, HttpStatusCodes } from '../../utils/constants/enums'; +import { RouteTags } from '../../utils/constants/constants'; const routes: RouteOptions[] = [ { diff --git a/src/routes/mflix/movies/autohooks.ts b/src/routes/mflix/movies/autohooks.ts index e49e066..9b4ce5b 100644 --- a/src/routes/mflix/movies/autohooks.ts +++ b/src/routes/mflix/movies/autohooks.ts @@ -2,7 +2,7 @@ import type { FastifyError, FastifyInstance } from 'fastify'; import fp from 'fastify-plugin'; import type { MovieSchemaType } from '../../../schemas/movies/data'; import type { Collection, Db, Sort, SortDirection } from 'mongodb'; -import { HttpStatusCodes } from '../../../utils/enums'; +import { HttpStatusCodes } from '../../../utils/constants/enums'; import type { MovieFilterSchemaType } from '../../../schemas/movies/http'; const notFoundError = (id: string): FastifyError => ({ diff --git a/src/routes/mflix/movies/base-routes.ts b/src/routes/mflix/movies/base-routes.ts index 2be14c2..071c13d 100644 --- a/src/routes/mflix/movies/base-routes.ts +++ b/src/routes/mflix/movies/base-routes.ts @@ -4,10 +4,10 @@ import { ListMoviesSchema, type MovieFilterSchemaType } from '../../../schemas/movies/http'; -import { HttpMethods, HttpStatusCodes } from '../../../utils/enums'; +import { HttpMethods, HttpStatusCodes } from '../../../utils/constants/enums'; import { genOptionsRoute } from '../../../utils/routing-utils'; import type { MovieSchemaType } from '../../../schemas/movies/data'; -import { RouteTags } from '../../../utils/constants'; +import { RouteTags } from '../../../utils/constants/constants'; const url = ''; diff --git a/src/routes/mflix/movies/id-routes.ts b/src/routes/mflix/movies/id-routes.ts index ab315d0..460f133 100644 --- a/src/routes/mflix/movies/id-routes.ts +++ b/src/routes/mflix/movies/id-routes.ts @@ -6,10 +6,10 @@ import { UpdateMovieSchema, DeleteMovieSchema } from '../../../schemas/movies/http'; -import { HttpMethods, HttpStatusCodes } from '../../../utils/enums'; +import { HttpMethods, HttpStatusCodes } from '../../../utils/constants/enums'; import { genOptionsRoute } from '../../../utils/routing-utils'; import type { MovieSchemaType } from '../../../schemas/movies/data'; -import { RouteTags } from '../../../utils/constants'; +import { RouteTags } from '../../../utils/constants/constants'; const url = '/:id'; diff --git a/src/schemas/dotenv.ts b/src/schemas/dotenv.ts index ca60f3e..dce2876 100644 --- a/src/schemas/dotenv.ts +++ b/src/schemas/dotenv.ts @@ -1,10 +1,12 @@ import { type Static, Type } from '@sinclair/typebox'; -import { AppConfigDefaults } from '../utils/constants'; +import { AppConfigDefaults } from '../utils/constants/constants'; const EnvSchema = Type.Object({ NODE_ENV: Type.String({ default: AppConfigDefaults.env }), APP_PORT: Type.Number({ default: AppConfigDefaults.port }), + MONGO_IMAGE: Type.String({ default: AppConfigDefaults.mongoImage }), MONGO_URL: Type.String({ default: AppConfigDefaults.mongoUrl }), + MONGO_DB_NAME: Type.String({ default: AppConfigDefaults.mongoDbName }), CACHE_EXPIRATION: Type.Number({ default: AppConfigDefaults.cacheExpiration }) }); diff --git a/src/schemas/http.ts b/src/schemas/http.ts index b551150..dc15e9f 100644 --- a/src/schemas/http.ts +++ b/src/schemas/http.ts @@ -1,6 +1,6 @@ import { type TObject, type TSchema, Type } from '@sinclair/typebox'; import { NaturalSchema } from './common'; -import { PaginationDefaults } from '../utils/constants'; +import { PaginationDefaults } from '../utils/constants/constants'; // const HttpRequestSchema = < // TBody extends TSchema | TNull, diff --git a/src/schemas/movies/http.ts b/src/schemas/movies/http.ts index ddfc898..415968d 100644 --- a/src/schemas/movies/http.ts +++ b/src/schemas/movies/http.ts @@ -1,5 +1,5 @@ import { type Static, Type } from '@sinclair/typebox'; -import { RouteTags } from '../../utils/constants'; +import { RouteTags } from '../../utils/constants/constants'; import { NoContentSchema, PaginatedDataSchema, @@ -7,7 +7,7 @@ import { SortStringSchema } from '../http'; import { MovieSchema, MovieWithIdSchema, PartialMovieSchema } from './data'; -import { HttpStatusCodes } from '../../utils/enums'; +import { HttpStatusCodes } from '../../utils/constants/enums'; import { createResponseSchema } from '../../utils/schema-utils'; import { ErrorSchema } from '../errors'; import type { FastifySchema } from 'fastify'; diff --git a/src/test/errors.test.ts b/src/test/errors.test.ts index 1199162..86e3129 100644 --- a/src/test/errors.test.ts +++ b/src/test/errors.test.ts @@ -1,6 +1,6 @@ import type { FastifyError } from 'fastify'; import { mapFastifyErrorToErrorSchemaType } from '../plugins/error-handling'; -import { HttpStatusCodes } from '../utils/enums'; +import { HttpStatusCodes } from '../utils/constants/enums'; describe('mapFastifyErrorToErrorSchemaType', () => { it('should return an error schema type', () => { diff --git a/src/test/routes/diagnostics-routes.test.ts b/src/test/routes/diagnostics-routes.test.ts index 07bdf2c..5453a43 100644 --- a/src/test/routes/diagnostics-routes.test.ts +++ b/src/test/routes/diagnostics-routes.test.ts @@ -1,6 +1,6 @@ import type { FastifyInstance } from 'fastify'; -import { buildTestInstance } from '../../utils/test-utils'; -import { HttpStatusCodes } from '../../utils/enums'; +import buildTestInstance from '../../utils/testing/build-test-instance'; +import { HttpStatusCodes } from '../../utils/constants/enums'; describe(' diagnosticsApi', () => { const fastifyInstance: FastifyInstance = buildTestInstance(); diff --git a/src/test/routes/movies-routes.test.ts b/src/test/routes/movies-routes.test.ts index d52eda3..d966596 100644 --- a/src/test/routes/movies-routes.test.ts +++ b/src/test/routes/movies-routes.test.ts @@ -1,13 +1,7 @@ import type { FastifyInstance } from 'fastify'; -import { buildTestInstance } from '../../utils/test-utils'; -import { HttpMethods, HttpStatusCodes } from '../../utils/enums'; -import { TestConstants } from '../../utils/constants'; - -// const mongod: MongoMemoryServer = await MongoMemoryServer.create(); -// const uri = mongod.getUri(); -// const client: MongoClient = new MongoClient(uri); -// const db: Db = client.db(); -// const movies: Collection = new Collection(); +import buildTestInstance from '../../utils/testing/build-test-instance'; +import { HttpMethods, HttpStatusCodes } from '../../utils/constants/enums'; +import { TestConstants } from '../../utils/constants/constants'; describe('movieApi', () => { const fastifyInstance: FastifyInstance = buildTestInstance(); diff --git a/src/test/utils/test-utils.test.ts b/src/test/utils/test-utils.test.ts new file mode 100644 index 0000000..9704f22 --- /dev/null +++ b/src/test/utils/test-utils.test.ts @@ -0,0 +1,29 @@ +import setupMongoTestcontainers from '../../utils/testing/setup-mongo-testcontainers'; +import { downloadMongoArchive } from '../../utils/testing/setup-mongo-common'; +import * as fs from 'fs'; +import type { FastifyMongodbOptions } from '@fastify/mongodb'; +import { TestConstants } from '../../utils/constants/constants'; + +describe('downloadMongoArchive', () => { + it( + 'should return the path where the archive was stored', + async () => { + const path = await downloadMongoArchive(); + expect(path).not.toBeNull(); + const exists = fs.existsSync(path); + expect(exists).toBeTruthy(); + }, + TestConstants.longTimeout + ); +}); + +describe('setupMongoTestcontainers', () => { + it( + 'should return FastifyMongodbOptions', + async () => { + const options: FastifyMongodbOptions = await setupMongoTestcontainers(); + expect(options).not.toBeNull(); + }, + TestConstants.longTimeout + ); +}); diff --git a/src/utils/constants.ts b/src/utils/constants/constants.ts similarity index 63% rename from src/utils/constants.ts rename to src/utils/constants/constants.ts index d77b581..235347b 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants/constants.ts @@ -1,9 +1,12 @@ -import type { MovieSchemaType } from '../schemas/movies/data'; +import type { MovieSchemaType } from '../../schemas/movies/data'; class AppConfigDefaults { static readonly env = 'development'; static readonly port = 3000; - static readonly mongoUrl = 'mongodb://localhost:27027/sample_mflix'; + static readonly mongoImage = 'mongo:8'; + static readonly mongoPort = 27027; + static readonly mongoDbName = 'sample_mflix'; + static readonly mongoUrl = `mongodb://localhost:${this.mongoPort}/${this.mongoDbName}`; static readonly cacheExpiration = 10000; } @@ -19,6 +22,9 @@ class TestConstants { static readonly fakeId = '000000000000000000000000'; static readonly magicId = '670f5e20c286545ba702aade'; static readonly testMovie: MovieSchemaType = { title: 'Test Movie', type: 'movie', year: 2024 }; + static readonly mongoArchiveUrl = 'https://atlas-education.s3.amazonaws.com/sampledata.archive'; + static readonly mongoTestcontainersPort = 27028; + static readonly longTimeout = 60000; } class RouteTags { diff --git a/src/utils/enums.ts b/src/utils/constants/enums.ts similarity index 100% rename from src/utils/enums.ts rename to src/utils/constants/enums.ts diff --git a/src/utils/records.ts b/src/utils/constants/records.ts similarity index 100% rename from src/utils/records.ts rename to src/utils/constants/records.ts diff --git a/src/utils/routing-utils.ts b/src/utils/routing-utils.ts index a97772d..7140aea 100644 --- a/src/utils/routing-utils.ts +++ b/src/utils/routing-utils.ts @@ -1,5 +1,5 @@ import type { RouteOptions } from 'fastify'; -import { HttpMethods, HttpStatusCodes } from './enums'; +import { HttpMethods, HttpStatusCodes } from './constants/enums'; const genOptionsRoute = (url: string, tags: string[], allowString: string): RouteOptions => { return { diff --git a/src/utils/schema-utils.ts b/src/utils/schema-utils.ts index 673a077..11f23c8 100644 --- a/src/utils/schema-utils.ts +++ b/src/utils/schema-utils.ts @@ -1,6 +1,6 @@ import type { TSchema } from '@sinclair/typebox'; -import type { HttpStatusCodes } from './enums'; -import { HttpCodesToDescriptions } from './records'; +import type { HttpStatusCodes } from './constants/enums'; +import { HttpCodesToDescriptions } from './constants/records'; const createResponseSchema = ( statusCode: HttpStatusCodes, diff --git a/src/utils/test-utils.ts b/src/utils/testing/build-test-instance.ts similarity index 63% rename from src/utils/test-utils.ts rename to src/utils/testing/build-test-instance.ts index ec2469b..9fbfed1 100644 --- a/src/utils/test-utils.ts +++ b/src/utils/testing/build-test-instance.ts @@ -1,8 +1,8 @@ import type { FastifyInstance } from 'fastify'; -import { buildInstance } from '../app'; -import { serverOptions } from '../options/server-options'; -import autoloadOptions from '../options/autoload-options'; -import { TestConstants } from '../utils/constants'; +import { buildInstance } from '../../app'; +import { serverOptions } from '../../options/server-options'; +import autoloadOptions from '../../options/autoload-options'; +import { TestConstants } from '../constants/constants'; import { ObjectId } from '@fastify/mongodb'; function buildTestInstance(): FastifyInstance { @@ -10,10 +10,11 @@ function buildTestInstance(): FastifyInstance { beforeAll(async () => { await fastifyApp.ready(); - }); + await fastifyApp.mongo.client.connect(); + }, TestConstants.longTimeout); beforeEach(async () => { - const db = (fastifyApp.mongo.db = fastifyApp.mongo.client.db()); + const db = fastifyApp.mongo.client.db(); const movieCollection = db.collection('movies'); const movie = await movieCollection.findOne({ _id: new ObjectId(TestConstants.magicId) }); @@ -26,10 +27,11 @@ function buildTestInstance(): FastifyInstance { }); afterAll(async () => { + await fastifyApp.mongo.client.close(); await fastifyApp.close(); }); return fastifyApp; } -export { buildTestInstance }; +export default buildTestInstance; diff --git a/src/utils/testing/setup-mongo-common.ts b/src/utils/testing/setup-mongo-common.ts new file mode 100644 index 0000000..a7b6188 --- /dev/null +++ b/src/utils/testing/setup-mongo-common.ts @@ -0,0 +1,27 @@ +import path from 'path'; +import { TestConstants } from '../constants/constants'; +import fs from 'fs'; +import os from 'os'; + +const downloadMongoArchive = async (): Promise => { + const archiveUrl = TestConstants.mongoArchiveUrl; + const tempDir = os.tmpdir(); + const archivePath = path.join(tempDir, 'sampledata.archive'); + + if (!fs.existsSync(archivePath)) { + try { + const response = await fetch(archiveUrl); + if (!response.ok) { + throw new Error(`Failed to download archive: ${response.statusText}`); + } + const buffer = await response.arrayBuffer(); + fs.writeFileSync(archivePath, Buffer.from(buffer)); + } catch (error) { + console.error(error); + } + } + + return archivePath; +}; + +export { downloadMongoArchive }; diff --git a/src/utils/testing/setup-mongo-testcontainers.ts b/src/utils/testing/setup-mongo-testcontainers.ts new file mode 100644 index 0000000..a523816 --- /dev/null +++ b/src/utils/testing/setup-mongo-testcontainers.ts @@ -0,0 +1,31 @@ +import { MongoDBContainer } from '@testcontainers/mongodb'; +import { downloadMongoArchive } from './setup-mongo-common'; +import * as fs from 'fs'; +import type { FastifyMongodbOptions } from '@fastify/mongodb'; +import { AppConfigDefaults } from '../constants/constants'; + +const setupMongoTestcontainers = async (): Promise => { + const archivePath: string = await downloadMongoArchive(); + const mongoArchivePath = '/tmp/sampledata.archive'; + if (!fs.existsSync(archivePath)) { + throw new Error('Archive path does not exist'); + } + const startedContainer = await new MongoDBContainer(AppConfigDefaults.mongoImage) + .withCopyFilesToContainer([ + { + source: archivePath, + target: mongoArchivePath + } + ]) + .start(); + + const result = await startedContainer.exec(['mongorestore', `--archive=${mongoArchivePath}`]); + if (result.exitCode !== 0) { + throw new Error(`Failed to restore MongoDB archive: ${result.output}`); + } + + const connectionString = `mongodb://localhost:${startedContainer.getFirstMappedPort()}/sample_mflix?directConnection=true`; + return { url: connectionString }; +}; + +export default setupMongoTestcontainers;