From 551667306dfaed8d76bcaaae7886f92053c015e2 Mon Sep 17 00:00:00 2001 From: Pasindu Pramodya Date: Tue, 19 Aug 2025 10:11:52 +0530 Subject: [PATCH] feat:add e2e testing initial setup --- package.json | 3 + pnpm-lock.yaml | 607 +++++++++++++++++++++++++- test/@types/globals.d.ts | 12 + test/app.e2e-spec.ts | 17 + test/factory/test-module.factory.ts | 41 ++ test/helpers/test-configuration.ts | 17 + test/helpers/test-container.helper.ts | 102 +++++ test/jest-e2e.json | 12 +- test/mocks/user-mocks.ts | 12 + test/modules/auth.e2e-spec.ts | 156 +++++++ test/modules/user.e2e-spec.ts | 216 +++++++++ test/setup/global-setup.ts | 20 + test/setup/global-teardown.ts | 3 + test/test-app.module.ts | 59 +++ 14 files changed, 1272 insertions(+), 5 deletions(-) create mode 100644 test/@types/globals.d.ts create mode 100644 test/app.e2e-spec.ts create mode 100644 test/factory/test-module.factory.ts create mode 100644 test/helpers/test-configuration.ts create mode 100644 test/helpers/test-container.helper.ts create mode 100644 test/mocks/user-mocks.ts create mode 100644 test/modules/auth.e2e-spec.ts create mode 100644 test/modules/user.e2e-spec.ts create mode 100644 test/setup/global-setup.ts create mode 100644 test/setup/global-teardown.ts create mode 100644 test/test-app.module.ts diff --git a/package.json b/package.json index 6fd9269..1a4a460 100644 --- a/package.json +++ b/package.json @@ -70,9 +70,11 @@ }, "devDependencies": { "@compodoc/compodoc": "^1.1.23", + "@faker-js/faker": "^9.9.0", "@nestjs/cli": "^11.0.2", "@nestjs/schematics": "^11.0.0", "@nestjs/testing": "^11.0.9", + "@testcontainers/postgresql": "^11.5.1", "@types/bcrypt": "^5.0.2", "@types/express": "^4.17.21", "@types/jest": "^29.5.12", @@ -89,6 +91,7 @@ "kysely-ctl": "^0.11.1", "prettier": "^3.2.5", "supertest": "^6.3.4", + "testcontainers": "^11.5.1", "ts-jest": "^29.1.2", "ts-loader": "^9.5.1", "ts-node": "^10.9.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a6acd4..fe83d7b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,7 +43,7 @@ importers: version: 11.0.3(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.9(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.0.9)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) '@nestjs/terminus': specifier: ^11.0.0 - version: 11.0.0(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.9(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.0.9)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 11.0.0(@grpc/grpc-js@1.13.4)(@grpc/proto-loader@0.7.15)(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.9(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.0.9)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) '@scalar/nestjs-api-reference': specifier: ^0.4.1 version: 0.4.1 @@ -102,6 +102,9 @@ importers: '@compodoc/compodoc': specifier: ^1.1.23 version: 1.1.26(typescript@5.7.2) + '@faker-js/faker': + specifier: ^9.9.0 + version: 9.9.0 '@nestjs/cli': specifier: ^11.0.2 version: 11.0.2(@types/node@20.17.10) @@ -111,6 +114,9 @@ importers: '@nestjs/testing': specifier: ^11.0.9 version: 11.0.9(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.9(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.0.9)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.0.9(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.9)) + '@testcontainers/postgresql': + specifier: ^11.5.1 + version: 11.5.1 '@types/bcrypt': specifier: ^5.0.2 version: 5.0.2 @@ -159,6 +165,9 @@ importers: supertest: specifier: ^6.3.4 version: 6.3.4 + testcontainers: + specifier: ^11.5.1 + version: 11.5.1 ts-jest: specifier: ^29.1.2 version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.10)(ts-node@10.9.2(@types/node@20.17.10)(typescript@5.7.2)))(typescript@5.7.2) @@ -889,6 +898,9 @@ packages: resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} engines: {node: '>=6.9.0'} + '@balena/dockerignore@1.0.2': + resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} + '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -1080,6 +1092,10 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@faker-js/faker@9.9.0': + resolution: {integrity: sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA==} + engines: {node: '>=18.0.0', npm: '>=9.0.0'} + '@graphql-tools/merge@8.4.2': resolution: {integrity: sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw==} peerDependencies: @@ -1118,6 +1134,15 @@ packages: peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + '@grpc/grpc-js@1.13.4': + resolution: {integrity: sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.7.15': + resolution: {integrity: sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==} + engines: {node: '>=6'} + hasBin: true + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -1357,6 +1382,9 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + '@lukeed/csprng@1.1.0': resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} engines: {node: '>=8'} @@ -1656,6 +1684,9 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@testcontainers/postgresql@11.5.1': + resolution: {integrity: sha512-6P1QYIKRkktSVwTuwU0Pke5WbXTkvpLleyQcgknJPbZwhaIsCrhnbZlVzj2g/e+Nf9Lmdy1F2OAai+vUrBq0AQ==} + '@thednp/event-listener@2.0.8': resolution: {integrity: sha512-bZY04sWSn2YWAqcuY/fYy03ynARYHwn8xzYgdqqcHBXsBXhOc+bbWwHyLwW28XAA2NjzjMPZZAM3N5D09i+zEQ==} engines: {node: '>=16', pnpm: '>=8.6.0'} @@ -1707,6 +1738,12 @@ packages: '@types/cookiejar@2.1.5': resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + '@types/docker-modem@3.0.6': + resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} + + '@types/dockerode@3.3.42': + resolution: {integrity: sha512-U1jqHMShibMEWHdxYhj3rCMNCiLx5f35i4e3CEUuW+JSSszc/tVqc6WCAPdhwBymG5R/vgbcceagK0St7Cq6Eg==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -1758,6 +1795,9 @@ packages: '@types/node-fetch@2.6.12': resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==} + '@types/node@18.19.123': + resolution: {integrity: sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==} + '@types/node@20.17.10': resolution: {integrity: sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==} @@ -1776,6 +1816,15 @@ packages: '@types/serve-static@1.15.7': resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} + '@types/ssh2-streams@0.1.12': + resolution: {integrity: sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==} + + '@types/ssh2@0.5.52': + resolution: {integrity: sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==} + + '@types/ssh2@1.15.5': + resolution: {integrity: sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==} + '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -2030,6 +2079,14 @@ packages: aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} + are-we-there-yet@2.0.0: resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} engines: {node: '>=10'} @@ -2057,6 +2114,12 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + + async-lock@1.4.1: + resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==} + async-retry@1.3.3: resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} @@ -2070,6 +2133,9 @@ packages: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} + b4a@1.6.7: + resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} + babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2116,6 +2182,36 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bare-events@2.6.1: + resolution: {integrity: sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==} + + bare-fs@4.2.0: + resolution: {integrity: sha512-oRfrw7gwwBVAWx9S5zPMo2iiOjxyiZE12DmblmMQREgcogbNO0AFaZ+QBxxkEXiPspcpvO/Qtqn8LabUx4uYXg==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.6.1: + resolution: {integrity: sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + + bare-stream@2.7.0: + resolution: {integrity: sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==} + peerDependencies: + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -2126,6 +2222,9 @@ packages: batch@0.6.1: resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} + bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + bcrypt@5.1.1: resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} engines: {node: '>= 10.0.0'} @@ -2181,6 +2280,10 @@ packages: bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} @@ -2193,10 +2296,18 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + buildcheck@0.0.6: + resolution: {integrity: sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==} + engines: {node: '>=10.0.0'} + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} + byline@5.0.0: + resolution: {integrity: sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==} + engines: {node: '>=0.10.0'} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -2265,6 +2376,9 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} @@ -2367,6 +2481,10 @@ packages: component-emitter@1.3.1: resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -2445,6 +2563,19 @@ packages: typescript: optional: true + cpu-features@0.0.10: + resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==} + engines: {node: '>=10.0.0'} + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + create-jest@29.7.0: resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2503,6 +2634,15 @@ packages: supports-color: optional: true + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decache@4.6.2: resolution: {integrity: sha512-2LPqkLeu8XWHU8qNCS3kcF6sCcb5zIzvWaAHYSvPfwhdd7mHuah29NssMzrTYyHN4F5oFy2ko9OBYxegtU0FEw==} @@ -2576,6 +2716,18 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + docker-compose@1.2.0: + resolution: {integrity: sha512-wIU1eHk3Op7dFgELRdmOYlPYS4gP8HhH1ZmZa13QZF59y0fblzFDFmKPhyc05phCy2hze9OEvNZAsoljrs+72w==} + engines: {node: '>= 6.0.0'} + + docker-modem@5.0.6: + resolution: {integrity: sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==} + engines: {node: '>= 8.0'} + + dockerode@4.0.7: + resolution: {integrity: sha512-R+rgrSRTRdU5mH14PZTCPZtW/zw3HDWNTS/1ZAQpL/5Upe/ye5K9WQkIysu4wBoiMwKynsz0a8qWuGsHgEvSAA==} + engines: {node: '>= 8.0'} + doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} @@ -2839,6 +2991,9 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -2956,6 +3111,9 @@ packages: from@0.1.7: resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -3003,6 +3161,10 @@ packages: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} + get-port@7.1.0: + resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==} + engines: {node: '>=16'} + get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -3557,6 +3719,10 @@ packages: resolution: {integrity: sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==} engines: {node: '>=14.0.0'} + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -3583,6 +3749,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} @@ -3636,6 +3805,9 @@ packages: long@4.0.0: resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -3734,6 +3906,10 @@ packages: resolution: {integrity: sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} @@ -3742,6 +3918,10 @@ packages: resolution: {integrity: sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==} engines: {node: '>= 0.6'} + mime-types@3.0.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} + engines: {node: '>= 0.6'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -3790,6 +3970,9 @@ packages: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -3826,6 +4009,9 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} + nan@2.23.0: + resolution: {integrity: sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -4222,6 +4408,17 @@ packages: propagating-hammerjs@1.5.0: resolution: {integrity: sha512-3PUXWmomwutoZfydC+lJwK1bKCh6sK6jZGB31RUX6+4EXzsbkDZrK4/sVR7gBrvJaEIwpTVyxQUAd29FKkmVdw==} + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + + properties-reader@2.3.0: + resolution: {integrity: sha512-z597WicA7nDZxK12kZqHr2TcvwNU1GCfA5UwfDY/HDp3hXPoPlb5rlEx9bwGTiJnc0OqbBTkU975jDToth8Gxw==} + engines: {node: '>=14'} + + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + engines: {node: '>=12.0.0'} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -4290,6 +4487,9 @@ packages: resolution: {integrity: sha512-cbAdYt0VcnpN2Bekq7PU+k363ZRsPwJoEEJOEtSJQlJXzwaxt3FIo/uL+KeDSGIjJqtkwyge4KQgD2S2kd+CQw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -4369,6 +4569,10 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -4437,6 +4641,10 @@ packages: resolution: {integrity: sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==} engines: {node: '>= 18'} + send@1.2.0: + resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} + engines: {node: '>= 18'} + serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} @@ -4520,6 +4728,9 @@ packages: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} + split-ca@1.0.1: + resolution: {integrity: sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==} + split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -4530,6 +4741,13 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + ssh-remote-port-forward@1.0.4: + resolution: {integrity: sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==} + + ssh2@1.16.0: + resolution: {integrity: sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==} + engines: {node: '>=10.16.0'} + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -4552,6 +4770,9 @@ packages: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} + streamx@2.22.1: + resolution: {integrity: sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==} + string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} @@ -4646,6 +4867,19 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tar-fs@2.1.3: + resolution: {integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==} + + tar-fs@3.1.0: + resolution: {integrity: sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} @@ -4675,6 +4909,12 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + testcontainers@11.5.1: + resolution: {integrity: sha512-YSSP4lSJB8498zTeu4HYTZYgSky54ozBmIDdC8PFU5inj+vBo5hPpilhcYTgmsqsYjrXOJGV7jl0MWByS7GwuA==} + + text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -4695,6 +4935,10 @@ packages: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} + tmp@0.2.5: + resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} + engines: {node: '>=14.14'} + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -4786,6 +5030,9 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -4835,9 +5082,16 @@ packages: resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} engines: {node: '>=8'} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici@7.14.0: + resolution: {integrity: sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==} + engines: {node: '>=20.18.1'} + unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -5051,6 +5305,11 @@ packages: yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -5074,6 +5333,10 @@ packages: zhead@2.2.4: resolution: {integrity: sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==} + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} + zod@3.24.2: resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} @@ -6027,6 +6290,8 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + '@balena/dockerignore@1.0.2': {} + '@bcoe/v8-coverage@0.2.3': {} '@colors/colors@1.5.0': @@ -6091,7 +6356,7 @@ snapshots: object-assign: 4.1.1 open: 8.4.0 proxy-middleware: 0.15.0 - send: 1.1.0 + send: 1.2.0 serve-index: 1.9.1 transitivePeerDependencies: - supports-color @@ -6208,6 +6473,8 @@ snapshots: '@eslint/js@8.57.1': {} + '@faker-js/faker@9.9.0': {} + '@graphql-tools/merge@8.4.2(graphql@16.10.0)': dependencies: '@graphql-tools/utils': 9.2.1(graphql@16.10.0) @@ -6254,6 +6521,18 @@ snapshots: dependencies: graphql: 16.10.0 + '@grpc/grpc-js@1.13.4': + dependencies: + '@grpc/proto-loader': 0.7.15 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.7.15': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.5.4 + yargs: 17.7.2 + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -6603,6 +6882,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@js-sdsl/ordered-map@4.4.2': {} + '@lukeed/csprng@1.1.0': {} '@mapbox/node-pre-gyp@1.0.11': @@ -6800,7 +7081,7 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.1 - '@nestjs/terminus@11.0.0(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.9(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.0.9)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/terminus@11.0.0(@grpc/grpc-js@1.13.4)(@grpc/proto-loader@0.7.15)(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.9(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.0.9)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: '@nestjs/common': 11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 11.0.9(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.0.9)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -6808,6 +7089,9 @@ snapshots: check-disk-space: 3.4.0 reflect-metadata: 0.2.2 rxjs: 7.8.1 + optionalDependencies: + '@grpc/grpc-js': 1.13.4 + '@grpc/proto-loader': 0.7.15 '@nestjs/testing@11.0.9(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.9(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.0.9)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.0.9(@nestjs/common@11.0.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.9))': dependencies: @@ -6885,6 +7169,13 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@testcontainers/postgresql@11.5.1': + dependencies: + testcontainers: 11.5.1 + transitivePeerDependencies: + - bare-buffer + - supports-color + '@thednp/event-listener@2.0.8': {} '@thednp/position-observer@1.0.7': @@ -6943,6 +7234,17 @@ snapshots: '@types/cookiejar@2.1.5': {} + '@types/docker-modem@3.0.6': + dependencies: + '@types/node': 20.17.10 + '@types/ssh2': 1.15.5 + + '@types/dockerode@3.3.42': + dependencies: + '@types/docker-modem': 3.0.6 + '@types/node': 20.17.10 + '@types/ssh2': 1.15.5 + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -7007,6 +7309,10 @@ snapshots: '@types/node': 20.17.10 form-data: 4.0.1 + '@types/node@18.19.123': + dependencies: + undici-types: 5.26.5 + '@types/node@20.17.10': dependencies: undici-types: 6.19.8 @@ -7032,6 +7338,19 @@ snapshots: '@types/node': 20.17.10 '@types/send': 0.17.4 + '@types/ssh2-streams@0.1.12': + dependencies: + '@types/node': 20.17.10 + + '@types/ssh2@0.5.52': + dependencies: + '@types/node': 20.17.10 + '@types/ssh2-streams': 0.1.12 + + '@types/ssh2@1.15.5': + dependencies: + '@types/node': 18.19.123 + '@types/stack-utils@2.0.3': {} '@types/superagent@8.1.9': @@ -7326,6 +7645,26 @@ snapshots: aproba@2.0.0: {} + archiver-utils@5.0.2: + dependencies: + glob: 10.4.5 + graceful-fs: 4.2.11 + is-stream: 2.0.1 + lazystream: 1.0.1 + lodash: 4.17.21 + normalize-path: 3.0.0 + readable-stream: 4.6.0 + + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.6.0 + readdir-glob: 1.1.3 + tar-stream: 3.1.7 + zip-stream: 6.0.1 + are-we-there-yet@2.0.0: dependencies: delegates: 1.0.0 @@ -7347,6 +7686,12 @@ snapshots: asap@2.0.6: {} + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + + async-lock@1.4.1: {} + async-retry@1.3.3: dependencies: retry: 0.13.1 @@ -7357,6 +7702,8 @@ snapshots: atomic-sleep@1.0.0: {} + b4a@1.6.7: {} + babel-jest@29.7.0(@babel/core@7.26.0): dependencies: '@babel/core': 7.26.0 @@ -7440,6 +7787,31 @@ snapshots: balanced-match@1.0.2: {} + bare-events@2.6.1: + optional: true + + bare-fs@4.2.0: + dependencies: + bare-events: 2.6.1 + bare-path: 3.0.0 + bare-stream: 2.7.0(bare-events@2.6.1) + optional: true + + bare-os@3.6.1: + optional: true + + bare-path@3.0.0: + dependencies: + bare-os: 3.6.1 + optional: true + + bare-stream@2.7.0(bare-events@2.6.1): + dependencies: + streamx: 2.22.1 + optionalDependencies: + bare-events: 2.6.1 + optional: true + base64-js@1.5.1: {} basic-auth@2.0.1: @@ -7448,6 +7820,10 @@ snapshots: batch@0.6.1: {} + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + bcrypt@5.1.1: dependencies: '@mapbox/node-pre-gyp': 1.0.11 @@ -7544,6 +7920,8 @@ snapshots: dependencies: node-int64: 0.4.0 + buffer-crc32@1.0.0: {} + buffer-equal-constant-time@1.0.1: {} buffer-from@1.1.2: {} @@ -7558,10 +7936,15 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + buildcheck@0.0.6: + optional: true + busboy@1.6.0: dependencies: streamsearch: 1.1.0 + byline@5.0.0: {} + bytes@3.1.2: {} c12@2.0.2: @@ -7645,6 +8028,8 @@ snapshots: dependencies: readdirp: 4.0.2 + chownr@1.1.4: {} + chownr@2.0.0: {} chrome-trace-event@1.0.4: {} @@ -7727,6 +8112,14 @@ snapshots: component-emitter@1.3.1: {} + compress-commons@6.0.2: + dependencies: + crc-32: 1.2.2 + crc32-stream: 6.0.0 + is-stream: 2.0.1 + normalize-path: 3.0.0 + readable-stream: 4.6.0 + concat-map@0.0.1: {} concat-stream@1.6.2: @@ -7800,6 +8193,19 @@ snapshots: optionalDependencies: typescript: 5.7.2 + cpu-features@0.0.10: + dependencies: + buildcheck: 0.0.6 + nan: 2.23.0 + optional: true + + crc-32@1.2.2: {} + + crc32-stream@6.0.0: + dependencies: + crc-32: 1.2.2 + readable-stream: 4.6.0 + create-jest@29.7.0(@types/node@20.17.10)(ts-node@10.9.2(@types/node@20.17.10)(typescript@5.7.2)): dependencies: '@jest/types': 29.6.3 @@ -7855,6 +8261,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.1: + dependencies: + ms: 2.1.3 + decache@4.6.2: dependencies: callsite: 1.0.0 @@ -7902,6 +8312,31 @@ snapshots: dependencies: path-type: 4.0.0 + docker-compose@1.2.0: + dependencies: + yaml: 2.8.1 + + docker-modem@5.0.6: + dependencies: + debug: 4.4.1 + readable-stream: 3.6.2 + split-ca: 1.0.1 + ssh2: 1.16.0 + transitivePeerDependencies: + - supports-color + + dockerode@4.0.7: + dependencies: + '@balena/dockerignore': 1.0.2 + '@grpc/grpc-js': 1.13.4 + '@grpc/proto-loader': 0.7.15 + docker-modem: 5.0.6 + protobufjs: 7.5.4 + tar-fs: 2.1.3 + uuid: 10.0.0 + transitivePeerDependencies: + - supports-color + doctrine@3.0.0: dependencies: esutils: 2.0.3 @@ -8268,6 +8703,8 @@ snapshots: fast-diff@1.3.0: {} + fast-fifo@1.3.2: {} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -8423,6 +8860,8 @@ snapshots: from@0.1.7: {} + fs-constants@1.0.0: {} + fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 @@ -8479,6 +8918,8 @@ snapshots: get-package-type@0.1.0: {} + get-port@7.1.0: {} + get-stream@5.2.0: dependencies: pump: 3.0.2 @@ -9209,6 +9650,10 @@ snapshots: kysely@0.27.5: {} + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + leven@3.1.0: {} levn@0.4.1: @@ -9230,6 +9675,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.camelcase@4.3.0: {} + lodash.debounce@4.0.8: {} lodash.includes@4.3.0: {} @@ -9267,6 +9714,8 @@ snapshots: long@4.0.0: {} + long@5.3.2: {} + lru-cache@10.4.3: {} lru-cache@11.0.2: {} @@ -9340,6 +9789,8 @@ snapshots: mime-db@1.53.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 @@ -9348,6 +9799,10 @@ snapshots: dependencies: mime-db: 1.53.0 + mime-types@3.0.1: + dependencies: + mime-db: 1.54.0 + mime@1.6.0: {} mime@2.6.0: {} @@ -9385,6 +9840,8 @@ snapshots: minipass: 3.3.6 yallist: 4.0.0 + mkdirp-classic@0.5.3: {} + mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -9428,6 +9885,9 @@ snapshots: mute-stream@2.0.0: {} + nan@2.23.0: + optional: true + natural-compare@1.4.0: {} negotiator@0.6.3: {} @@ -9808,6 +10268,31 @@ snapshots: dependencies: hammerjs: 2.0.8 + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + + properties-reader@2.3.0: + dependencies: + mkdirp: 1.0.4 + + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 20.17.10 + long: 5.3.2 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -9891,6 +10376,10 @@ snapshots: process: 0.11.10 string_decoder: 1.3.0 + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.6 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -9957,6 +10446,8 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 + retry@0.12.0: {} + retry@0.13.1: {} reusify@1.0.4: {} @@ -10045,6 +10536,22 @@ snapshots: transitivePeerDependencies: - supports-color + send@1.2.0: + dependencies: + debug: 4.4.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.0 + mime-types: 3.0.1 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 @@ -10150,6 +10657,8 @@ snapshots: source-map@0.7.4: {} + split-ca@1.0.1: {} + split2@4.2.0: {} split@1.0.1: @@ -10158,6 +10667,19 @@ snapshots: sprintf-js@1.0.3: {} + ssh-remote-port-forward@1.0.4: + dependencies: + '@types/ssh2': 0.5.52 + ssh2: 1.16.0 + + ssh2@1.16.0: + dependencies: + asn1: 0.2.6 + bcrypt-pbkdf: 1.0.2 + optionalDependencies: + cpu-features: 0.0.10 + nan: 2.23.0 + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -10175,6 +10697,13 @@ snapshots: streamsearch@1.1.0: {} + streamx@2.22.1: + dependencies: + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + optionalDependencies: + bare-events: 2.6.1 + string-length@4.0.2: dependencies: char-regex: 1.0.2 @@ -10279,6 +10808,37 @@ snapshots: tapable@2.2.1: {} + tar-fs@2.1.3: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.2 + tar-stream: 2.2.0 + + tar-fs@3.1.0: + dependencies: + pump: 3.0.2 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.2.0 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-buffer + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar-stream@3.1.7: + dependencies: + b4a: 1.6.7 + fast-fifo: 1.3.2 + streamx: 2.22.1 + tar@6.2.1: dependencies: chownr: 2.0.0 @@ -10310,6 +10870,31 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 + testcontainers@11.5.1: + dependencies: + '@balena/dockerignore': 1.0.2 + '@types/dockerode': 3.3.42 + archiver: 7.0.1 + async-lock: 1.4.1 + byline: 5.0.0 + debug: 4.4.1 + docker-compose: 1.2.0 + dockerode: 4.0.7 + get-port: 7.1.0 + proper-lockfile: 4.1.2 + properties-reader: 2.3.0 + ssh-remote-port-forward: 1.0.4 + tar-fs: 3.1.0 + tmp: 0.2.5 + undici: 7.14.0 + transitivePeerDependencies: + - bare-buffer + - supports-color + + text-decoder@1.2.3: + dependencies: + b4a: 1.6.7 + text-table@0.2.0: {} thread-stream@2.7.0: @@ -10329,6 +10914,8 @@ snapshots: dependencies: os-tmpdir: 1.0.2 + tmp@0.2.5: {} + tmpl@1.0.5: {} to-regex-range@5.0.1: @@ -10421,6 +11008,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tweetnacl@0.14.5: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -10457,8 +11046,12 @@ snapshots: dependencies: '@lukeed/csprng': 1.1.0 + undici-types@5.26.5: {} + undici-types@6.19.8: {} + undici@7.14.0: {} + unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-match-property-ecmascript@2.0.0: @@ -10648,6 +11241,8 @@ snapshots: yallist@4.0.0: {} + yaml@2.8.1: {} + yargs-parser@21.1.1: {} yargs@17.7.2: @@ -10668,4 +11263,10 @@ snapshots: zhead@2.2.4: {} + zip-stream@6.0.1: + dependencies: + archiver-utils: 5.0.2 + compress-commons: 6.0.2 + readable-stream: 4.6.0 + zod@3.24.2: {} diff --git a/test/@types/globals.d.ts b/test/@types/globals.d.ts new file mode 100644 index 0000000..4f2f422 --- /dev/null +++ b/test/@types/globals.d.ts @@ -0,0 +1,12 @@ +import { StartedPostgreSqlContainer } from '@testcontainers/postgresql'; + +declare global { + // eslint-disable-next-line no-var + var __TEST__: boolean; + // eslint-disable-next-line no-var + var __Container__: { + postgres: StartedPostgreSqlContainer | null; + }; +} + +export {}; diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts new file mode 100644 index 0000000..0a27ea2 --- /dev/null +++ b/test/app.e2e-spec.ts @@ -0,0 +1,17 @@ +import { TestingModule } from '@nestjs/testing'; +import { TestModuleFactory } from './factory/test-module.factory'; +import { INestApplication } from '@nestjs/common'; + +describe('AppController (e2e)', () => { + let testingModule: TestingModule; + let app: INestApplication; + + beforeAll(async () => { + testingModule = await TestModuleFactory.createTestModule(); + app = testingModule.createNestApplication(); + await app.init(); + }, 60000); + it('should return "Hello World!"', () => { + expect(true).toBe(true); + }); +}); diff --git a/test/factory/test-module.factory.ts b/test/factory/test-module.factory.ts new file mode 100644 index 0000000..27e04e7 --- /dev/null +++ b/test/factory/test-module.factory.ts @@ -0,0 +1,41 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { createTestConfig } from '../helpers/test-configuration'; +import { TestAppModule } from '../test-app.module'; +import { StartedPostgreSqlContainer } from '@testcontainers/postgresql'; +export class TestModuleFactory { + static async createTestModule(): Promise { + const postgresContainer = globalThis.__Container__ + .postgres as StartedPostgreSqlContainer; + + // this is to ignore warning from env not found error. does not matter what we put + process.env.DATABASE_URL = postgresContainer.getConnectionUri(); + process.env.SECRET = 'ASD'; + const testConfig = createTestConfig( + postgresContainer.getConnectionUri() + '?sslmode=disable', + ); + const moduleRef = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({ + load: [() => testConfig], + isGlobal: true, + }), + TestAppModule, + ], + }) + .overrideProvider(ConfigService) + .useValue({ + get: (key: string) => { + const keys = key.split('.'); + let value = testConfig; + for (const k of keys) { + value = value[k]; + } + return value; + }, + }) + .compile(); + + return moduleRef; + } +} diff --git a/test/helpers/test-configuration.ts b/test/helpers/test-configuration.ts new file mode 100644 index 0000000..1b9d319 --- /dev/null +++ b/test/helpers/test-configuration.ts @@ -0,0 +1,17 @@ +// test/config/test-configuration.ts +import { AppConfig, LoggerFormat } from '../../src/config/configuration'; + +export const createTestConfig = (databaseUrl: string): AppConfig => ({ + corsMaxAge: 86400, + database: { + poolSize: 5, + url: databaseUrl, + }, + port: 3000, + secret: 'kugk2iz30q5mlc6056der8sdnadibb', + logger: { + format: LoggerFormat.Json, + level: 'error', + }, + isDevEnv: false, +}); diff --git a/test/helpers/test-container.helper.ts b/test/helpers/test-container.helper.ts new file mode 100644 index 0000000..1e9018b --- /dev/null +++ b/test/helpers/test-container.helper.ts @@ -0,0 +1,102 @@ +import { + PostgreSqlContainer, + StartedPostgreSqlContainer, +} from '@testcontainers/postgresql'; +import { DB } from 'database/schema/db'; +import { + CamelCasePlugin, + FileMigrationProvider, + Kysely, + Migrator, + PostgresDialect, +} from 'kysely'; +import { join } from 'path'; +import { promises as fs } from 'fs'; +import * as path from 'path'; + +import { Pool } from 'pg'; + +export class TestContainerHelper { + private static instance: TestContainerHelper; + private static container: StartedPostgreSqlContainer; + + private constructor() {} + + public static getInstance(): TestContainerHelper { + if (!TestContainerHelper.instance) { + TestContainerHelper.instance = new TestContainerHelper(); + } + return TestContainerHelper.instance; + } + + public async startPostgresContainer(): Promise { + if (!TestContainerHelper.container) { + TestContainerHelper.container = await new PostgreSqlContainer( + 'postgres:15-alpine', + ).start(); + console.log('PostgreSQL container started !!!'); + + const db = new Kysely({ + dialect: new PostgresDialect({ + pool: new Pool({ + connectionString: `${TestContainerHelper.container.getConnectionUri()}?sslmode=disable`, + max: 5, + }), + }), + plugins: [new CamelCasePlugin()], + }); + await this.runMigrations(db); + await db.destroy(); + } + return TestContainerHelper.container; + } + + public async stopPostgresContainer(): Promise { + if (TestContainerHelper.container) { + await TestContainerHelper.container.stop(); + TestContainerHelper.container = undefined; + } + } + + private async runMigrations(db: Kysely): Promise { + console.log('Running migrations...'); + + const migrationsPath = join(__dirname, '../../src/database/migrations'); + + try { + await fs.access(migrationsPath); + + const migrator = new Migrator({ + db: db, + provider: new FileMigrationProvider({ + fs, + path, + migrationFolder: migrationsPath, + }), + allowUnorderedMigrations: true, + }); + + const { error, results } = await migrator.migrateToLatest(); + + results?.forEach((it) => { + if (it.status === 'Success') { + console.log( + `Migration "${it.migrationName}" was executed successfully`, + ); + } else if (it.status === 'Error') { + console.error(`Failed to execute migration "${it.migrationName}"`); + } + }); + + if (error) { + console.error('Failed to migrate:', error); + throw error; + } + + console.log('Migrations completed successfully'); + } catch (error) { + console.error('Migration error:', error); + throw error; + } + } +} diff --git a/test/jest-e2e.json b/test/jest-e2e.json index e9d912f..d643cd4 100644 --- a/test/jest-e2e.json +++ b/test/jest-e2e.json @@ -1,9 +1,17 @@ { "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": ".", + "rootDir": "./", + "moduleNameMapper": { + "^src/(.*)$": "/src/$1" + }, + "modulePaths": [""], + "moduleDirectories": ["/", "node_modules", "src", "/../"], "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" - } + }, + "globalSetup": "./setup/global-setup.ts", + "globalTeardown": "./setup/global-teardown.ts", + "setupFilesAfterEnv": ["/@types/globals.d.ts"] } diff --git a/test/mocks/user-mocks.ts b/test/mocks/user-mocks.ts new file mode 100644 index 0000000..6f12381 --- /dev/null +++ b/test/mocks/user-mocks.ts @@ -0,0 +1,12 @@ +const MOCK_USERS = { + user1: { + email: 'user1@example.com', + password: 'password1', + }, + user2: { + email: 'user2@example.com', + password: 'password2', + }, +}; + +export default MOCK_USERS; diff --git a/test/modules/auth.e2e-spec.ts b/test/modules/auth.e2e-spec.ts new file mode 100644 index 0000000..2673943 --- /dev/null +++ b/test/modules/auth.e2e-spec.ts @@ -0,0 +1,156 @@ +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { TestingModule } from '@nestjs/testing'; +import { TestModuleFactory } from '../factory/test-module.factory'; +import { ValidationException } from 'common/exceptions/validation.exception'; +import { BaseExceptionsFilter } from 'common/filters/base-exception.filter'; +import { AllExceptionsFilter } from 'common/filters/all-exception.filter'; +import MOCK_USERS from '../mocks/user-mocks'; +import request from 'supertest'; + +describe('AuthModule', () => { + let testingModule: TestingModule; + let app: INestApplication; + let registeredUser: { email: string; password: string }; + let authToken = ''; + beforeAll(async () => { + testingModule = await TestModuleFactory.createTestModule(); + app = testingModule.createNestApplication(); + + app.useGlobalFilters(new AllExceptionsFilter(), new BaseExceptionsFilter()); + app.useGlobalPipes( + new ValidationPipe({ + exceptionFactory: (errors) => { + return new ValidationException(errors); + }, + transform: true, + whitelist: true, + validationError: { target: false }, + }), + ); + + await app.init(); + }); + + afterAll(async () => { + await app.close(); + }); + const generateValidUserDto = () => ({ + email: MOCK_USERS.user2.email, + password: MOCK_USERS.user2.password, + }); + describe('Authentication Flow', () => { + beforeAll(async () => { + // Ensure we have a registered user for login tests + if (!registeredUser) { + registeredUser = generateValidUserDto(); + await request(app.getHttpServer()) + .post('/users/register') + .send(registeredUser) + .expect(201); + } + }); + + describe('POST /auth/login', () => { + it('should login with valid credentials', async () => { + const response = await request(app.getHttpServer()) + .post('/auth/login') + .send({ + login: registeredUser.email, + password: registeredUser.password, + }) + .expect(200); + + expect(response.body).toHaveProperty('accessToken'); + expect(response.body).toHaveProperty('user'); + expect(response.body.user).toHaveProperty( + 'email', + registeredUser.email.toLowerCase(), + ); + + authToken = response.body.accessToken; + }); + + it('should return 401 with invalid email', async () => { + const response = await request(app.getHttpServer()) + .post('/auth/login') + .send({ + login: 'nonexistent@example.com', + password: registeredUser.password, + }) + .expect(401); + + expect(response.body.message).toContain('Invalid login or password'); + }); + + it('should return 401 with invalid password', async () => { + const response = await request(app.getHttpServer()) + .post('/auth/login') + .send({ + login: registeredUser.email, + password: 'wrongpassword', + }) + .expect(401); + + expect(response.body.message).toContain('Invalid login or password'); + }); + + it('should return 400 when login is missing', async () => { + const response = await request(app.getHttpServer()) + .post('/auth/login') + .send({ + password: registeredUser.password, + }) + .expect(400); + + expect(response.body.message).toBe('Validation Failed'); + expect(response.body.errors).toBeDefined(); + }); + + it('should return 400 when password is missing', async () => { + const response = await request(app.getHttpServer()) + .post('/auth/login') + .send({ + login: registeredUser.email, + }) + .expect(400); + + expect(response.body.message).toBe('Validation Failed'); + expect(response.body.errors).toBeDefined(); + }); + }); + + describe('GET /auth/me', () => { + it('should return current user when authenticated', async () => { + const response = await request(app.getHttpServer()) + .get('/auth/me') + .set('Authorization', `Bearer ${authToken}`) + .expect(200); + + expect(response.body).toHaveProperty('user'); + expect(response.body.user).toHaveProperty( + 'email', + registeredUser.email.toLowerCase(), + ); + expect(response.body.user).toHaveProperty('id'); + expect(response.body.user).not.toHaveProperty('password'); + }); + + it('should return 401 when not authenticated', async () => { + const response = await request(app.getHttpServer()) + .get('/auth/me') + .expect(401); + + expect(response.body.message).toContain('Unauthorized'); + }); + + it('should return 401 with malformed authorization header', async () => { + const response = await request(app.getHttpServer()) + .get('/auth/me') + .set('Authorization', 'InvalidFormat') + .expect(401); + + expect(response.body.message).toContain('Unauthorized'); + }); + }); + }); +}); diff --git a/test/modules/user.e2e-spec.ts b/test/modules/user.e2e-spec.ts new file mode 100644 index 0000000..54b2d09 --- /dev/null +++ b/test/modules/user.e2e-spec.ts @@ -0,0 +1,216 @@ +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { TestingModule } from '@nestjs/testing'; +import { TestModuleFactory } from '../factory/test-module.factory'; +import { faker } from '@faker-js/faker'; +import request from 'supertest'; +import { ValidationException } from 'common/exceptions/validation.exception'; +import { BaseExceptionsFilter } from 'common/filters/base-exception.filter'; +import { AllExceptionsFilter } from 'common/filters/all-exception.filter'; + +describe('UserModule', () => { + let testingModule: TestingModule; + let app: INestApplication; + + beforeAll(async () => { + testingModule = await TestModuleFactory.createTestModule(); + app = testingModule.createNestApplication(); + + app.useGlobalFilters(new AllExceptionsFilter(), new BaseExceptionsFilter()); + app.useGlobalPipes( + new ValidationPipe({ + exceptionFactory: (errors) => { + return new ValidationException(errors); + }, + transform: true, + whitelist: true, + validationError: { target: false }, + }), + ); + + await app.init(); + }); + + afterAll(async () => { + await app.close(); + }); + + const generateValidUserDto = () => ({ + email: faker.internet.email(), + password: faker.internet.password({ length: 8 }), + }); + + describe('POST /users/register', () => { + it('should register a user with valid data', async () => { + const userDto = generateValidUserDto(); + const response = await request(app.getHttpServer()) + .post('/users/register') + .send(userDto) + .expect(201); + + expect(response.body).toEqual({ + message: 'Account created', + }); + }); + + it('should return 400 when email is invalid', async () => { + const userDto = { + email: 'invalid-email', + password: faker.internet.password({ length: 8 }), + }; + + const response = await request(app.getHttpServer()) + .post('/users/register') + .send(userDto) + .expect(400); + + expect(response.body.message).toBe('Validation Failed'); + expect(response.body.errors).toBeDefined(); + }); + + it('should return 400 when email is missing', async () => { + const userDto = { + password: faker.internet.password({ length: 8 }), + }; + + const response = await request(app.getHttpServer()) + .post('/users/register') + .send(userDto) + .expect(400); + + expect(response.body.message).toBe('Validation Failed'); + expect(response.body.errors).toBeDefined(); + }); + + it('should return 400 when password is missing', async () => { + const userDto = { + email: faker.internet.email(), + }; + + const response = await request(app.getHttpServer()) + .post('/users/register') + .send(userDto) + .expect(400); + + expect(response.body.message).toBe('Validation Failed'); + expect(response.body.errors).toBeDefined(); + }); + + it('should return 400 when password is empty', async () => { + const userDto = { + email: faker.internet.email(), + password: '', + }; + + const response = await request(app.getHttpServer()) + .post('/users/register') + .send(userDto) + .expect(400); + + expect(response.body.message).toBe('Validation Failed'); + expect(response.body.errors).toBeDefined(); + }); + + it('should return 409 when email is already taken', async () => { + // First, register a user + const userDto = generateValidUserDto(); + await request(app.getHttpServer()) + .post('/users/register') + .send(userDto) + .expect(201); + + // Try to register with the same email + const response = await request(app.getHttpServer()) + .post('/users/register') + .send(userDto) + .expect(409); + + expect(response.body.message).toContain('already in use'); + }); + + it('should return 400 when request body is empty', async () => { + const response = await request(app.getHttpServer()) + .post('/users/register') + .send({}) + .expect(400); + + expect(response.body.message).toBe('Validation Failed'); + expect(response.body.errors).toBeDefined(); + }); + }); + + describe('Edge Cases and Security', () => { + it('should handle very long email addresses', async () => { + const longEmail = 'a'.repeat(200) + '@example.com'; + const userDto = { + email: longEmail, + password: faker.internet.password({ length: 8 }), + }; + + // There seems to be validation for long emails + const response = await request(app.getHttpServer()) + .post('/users/register') + .send(userDto) + .expect(400); + + expect(response.body.message).toBe('Validation Failed'); + expect(response.body.errors).toBeDefined(); + }); + + it('should handle special characters in password', async () => { + const userDto = { + email: faker.internet.email(), + password: '!@#$%^&*()_+-=[]{}|;:,.<>?', + }; + + const response = await request(app.getHttpServer()) + .post('/users/register') + .send(userDto) + .expect(201); + + expect(response.body.message).toBe('Account created'); + }); + + it('should handle unicode characters in email', async () => { + const userDto = { + email: `test-unicode-${Date.now()}@example.com`, + password: faker.internet.password({ length: 8 }), + }; + + const response = await request(app.getHttpServer()) + .post('/users/register') + .send(userDto) + .expect(201); + + expect(response.body.message).toBe('Account created'); + }); + + it('should be case insensitive for email comparison', async () => { + const baseEmail = faker.internet.email().toLowerCase(); + const upperEmail = baseEmail.toUpperCase(); + + const userDto1 = { + email: baseEmail, + password: faker.internet.password({ length: 8 }), + }; + + const userDto2 = { + email: upperEmail, + password: faker.internet.password({ length: 8 }), + }; + + // Register with lowercase email + await request(app.getHttpServer()) + .post('/users/register') + .send(userDto1) + .expect(201); + + // Try to register with uppercase email - should fail since emails are normalized to lowercase + const response = await request(app.getHttpServer()) + .post('/users/register') + .send(userDto2) + .expect(409); + + expect(response.body.message).toContain('already in use'); + }); + }); +}); diff --git a/test/setup/global-setup.ts b/test/setup/global-setup.ts new file mode 100644 index 0000000..9fb7040 --- /dev/null +++ b/test/setup/global-setup.ts @@ -0,0 +1,20 @@ +import { StartedPostgreSqlContainer } from '@testcontainers/postgresql'; +import { TestContainerHelper } from '../helpers/test-container.helper'; + +globalThis.__TEST__ = true; +globalThis.__Container__ = { + postgres: null as StartedPostgreSqlContainer | null, +}; + +export default async function globalSetup() { + console.log('🚀 Global Setup: Starting...'); + + try { + const postgresContainer = + await TestContainerHelper.getInstance().startPostgresContainer(); + globalThis.__Container__.postgres = postgresContainer; + } catch (error) { + console.error('❌ Global Setup: Failed to initialize container:', error); + throw error; + } +} diff --git a/test/setup/global-teardown.ts b/test/setup/global-teardown.ts new file mode 100644 index 0000000..5e55687 --- /dev/null +++ b/test/setup/global-teardown.ts @@ -0,0 +1,3 @@ +export default async function globalTeardown() { + console.log('🚀 Global Teardown: Stopping...'); +} diff --git a/test/test-app.module.ts b/test/test-app.module.ts new file mode 100644 index 0000000..4a69348 --- /dev/null +++ b/test/test-app.module.ts @@ -0,0 +1,59 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { APP_GUARD } from '@nestjs/core'; +import { DevtoolsModule } from '@nestjs/devtools-integration'; +import { JwtGuard } from 'common/guards'; +import { AppConfig, LoggerConfig, LoggerFormat } from 'config/configuration'; +import { DatabaseModule } from 'database/database.module'; +import { AuthModule } from 'modules/auth/auth.module'; +import { HealthModule } from 'modules/health/health.module'; +import { UserModule } from 'modules/user/user.module'; +import { Logger, LoggerModule } from 'nestjs-pino'; + +@Module({ + imports: [ + DevtoolsModule.register({ + http: process.env.NODE_ENV !== 'test', + }), + LoggerModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: (config: ConfigService) => { + const loggerConfig = config.get('logger'); + + return { + pinoHttp: { + level: loggerConfig.level, + transport: + loggerConfig.format === LoggerFormat.Pretty + ? { target: 'pino-pretty' } + : undefined, + useLevelLabels: true, + formatters: { + level: (label: string) => { + return { level: label }; + }, + }, + autoLogging: false, + }, + }; + }, + }), + DatabaseModule, + + // Http modules + AuthModule, + UserModule, + HealthModule, + ], + providers: [ + Logger, + // we set all routes to be private by default + // use `@Public()` to make them public + { + provide: APP_GUARD, + useClass: JwtGuard, + }, + ], +}) +export class TestAppModule {}