diff --git a/docs/build-latest-version.sh b/docs/build-latest-version.sh old mode 100644 new mode 100755 index 06a62d817f..e8004f169d --- a/docs/build-latest-version.sh +++ b/docs/build-latest-version.sh @@ -1,9 +1,33 @@ #!/bin/bash set -e -jq '[.[0]]' versions.json > tmp_versions.json -mv tmp_versions.json versions.json +CONFIG_FILE="docusaurus.config.js" +# Extract lastVersion from docusaurus.config.js using sed +LAST_VERSION=$(sed -n "s/.*lastVersion: *'\\([^']*\\)'.*/\\1/p" "$CONFIG_FILE") +if [ -z "$LAST_VERSION" ]; then + echo "Error: lastVersion not found in $CONFIG_FILE" + exit 1 +fi +echo "Found lastVersion: $LAST_VERSION" + +# Extract all version numbers from the entire file +ALL_VERSIONS=$(grep -oE "'[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z]+)?'" "$CONFIG_FILE" | sed "s/'//g" | sort -u -V -r) +if [ -z "$ALL_VERSIONS" ]; then + echo "Error: No versions found in $CONFIG_FILE" + exit 1 +fi +echo "Found raw versions:" +echo "$ALL_VERSIONS" + +# Convert the extracted versions into a JSON array format +VERSION_ARRAY=$(echo "$ALL_VERSIONS" | jq -R -s -c 'split("\n")[:-1] + ["'"$LAST_VERSION"'"] | unique') +echo "Updating versions.json with: $VERSION_ARRAY" + +# Update versions.json with combined data +echo $VERSION_ARRAY | jq . > versions.json + +# Install dependencies and build the project npm i && npm run build -exec "$@" +exec "$@" \ No newline at end of file diff --git a/server/jest.config.ts b/server/jest.config.ts index 1a2d613ac7..11ba2beec6 100644 --- a/server/jest.config.ts +++ b/server/jest.config.ts @@ -1,22 +1,29 @@ -module.exports = async () => { - return { - verbose: true, - moduleFileExtensions: ['js', 'json', 'ts'], - rootDir: '.', - testEnvironment: 'node', - testRegex: '.spec.ts$', - testPathIgnorePatterns: ['.e2e-spec.ts$'], - transform: { - '^.+\\.(t|j)s$': 'ts-jest', - }, - moduleNameMapper: { - 'dist/src/entities/(.*)': '/dist/src/entities/$1', - '^src/(.*)': '/src/$1', - '@dto/(.*)': '/src/dto/$1', - '@plugins/(.*)': '/plugins/$1', - '@services/(.*)': '/src/services/$1', - '@controllers/(.*)': '/src/controllers/$1', - '@ee/(.*)': '/ee/$1', - }, - }; +import type { Config } from '@jest/types'; + +const config: Config.InitialOptions = { + verbose: true, + moduleFileExtensions: ['js', 'json', 'ts', 'node'], + rootDir: '.', + testEnvironment: 'node', + testRegex: '.spec.ts$', + transform: { + '^.+\\.(t|j)s$': [ + 'ts-jest', + { + tsconfig: 'tsconfig.json', + }, + ], + }, + moduleNameMapper: { + '^src/(.*)': '/src/$1', + '@dto/(.*)': '/src/dto/$1', + '@plugins/(.*)': '/plugins/$1', + '@services/(.*)': '/src/services/$1', + '@entities/(.*)': '/src/entities/$1', + '@controllers/(.*)': '/src/controllers/$1', + '@ee/(.*)': '/ee/$1', + }, + testTimeout: 30000, }; + +export default config; diff --git a/server/package-lock.json b/server/package-lock.json index d5c1c6571b..6678b4b13a 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -67,13 +67,16 @@ "@golevelup/ts-jest": "^0.3.2", "@nestjs/schematics": "^8.0.0", "@nestjs/testing": "^8.0.0", + "@pollyjs/adapter-node-http": "^6.0.6", + "@pollyjs/core": "^6.0.6", + "@pollyjs/persister-fs": "^6.0.6", "@types/compression": "^1.7.2", "@types/cookie-parser": "^1.4.3", "@types/express": "^4.17.13", "@types/express-http-proxy": "^1.6.3", "@types/got": "^9.6.12", "@types/humps": "^2.0.1", - "@types/jest": "^27.0.0", + "@types/jest": "^27.5.2", "@types/multer": "^1.4.7", "@types/node": "^16.0.0", "@types/nodemailer": "^6.4.4", @@ -93,8 +96,9 @@ "prettier": "^2.3.2", "preview-email": "^3.0.19", "rimraf": "^3.0.2", + "setup-polly-jest": "^0.11.0", "supertest": "^6.1.3", - "ts-jest": "^29.1.1", + "ts-jest": "^29.1.5", "ts-loader": "^9.2.3", "typescript": "^4.3.5" }, @@ -2776,6 +2780,95 @@ "node": ">=14" } }, + "node_modules/@pollyjs/adapter": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@pollyjs/adapter/-/adapter-6.0.6.tgz", + "integrity": "sha512-szhys0NiFQqCJDMC0kpDyjhLqSI7aWc6m6iATCRKgcMcN/7QN85pb3GmRzvnNV8+/Bi2AUSCwxZljcsKhbYVWQ==", + "dev": true, + "dependencies": { + "@pollyjs/utils": "^6.0.6" + } + }, + "node_modules/@pollyjs/adapter-node-http": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@pollyjs/adapter-node-http/-/adapter-node-http-6.0.6.tgz", + "integrity": "sha512-jdJG7oncmSHZAtVMmRgOxh5A56b7G8H9ULlk/ZaVJ+jNrlFXhLmPpx8OQoSF4Cuq2ugdiWmwmAjFXHStcpY3Mw==", + "dev": true, + "dependencies": { + "@pollyjs/adapter": "^6.0.6", + "@pollyjs/utils": "^6.0.6", + "lodash-es": "^4.17.21", + "nock": "^13.2.1" + } + }, + "node_modules/@pollyjs/core": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@pollyjs/core/-/core-6.0.6.tgz", + "integrity": "sha512-1ZZcmojW8iSFmvHGeLlvuudM3WiDV842FsVvtPAo3HoAYE6jCNveLHJ+X4qvonL4enj1SyTF3hXA107UkQFQrA==", + "dev": true, + "dependencies": { + "@pollyjs/utils": "^6.0.6", + "@sindresorhus/fnv1a": "^2.0.1", + "blueimp-md5": "^2.19.0", + "fast-json-stable-stringify": "^2.1.0", + "is-absolute-url": "^3.0.3", + "lodash-es": "^4.17.21", + "loglevel": "^1.8.0", + "route-recognizer": "^0.3.4", + "slugify": "^1.6.3" + } + }, + "node_modules/@pollyjs/node-server": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@pollyjs/node-server/-/node-server-6.0.6.tgz", + "integrity": "sha512-nkP1+hdNoVOlrRz9R84haXVsaSmo8Xmq7uYK9GeUMSLQy4Fs55ZZ9o2KI6vRA8F6ZqJSbC31xxwwIoTkjyP7Vg==", + "dev": true, + "dependencies": { + "@pollyjs/utils": "^6.0.6", + "body-parser": "^1.19.0", + "cors": "^2.8.5", + "express": "^4.17.1", + "fs-extra": "^10.0.0", + "http-graceful-shutdown": "^3.1.5", + "morgan": "^1.10.0", + "nocache": "^3.0.1" + } + }, + "node_modules/@pollyjs/persister": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@pollyjs/persister/-/persister-6.0.6.tgz", + "integrity": "sha512-9KB1p+frvYvFGur4ifzLnFKFLXAMXrhAhCnVhTnkG2WIqqQPT7y+mKBV/DKCmYFx8GPA9FiNGqt2pB53uJpIdw==", + "dev": true, + "dependencies": { + "@pollyjs/utils": "^6.0.6", + "@types/set-cookie-parser": "^2.4.1", + "bowser": "^2.4.0", + "fast-json-stable-stringify": "^2.1.0", + "lodash-es": "^4.17.21", + "set-cookie-parser": "^2.4.8", + "utf8-byte-length": "^1.0.4" + } + }, + "node_modules/@pollyjs/persister-fs": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@pollyjs/persister-fs/-/persister-fs-6.0.6.tgz", + "integrity": "sha512-/ALVgZiH2zGqwLkW0Mntc0Oq1v7tR8LS8JD2SAyIsHpnSXeBUnfPWwjAuYw0vqORHFVEbwned6MBRFfvU/3qng==", + "dev": true, + "dependencies": { + "@pollyjs/node-server": "^6.0.6", + "@pollyjs/persister": "^6.0.6" + } + }, + "node_modules/@pollyjs/utils": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@pollyjs/utils/-/utils-6.0.6.tgz", + "integrity": "sha512-nhVJoI3nRgRimE0V2DVSvsXXNROUH6iyJbroDu4IdsOIOFC1Ds0w+ANMB4NMwFaqE+AisWOmXFzwAGdAfyiQVg==", + "dev": true, + "dependencies": { + "qs": "^6.10.1", + "url-parse": "^1.5.3" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -2991,6 +3084,15 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, + "node_modules/@sindresorhus/fnv1a": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-2.0.1.tgz", + "integrity": "sha512-suq9tRQ6bkpMukTG5K5z0sPWB7t0zExMzZCdmYm6xTSSIm/yCKNm7VCL36wVeyTsFr597/UhU1OAYdHGMDiHrw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -3333,9 +3435,9 @@ } }, "node_modules/@types/node": { - "version": "16.18.68", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.68.tgz", - "integrity": "sha512-sG3hPIQwJLoewrN7cr0dwEy+yF5nD4D/4FxtQpFciRD/xwUzgD+G05uxZHv5mhfXo4F9Jkp13jjn0CC2q325sg==" + "version": "16.18.101", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.101.tgz", + "integrity": "sha512-AAsx9Rgz2IzG8KJ6tXd6ndNkVcu+GYB6U/SnFAaokSPNx2N7dcIIfnighYUNumvj6YS2q39Dejz5tT0NCV7CWA==" }, "node_modules/@types/nodemailer": { "version": "6.4.14", @@ -3490,6 +3592,15 @@ "@types/node": "*" } }, + "node_modules/@types/set-cookie-parser": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.9.tgz", + "integrity": "sha512-bCorlULvl0xTdjj4BPUHX4cqs9I+go2TfW/7Do1nnFYWS0CPP429Qr1AY42kiFhCwLpvAkWFr1XIBHd8j6/MCQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -4882,6 +4993,18 @@ } ] }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -4934,6 +5057,12 @@ "node": ">= 6" } }, + "node_modules/blueimp-md5": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", + "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", + "dev": true + }, "node_modules/body-parser": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", @@ -4980,6 +5109,12 @@ "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==" }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "dev": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -8532,6 +8667,18 @@ "node": ">= 0.8" } }, + "node_modules/http-graceful-shutdown": { + "version": "3.1.13", + "resolved": "https://registry.npmjs.org/http-graceful-shutdown/-/http-graceful-shutdown-3.1.13.tgz", + "integrity": "sha512-Ci5LRufQ8AtrQ1U26AevS8QoMXDOhnAHCJI3eZu1com7mZGHxREmw3dNj85ftpQokQCvak8nI2pnFS8zyM1M+Q==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/http2-wrapper": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", @@ -8719,6 +8866,15 @@ "node": ">= 0.10" } }, + "node_modules/is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -10653,6 +10809,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "dev": true + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -10761,6 +10923,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loglevel": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", + "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, "node_modules/long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", @@ -11574,6 +11749,49 @@ "node": ">=12.20.0" } }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/mri": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", @@ -11688,6 +11906,29 @@ "lower-case": "^1.1.1" } }, + "node_modules/nocache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", + "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/nock": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.4.tgz", + "integrity": "sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 10.13" + } + }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -12745,6 +12986,15 @@ "node": ">= 6" } }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -12947,6 +13197,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -13130,6 +13386,12 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -13262,6 +13524,12 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" }, + "node_modules/route-recognizer": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/route-recognizer/-/route-recognizer-0.3.4.tgz", + "integrity": "sha512-2+MhsfPhvauN1O8KaXpXAOfR/fwe8dnUXVM+xw7yt40lJRfPVQxV6yryZm0cgRvAj5fMF/mdRZbL2ptwbs5i2g==", + "dev": true + }, "node_modules/run-applescript": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-3.2.0.tgz", @@ -13619,6 +13887,12 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, + "node_modules/set-cookie-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", + "dev": true + }, "node_modules/set-function-length": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", @@ -13643,6 +13917,15 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/setup-polly-jest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/setup-polly-jest/-/setup-polly-jest-0.11.0.tgz", + "integrity": "sha512-3ywsCFGfCvfi3ZpwYyDc4YDPNiB70QtjODoKFD5hbhza1GMOh0ZzAYUZO9OBmo/1isasynxcS5WzKYMyDJUeZw==", + "dev": true, + "peerDependencies": { + "@pollyjs/core": "*" + } + }, "node_modules/sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", @@ -13754,6 +14037,15 @@ "node": "*" } }, + "node_modules/slugify": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/sonic-boom": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.8.0.tgz", @@ -14353,9 +14645,9 @@ } }, "node_modules/ts-jest": { - "version": "29.1.1", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", - "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "version": "29.1.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.5.tgz", + "integrity": "sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg==", "dev": true, "dependencies": { "bs-logger": "0.x", @@ -14371,10 +14663,11 @@ "ts-jest": "cli.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", "@jest/types": "^29.0.0", "babel-jest": "^29.0.0", "jest": "^29.0.0", @@ -14384,6 +14677,9 @@ "@babel/core": { "optional": true }, + "@jest/transform": { + "optional": true + }, "@jest/types": { "optional": true }, @@ -14857,6 +15153,22 @@ "node": ">=6" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/server/package.json b/server/package.json index fa53710307..b29e6ec08c 100644 --- a/server/package.json +++ b/server/package.json @@ -13,11 +13,13 @@ "start:dev": "NODE_ENV=development nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "NODE_ENV=production node dist/src/main", - "test": "NODE_ENV=test jest --config jest.config.ts", + "test:record": "NODE_ENV=test POLLY_MODE=record jest --config jest.config.ts --detectOpenHandles", + "test": "NODE_ENV=test jest --config jest.config.ts --detectOpenHandles", "test:watch": "NODE_ENV=test jest --watch", "test:cov": "NODE_ENV=test jest --coverage", "test:debug": "NODE_ENV=test node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "NODE_ENV=test jest --runInBand --config ./test/jest-e2e.json --detectOpenHandles", + "test:e2e:record": "NODE_ENV=test POLLY_MODE=record jest --runInBand --config ./test/jest-e2e.json --detectOpenHandles", "db:create": "ts-node ./scripts/create-database.ts", "db:create:prod": "node dist/scripts/create-database.js ", "db:drop": "ts-node ./scripts/drop-database.ts", @@ -95,13 +97,16 @@ "@golevelup/ts-jest": "^0.3.2", "@nestjs/schematics": "^8.0.0", "@nestjs/testing": "^8.0.0", + "@pollyjs/adapter-node-http": "^6.0.6", + "@pollyjs/core": "^6.0.6", + "@pollyjs/persister-fs": "^6.0.6", "@types/compression": "^1.7.2", "@types/cookie-parser": "^1.4.3", "@types/express": "^4.17.13", "@types/express-http-proxy": "^1.6.3", "@types/got": "^9.6.12", "@types/humps": "^2.0.1", - "@types/jest": "^27.0.0", + "@types/jest": "^27.5.2", "@types/multer": "^1.4.7", "@types/node": "^16.0.0", "@types/nodemailer": "^6.4.4", @@ -121,8 +126,9 @@ "prettier": "^2.3.2", "preview-email": "^3.0.19", "rimraf": "^3.0.2", + "setup-polly-jest": "^0.11.0", "supertest": "^6.1.3", - "ts-jest": "^29.1.1", + "ts-jest": "^29.1.5", "ts-loader": "^9.2.3", "typescript": "^4.3.5" }, diff --git a/server/src/modules/tooljet_db/tooljet-db.types.ts b/server/src/modules/tooljet_db/tooljet-db.types.ts index 22b5c7d52c..ccabaf1978 100644 --- a/server/src/modules/tooljet_db/tooljet-db.types.ts +++ b/server/src/modules/tooljet_db/tooljet-db.types.ts @@ -2,6 +2,50 @@ import { QueryFailedError } from 'typeorm'; import { InternalTable } from 'src/entities/internal_table.entity'; import { capitalize } from 'lodash'; +export const TJDB = { + character_varying: 'character varying' as const, + integer: 'integer' as const, + bigint: 'bigint' as const, + serial: 'serial' as const, + double_precision: 'double precision' as const, + boolean: 'boolean' as const, +}; + +export type TooljetDatabaseDataTypes = (typeof TJDB)[keyof typeof TJDB]; + +export type TooljetDatabaseColumn = { + column_name: string; + data_type: TooljetDatabaseDataTypes; + column_default: string | null; + character_maximum_length: number | null; + numeric_precision: number | null; + constraints_type: { + is_not_null: boolean; + is_primary_key: boolean; + is_unique: boolean; + }; + keytype: string | null; +}; + +export type TooljetDatabaseForeignKey = { + column_names: string[]; + referenced_table_name: string; + referenced_column_names: string[]; + on_update: string; + on_delete: string; + constraint_name: string; + referenced_table_id: string; +}; + +export type TooljetDatabaseTable = { + id: string; + table_name: string; + schema: { + columns: TooljetDatabaseColumn[]; + foreign_keys: TooljetDatabaseForeignKey[]; + }; +}; + enum PostgresErrorCode { UniqueViolation = '23505', CheckViolation = '23514', diff --git a/server/src/services/tooljet_db.service.ts b/server/src/services/tooljet_db.service.ts index 48498b4a84..fb7e3461d1 100644 --- a/server/src/services/tooljet_db.service.ts +++ b/server/src/services/tooljet_db.service.ts @@ -3,28 +3,14 @@ import { EntityManager, In, ObjectLiteral, SelectQueryBuilder, Table, TableColum import { InjectEntityManager } from '@nestjs/typeorm'; import { InternalTable } from 'src/entities/internal_table.entity'; import { isString, isEmpty, camelCase } from 'lodash'; -import { TooljetDatabaseError, TooljetDbActions } from 'src/modules/tooljet_db/tooljet-db.types'; - -export type TableColumnSchema = { - column_name: string; - data_type: SupportedDataTypes; - column_default: string | null; - character_maximum_length: number | null; - numeric_precision: number | null; - is_nullable: 'YES' | 'NO'; - constraint_type: string | null; - keytype: string | null; -}; - -export type ForeignKeyDetails = { - column_names: Array; - referenced_table_name: string; - referenced_column_names: Array; - on_delete: string; - on_update: string; -}; - -export type SupportedDataTypes = 'character varying' | 'integer' | 'bigint' | 'serial' | 'double precision' | 'boolean'; +import { + TooljetDatabaseColumn, + TooljetDatabaseDataTypes, + TooljetDatabaseError, + TooljetDatabaseForeignKey, + TooljetDbActions, + TJDB, +} from 'src/modules/tooljet_db/tooljet-db.types'; // Patching TypeORM SelectQueryBuilder to handle for right and full outer joins declare module 'typeorm' { @@ -91,7 +77,7 @@ export class TooljetDbService { organizationId: string, params, connectionManagers: Record = { appManager: this.manager, tjdbManager: this.tooljetDbManager } - ): Promise<{ foreign_keys: ForeignKeyDetails[]; columns: TableColumnSchema[] }> { + ): Promise<{ foreign_keys: TooljetDatabaseForeignKey[]; columns: TooljetDatabaseColumn[] }> { const { table_name: tableName, id: id } = params; const { appManager, tjdbManager } = connectionManagers; @@ -667,7 +653,7 @@ export class TooljetDbService { if (!tables?.length) throw new BadRequestException('Tables are not chosen'); const tableIdList: Array = tables - .filter((table) => table.type === 'Table') + .filter((table) => table.type === 'Table' && !isEmpty(table.name)) .map((filteredTable) => filteredTable.name); const internalTables = await this.findOrFailInternalTableFromTableId(tableIdList, organizationId); @@ -847,17 +833,17 @@ export class TooljetDbService { } } - private prepareColumnListForCreateTable(columns) { + private prepareColumnListForCreateTable(columns: TooljetDatabaseColumn[]) { const columnList = columns.map((column) => { - const { column_name, constraints_type = {} } = column; + const { column_name, constraints_type = {} as any } = column; const is_primary_key_column = constraints_type?.is_primary_key || false; - const prepareDataTypeAndDefault = (column): { data_type: SupportedDataTypes; column_default: unknown } => { + const prepareDataTypeAndDefault = (column): { data_type: TooljetDatabaseDataTypes; column_default: unknown } => { const { data_type, column_default = undefined } = column; - const isSerial = () => data_type === 'integer' && /^nextval\(/.test(column_default); - const isCharacterVarying = () => data_type === 'character varying'; + const isSerial = () => data_type === TJDB.integer && /^nextval\(/.test(column_default); + const isCharacterVarying = () => data_type === TJDB.character_varying; - if (isSerial()) return { data_type: 'serial', column_default: undefined }; + if (isSerial()) return { data_type: TJDB.serial, column_default: undefined }; if (isCharacterVarying()) return { data_type, column_default: this.addQuotesIfString(column_default) }; return { data_type, column_default }; @@ -876,7 +862,7 @@ export class TooljetDbService { return columnList; } - private prepareForeignKeyDetailsJSON(foreign_keys, referenced_tables_info) { + private prepareForeignKeyDetailsJSON(foreign_keys: TooljetDatabaseForeignKey[], referenced_tables_info) { if (!foreign_keys.length) return []; const foreignKeyList = foreign_keys.map((foreignKeyDetail) => { const { diff --git a/server/src/services/tooljet_db_bulk_upload.service.ts b/server/src/services/tooljet_db_bulk_upload.service.ts index b9d0a4b2a7..982171eb65 100644 --- a/server/src/services/tooljet_db_bulk_upload.service.ts +++ b/server/src/services/tooljet_db_bulk_upload.service.ts @@ -2,12 +2,13 @@ import { BadRequestException, Injectable, NotFoundException, Optional } from '@n import { EntityManager } from 'typeorm'; import { InternalTable } from 'src/entities/internal_table.entity'; import * as csv from 'fast-csv'; -import { SupportedDataTypes, TableColumnSchema, TooljetDbService } from './tooljet_db.service'; +import { TooljetDbService } from './tooljet_db.service'; import { InjectEntityManager } from '@nestjs/typeorm'; import { isEmpty } from 'lodash'; import { pipeline } from 'stream/promises'; import { PassThrough } from 'stream'; import { v4 as uuid } from 'uuid'; +import { TJDB, TooljetDatabaseColumn, TooljetDatabaseDataTypes } from 'src/modules/tooljet_db/tooljet-db.types'; const MAX_ROW_COUNT = 1000; @@ -33,17 +34,17 @@ export class TooljetDbBulkUploadService { throw new NotFoundException(`Table ${tableName} not found`); } - const { columns: internalTableColumnSchema }: { columns: TableColumnSchema[] } = + const { columns: internalTableDatabaseColumn }: { columns: TooljetDatabaseColumn[] } = await this.tooljetDbService.perform(organizationId, 'view_table', { table_name: tableName, }); - return await this.bulkUploadCsv(internalTable.id, internalTableColumnSchema, fileBuffer); + return await this.bulkUploadCsv(internalTable.id, internalTableDatabaseColumn, fileBuffer); } async bulkUploadCsv( internalTableId: string, - internalTableColumnSchema: TableColumnSchema[], + internalTableDatabaseColumn: TooljetDatabaseColumn[], fileBuffer: Buffer ): Promise<{ processedRows: number }> { const rowsToUpsert = []; @@ -53,17 +54,17 @@ export class TooljetDbBulkUploadService { strictColumnHandling: true, discardUnmappedColumns: true, }); - const primaryKeyColumnSchema = internalTableColumnSchema.filter( + const primaryKeyColumnSchema = internalTableDatabaseColumn.filter( (colDetails) => colDetails.keytype === 'PRIMARY KEY' ); const primaryKeyValuesToUpsert = new Set(); let rowsProcessed = 0; csvStream - .on('headers', (headers) => this.validateHeadersAsColumnSubset(internalTableColumnSchema, headers, csvStream)) + .on('headers', (headers) => this.validateHeadersAsColumnSubset(internalTableDatabaseColumn, headers, csvStream)) .transform((row) => this.validateAndParseColumnDataType( - internalTableColumnSchema, + internalTableDatabaseColumn, primaryKeyColumnSchema, row, rowsProcessed, @@ -102,7 +103,7 @@ export class TooljetDbBulkUploadService { await pipeline(passThrough, csvStream); await this.tooljetDbManager.transaction(async (tooljetDbManager) => { - await this.bulkUpsertRows(tooljetDbManager, rowsToUpsert, internalTableId, internalTableColumnSchema); + await this.bulkUpsertRows(tooljetDbManager, rowsToUpsert, internalTableId, internalTableDatabaseColumn); }); return { processedRows: rowsProcessed }; @@ -112,15 +113,15 @@ export class TooljetDbBulkUploadService { tooljetDbManager: EntityManager, rowsToUpsert: unknown[], internalTableId: string, - internalTableColumnSchema: TableColumnSchema[] + internalTableDatabaseColumn: TooljetDatabaseColumn[] ) { if (isEmpty(rowsToUpsert)) return; - const primaryKeyColumns = internalTableColumnSchema + const primaryKeyColumns = internalTableDatabaseColumn .filter((colDetails) => colDetails.keytype === 'PRIMARY KEY') .map((colDetails) => colDetails.column_name); - const serialTypeColumns = internalTableColumnSchema + const serialTypeColumns = internalTableDatabaseColumn .filter((colDetails) => colDetails.data_type === 'integer' && /^nextval\(/.test(colDetails.column_default)) .map((colDetails) => colDetails.column_name); @@ -164,11 +165,11 @@ export class TooljetDbBulkUploadService { } async validateHeadersAsColumnSubset( - internalTableColumnSchema: TableColumnSchema[], + internalTableDatabaseColumn: TooljetDatabaseColumn[], headers: string[], csvStream: csv.CsvParserStream, csv.ParserRow> ) { - const internalTableColumns = new Set(internalTableColumnSchema.map((c) => c.column_name)); + const internalTableColumns = new Set(internalTableDatabaseColumn.map((c) => c.column_name)); const columnsInCsv = new Set(headers); const isSubset = (subset: Set, superset: Set) => [...subset].every((item) => superset.has(item)); @@ -179,15 +180,15 @@ export class TooljetDbBulkUploadService { } } - findPrimaryKey(columnName: string, primaryKeyColumns: TableColumnSchema[]) { + findPrimaryKey(columnName: string, primaryKeyColumns: TooljetDatabaseColumn[]) { return primaryKeyColumns.find( (colDetails) => colDetails.column_name === columnName && colDetails.keytype === 'PRIMARY KEY' ); } validateAndParseColumnDataType( - internalTableColumnSchema: TableColumnSchema[], - primaryKeyColumnSchema: TableColumnSchema[], + internalTableDatabaseColumn: TooljetDatabaseColumn[], + primaryKeyColumnSchema: TooljetDatabaseColumn[], row: unknown, rowsProcessed: number, csvStream: csv.CsvParserStream, csv.ParserRow> @@ -197,7 +198,7 @@ export class TooljetDbBulkUploadService { try { const columnsInCsv = Object.keys(row); const transformedRow = columnsInCsv.reduce((result, columnInCsv) => { - const columnDetails = internalTableColumnSchema.find((colDetails) => colDetails.column_name === columnInCsv); + const columnDetails = internalTableDatabaseColumn.find((colDetails) => colDetails.column_name === columnInCsv); const primaryKey = this.findPrimaryKey(columnInCsv, primaryKeyColumnSchema); if (!isEmpty(primaryKey) && isEmpty(primaryKey.column_default) && isEmpty(row[columnInCsv])) @@ -213,15 +214,15 @@ export class TooljetDbBulkUploadService { } } - convertToDataType(columnValue: string, supportedDataType: SupportedDataTypes) { + convertToDataType(columnValue: string, supportedDataType: TooljetDatabaseDataTypes) { if (!columnValue) return null; switch (supportedDataType) { - case 'boolean': + case TJDB.boolean: return this.convertBoolean(columnValue); - case 'integer': - case 'double precision': - case 'bigint': + case TJDB.integer: + case TJDB.double_precision: + case TJDB.bigint: return this.convertNumber(columnValue, supportedDataType); default: return columnValue; diff --git a/server/test/__fixtures__/TooljetDbOperationsService_2702950297/createRow_4100895903/should-create-a-new-row-and-verify-its-content_3372386196/recording.har b/server/test/__fixtures__/TooljetDbOperationsService_2702950297/createRow_4100895903/should-create-a-new-row-and-verify-its-content_3372386196/recording.har new file mode 100644 index 0000000000..499834fadd --- /dev/null +++ b/server/test/__fixtures__/TooljetDbOperationsService_2702950297/createRow_4100895903/should-create-a-new-row-and-verify-its-content_3372386196/recording.har @@ -0,0 +1,128 @@ +{ + "log": { + "_recordingName": "TooljetDbOperationsService/.createRow/should create a new row and verify its content", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "197a6feb7e633d071ca275fc502d5b6f", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 46, + "cookies": [], + "headers": [ + { + "name": "user-agent", + "value": "got (https://github.com/sindresorhus/got)" + }, + { + "name": "data-query-id", + "value": "query-id" + }, + { + "name": "tj-workspace-id", + "value": "b17c6d5a-93b6-4eee-8420-4c19b9bf3ddf" + }, + { + "name": "tableinfo", + "value": { + "9a26655a-3afb-410f-8dd1-13f0787255b4": "users" + } + }, + { + "name": "authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3NTUsImV4cCI6MTcyMDEyNjgxNX0.rcfp0ZwfIdJ8fIkpb1ZBvOdyQLF9KtKdQJXc8F7gtX4" + }, + { + "name": "prefer", + "value": "count=exact, return=representation" + }, + { + "name": "accept", + "value": "application/json" + }, + { + "name": "content-length", + "value": "46" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "host", + "value": "localhost:3001" + } + ], + "headersSize": 563, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "text/plain", + "params": [], + "text": "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}" + }, + "queryString": [], + "url": "http://localhost:3001/9a26655a-3afb-410f-8dd1-13f0787255b4" + }, + "response": { + "bodySize": 55, + "content": { + "mimeType": "application/json; charset=utf-8", + "size": 55, + "text": "[{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}]" + }, + "cookies": [], + "headers": [ + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "date", + "value": "Thu, 04 Jul 2024 20:59:15 GMT" + }, + { + "name": "server", + "value": "postgrest/12.0.3 (6a506d1)" + }, + { + "name": "content-range", + "value": "*/1" + }, + { + "name": "preference-applied", + "value": "return=representation, count=exact" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + } + ], + "headersSize": 226, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 201, + "statusText": "Created" + }, + "startedDateTime": "2024-07-04T20:59:15.597Z", + "time": 78, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 78 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/server/test/__fixtures__/TooljetDbOperationsService_2702950297/deleteRows_617995275/should-delete-rows-and-verify-the-deletion_3165680739/recording.har b/server/test/__fixtures__/TooljetDbOperationsService_2702950297/deleteRows_617995275/should-delete-rows-and-verify-the-deletion_3165680739/recording.har new file mode 100644 index 0000000000..a86a1bf8c1 --- /dev/null +++ b/server/test/__fixtures__/TooljetDbOperationsService_2702950297/deleteRows_617995275/should-delete-rows-and-verify-the-deletion_3165680739/recording.har @@ -0,0 +1,246 @@ +{ + "log": { + "_recordingName": "TooljetDbOperationsService/.deleteRows/should delete rows and verify the deletion", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "197a6feb7e633d071ca275fc502d5b6f", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 46, + "cookies": [], + "headers": [ + { + "name": "user-agent", + "value": "got (https://github.com/sindresorhus/got)" + }, + { + "name": "data-query-id", + "value": "query-id" + }, + { + "name": "tj-workspace-id", + "value": "d029ddeb-e4c5-46ed-aa23-bbd58fbb445b" + }, + { + "name": "tableinfo", + "value": { + "d4dbdb73-e3fd-4cb8-8fd0-a8540f38267d": "users" + } + }, + { + "name": "authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3ODAsImV4cCI6MTcyMDEyNjg0MH0.oSaB7oSG8LYnxHs_CbaiBd7ZZXasu--TZuqo9nwohZY" + }, + { + "name": "prefer", + "value": "count=exact, return=representation" + }, + { + "name": "accept", + "value": "application/json" + }, + { + "name": "content-length", + "value": "46" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "host", + "value": "localhost:3001" + } + ], + "headersSize": 563, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "text/plain", + "params": [], + "text": "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}" + }, + "queryString": [], + "url": "http://localhost:3001/d4dbdb73-e3fd-4cb8-8fd0-a8540f38267d" + }, + "response": { + "bodySize": 55, + "content": { + "mimeType": "application/json; charset=utf-8", + "size": 55, + "text": "[{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}]" + }, + "cookies": [], + "headers": [ + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "date", + "value": "Thu, 04 Jul 2024 20:59:40 GMT" + }, + { + "name": "server", + "value": "postgrest/12.0.3 (6a506d1)" + }, + { + "name": "content-range", + "value": "*/1" + }, + { + "name": "preference-applied", + "value": "return=representation, count=exact" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + } + ], + "headersSize": 226, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 201, + "statusText": "Created" + }, + "startedDateTime": "2024-07-04T20:59:40.142Z", + "time": 34, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 34 + } + }, + { + "_id": "025d1c262b73f8739031fa6b0b522c67", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 0, + "cookies": [], + "headers": [ + { + "name": "user-agent", + "value": "got (https://github.com/sindresorhus/got)" + }, + { + "name": "data-query-id", + "value": "query-id" + }, + { + "name": "tj-workspace-id", + "value": "d029ddeb-e4c5-46ed-aa23-bbd58fbb445b" + }, + { + "name": "tableinfo", + "value": { + "d4dbdb73-e3fd-4cb8-8fd0-a8540f38267d": "users" + } + }, + { + "name": "authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3ODAsImV4cCI6MTcyMDEyNjg0MH0.oSaB7oSG8LYnxHs_CbaiBd7ZZXasu--TZuqo9nwohZY" + }, + { + "name": "prefer", + "value": "count=exact, return=representation" + }, + { + "name": "accept", + "value": "application/json" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "host", + "value": "localhost:3001" + } + ], + "headersSize": 581, + "httpVersion": "HTTP/1.1", + "method": "DELETE", + "queryString": [ + { + "name": "name", + "value": "eq.John Doe" + }, + { + "name": "limit", + "value": "1" + }, + { + "name": "order", + "value": "id" + } + ], + "url": "http://localhost:3001/d4dbdb73-e3fd-4cb8-8fd0-a8540f38267d?name=eq.John%20Doe&limit=1&order=id" + }, + "response": { + "bodySize": 55, + "content": { + "mimeType": "application/json; charset=utf-8", + "size": 55, + "text": "[{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}]" + }, + "cookies": [], + "headers": [ + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "date", + "value": "Thu, 04 Jul 2024 20:59:40 GMT" + }, + { + "name": "server", + "value": "postgrest/12.0.3 (6a506d1)" + }, + { + "name": "content-range", + "value": "*/1" + }, + { + "name": "preference-applied", + "value": "return=representation, count=exact" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + } + ], + "headersSize": 226, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2024-07-04T20:59:40.235Z", + "time": 25, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 25 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/server/test/__fixtures__/TooljetDbOperationsService_2702950297/joinTables_306409236/should-join-tables-and-verify-the-result_3664012573/recording.har b/server/test/__fixtures__/TooljetDbOperationsService_2702950297/joinTables_306409236/should-join-tables-and-verify-the-result_3664012573/recording.har new file mode 100644 index 0000000000..53e33f7d8f --- /dev/null +++ b/server/test/__fixtures__/TooljetDbOperationsService_2702950297/joinTables_306409236/should-join-tables-and-verify-the-result_3664012573/recording.har @@ -0,0 +1,242 @@ +{ + "log": { + "_recordingName": "TooljetDbOperationsService/.joinTables/should join tables and verify the result", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "197a6feb7e633d071ca275fc502d5b6f", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 46, + "cookies": [], + "headers": [ + { + "name": "user-agent", + "value": "got (https://github.com/sindresorhus/got)" + }, + { + "name": "data-query-id", + "value": "query-id" + }, + { + "name": "tj-workspace-id", + "value": "14ff6b2c-18b9-45d5-8098-cbb2d3faa8d8" + }, + { + "name": "tableinfo", + "value": { + "0d81d897-f3f7-4616-afcb-a7873a6fdbd0": "users" + } + }, + { + "name": "authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3ODcsImV4cCI6MTcyMDEyNjg0N30.zMLW2N6-FhzS7zgwBgQM6fAPjI4rxqfAhum_izOLpDA" + }, + { + "name": "prefer", + "value": "count=exact, return=representation" + }, + { + "name": "accept", + "value": "application/json" + }, + { + "name": "content-length", + "value": "46" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "host", + "value": "localhost:3001" + } + ], + "headersSize": 563, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "text/plain", + "params": [], + "text": "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}" + }, + "queryString": [], + "url": "http://localhost:3001/0d81d897-f3f7-4616-afcb-a7873a6fdbd0" + }, + "response": { + "bodySize": 55, + "content": { + "mimeType": "application/json; charset=utf-8", + "size": 55, + "text": "[{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}]" + }, + "cookies": [], + "headers": [ + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "date", + "value": "Thu, 04 Jul 2024 20:59:47 GMT" + }, + { + "name": "server", + "value": "postgrest/12.0.3 (6a506d1)" + }, + { + "name": "content-range", + "value": "*/1" + }, + { + "name": "preference-applied", + "value": "return=representation, count=exact" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + } + ], + "headersSize": 226, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 201, + "statusText": "Created" + }, + "startedDateTime": "2024-07-04T20:59:47.938Z", + "time": 34, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 34 + } + }, + { + "_id": "11ab62f4916d91ebf3464b4d366801f1", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 27, + "cookies": [], + "headers": [ + { + "name": "user-agent", + "value": "got (https://github.com/sindresorhus/got)" + }, + { + "name": "data-query-id", + "value": "query-id" + }, + { + "name": "tj-workspace-id", + "value": "14ff6b2c-18b9-45d5-8098-cbb2d3faa8d8" + }, + { + "name": "tableinfo", + "value": { + "b8e3bafa-fa05-4bbc-8e3f-5af967405d30": "orders" + } + }, + { + "name": "authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3ODcsImV4cCI6MTcyMDEyNjg0N30.zMLW2N6-FhzS7zgwBgQM6fAPjI4rxqfAhum_izOLpDA" + }, + { + "name": "prefer", + "value": "count=exact, return=representation" + }, + { + "name": "accept", + "value": "application/json" + }, + { + "name": "content-length", + "value": "27" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "host", + "value": "localhost:3001" + } + ], + "headersSize": 563, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "text/plain", + "params": [], + "text": "{\"user_id\":1,\"total\":100.5}" + }, + "queryString": [], + "url": "http://localhost:3001/b8e3bafa-fa05-4bbc-8e3f-5af967405d30" + }, + "response": { + "bodySize": 36, + "content": { + "mimeType": "application/json; charset=utf-8", + "size": 36, + "text": "[{\"id\":1,\"user_id\":1,\"total\":100.5}]" + }, + "cookies": [], + "headers": [ + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "date", + "value": "Thu, 04 Jul 2024 20:59:47 GMT" + }, + { + "name": "server", + "value": "postgrest/12.0.3 (6a506d1)" + }, + { + "name": "content-range", + "value": "*/1" + }, + { + "name": "preference-applied", + "value": "return=representation, count=exact" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + } + ], + "headersSize": 226, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 201, + "statusText": "Created" + }, + "startedDateTime": "2024-07-04T20:59:47.999Z", + "time": 14, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 14 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/server/test/__fixtures__/TooljetDbOperationsService_2702950297/listRows_890836256/should-list-rows-correctly_2550208038/recording.har b/server/test/__fixtures__/TooljetDbOperationsService_2702950297/listRows_890836256/should-list-rows-correctly_2550208038/recording.har new file mode 100644 index 0000000000..5068d5f005 --- /dev/null +++ b/server/test/__fixtures__/TooljetDbOperationsService_2702950297/listRows_890836256/should-list-rows-correctly_2550208038/recording.har @@ -0,0 +1,237 @@ +{ + "log": { + "_recordingName": "TooljetDbOperationsService/.listRows/should list rows correctly", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "197a6feb7e633d071ca275fc502d5b6f", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 46, + "cookies": [], + "headers": [ + { + "name": "user-agent", + "value": "got (https://github.com/sindresorhus/got)" + }, + { + "name": "data-query-id", + "value": "query-id" + }, + { + "name": "tj-workspace-id", + "value": "08eae723-42ac-4fd5-a7dc-3db4f0a1ba9c" + }, + { + "name": "tableinfo", + "value": { + "291dda09-4ca4-48cd-b62c-cf0e325c65ae": "users" + } + }, + { + "name": "authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3NjMsImV4cCI6MTcyMDEyNjgyM30.dlBsKWsPZMxNr3fGSL0-bi8x-viUGYpTScCU0YS8CQg" + }, + { + "name": "prefer", + "value": "count=exact, return=representation" + }, + { + "name": "accept", + "value": "application/json" + }, + { + "name": "content-length", + "value": "46" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "host", + "value": "localhost:3001" + } + ], + "headersSize": 563, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "text/plain", + "params": [], + "text": "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}" + }, + "queryString": [], + "url": "http://localhost:3001/291dda09-4ca4-48cd-b62c-cf0e325c65ae" + }, + "response": { + "bodySize": 55, + "content": { + "mimeType": "application/json; charset=utf-8", + "size": 55, + "text": "[{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}]" + }, + "cookies": [], + "headers": [ + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "date", + "value": "Thu, 04 Jul 2024 20:59:23 GMT" + }, + { + "name": "server", + "value": "postgrest/12.0.3 (6a506d1)" + }, + { + "name": "content-range", + "value": "*/1" + }, + { + "name": "preference-applied", + "value": "return=representation, count=exact" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + } + ], + "headersSize": 226, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 201, + "statusText": "Created" + }, + "startedDateTime": "2024-07-04T20:59:23.808Z", + "time": 59, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 59 + } + }, + { + "_id": "a201948f8b1cb6b14aa3987eeb19b1f5", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 0, + "cookies": [], + "headers": [ + { + "name": "user-agent", + "value": "got (https://github.com/sindresorhus/got)" + }, + { + "name": "data-query-id", + "value": "query-id" + }, + { + "name": "tj-workspace-id", + "value": "08eae723-42ac-4fd5-a7dc-3db4f0a1ba9c" + }, + { + "name": "tableinfo", + "value": { + "291dda09-4ca4-48cd-b62c-cf0e325c65ae": "users" + } + }, + { + "name": "authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3NjMsImV4cCI6MTcyMDEyNjgyM30.dlBsKWsPZMxNr3fGSL0-bi8x-viUGYpTScCU0YS8CQg" + }, + { + "name": "prefer", + "value": "count=exact, return=representation" + }, + { + "name": "accept", + "value": "application/json" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "host", + "value": "localhost:3001" + } + ], + "headersSize": 542, + "httpVersion": "HTTP/1.1", + "method": "GET", + "queryString": [], + "url": "http://localhost:3001/291dda09-4ca4-48cd-b62c-cf0e325c65ae" + }, + "response": { + "bodySize": 55, + "content": { + "mimeType": "application/json; charset=utf-8", + "size": 55, + "text": "[{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}]" + }, + "cookies": [], + "headers": [ + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "date", + "value": "Thu, 04 Jul 2024 20:59:23 GMT" + }, + { + "name": "server", + "value": "postgrest/12.0.3 (6a506d1)" + }, + { + "name": "content-range", + "value": "0-0/1" + }, + { + "name": "content-location", + "value": "/291dda09-4ca4-48cd-b62c-cf0e325c65ae" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "preference-applied", + "value": "count=exact" + } + ], + "headersSize": 262, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2024-07-04T20:59:23.901Z", + "time": 19, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 19 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/server/test/__fixtures__/TooljetDbOperationsService_2702950297/recording.har b/server/test/__fixtures__/TooljetDbOperationsService_2702950297/recording.har new file mode 100644 index 0000000000..fd318d6f8a --- /dev/null +++ b/server/test/__fixtures__/TooljetDbOperationsService_2702950297/recording.har @@ -0,0 +1,120 @@ +{ + "log": { + "_recordingName": "TooljetDbOperationsService", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "bed7fb75983310f155c3e3b6b1ccc6d8", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 46, + "cookies": [], + "headers": [ + { + "name": "user-agent", + "value": "got (https://github.com/sindresorhus/got)" + }, + { + "name": "data-query-id", + "value": "query-id" + }, + { + "name": "tj-workspace-id", + "value": "5e724498-ae98-42bc-bb3d-b7d255b1d2c5" + }, + { + "name": "tableinfo", + "value": { + "96732d2a-9d32-409e-b34f-4cf6a99ef342": "users" + } + }, + { + "name": "authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMTY0MDAsImV4cCI6MTcyMDExNjQ2MH0.nQu4YxlacdZMYi0ssTe7MIWWU61rQNKe7xZc0GeGTXQ" + }, + { + "name": "prefer", + "value": "count=exact, return=representation" + }, + { + "name": "accept", + "value": "application/json" + }, + { + "name": "content-length", + "value": "46" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "host", + "value": "localhost:3001" + } + ], + "headersSize": 563, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "text/plain", + "params": [], + "text": "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}" + }, + "queryString": [], + "url": "http://localhost:3001/96732d2a-9d32-409e-b34f-4cf6a99ef342" + }, + "response": { + "bodySize": 2, + "content": { + "mimeType": "application/json; charset=utf-8", + "size": 2, + "text": "{}" + }, + "cookies": [], + "headers": [ + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "date", + "value": "Thu, 04 Jul 2024 18:06:40 GMT" + }, + { + "name": "server", + "value": "postgrest/12.0.3 (6a506d1)" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + } + ], + "headersSize": 150, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 404, + "statusText": "Not Found" + }, + "startedDateTime": "2024-07-04T18:06:40.291Z", + "time": 13, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 13 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/server/test/__fixtures__/TooljetDbOperationsService_2702950297/updateRows_1779216101/should-update-rows-and-verify-the-changes_682898660/recording.har b/server/test/__fixtures__/TooljetDbOperationsService_2702950297/updateRows_1779216101/should-update-rows-and-verify-the-changes_682898660/recording.har new file mode 100644 index 0000000000..d99f19c4eb --- /dev/null +++ b/server/test/__fixtures__/TooljetDbOperationsService_2702950297/updateRows_1779216101/should-update-rows-and-verify-the-changes_682898660/recording.har @@ -0,0 +1,360 @@ +{ + "log": { + "_recordingName": "TooljetDbOperationsService/.updateRows/should update rows and verify the changes", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "197a6feb7e633d071ca275fc502d5b6f", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 46, + "cookies": [], + "headers": [ + { + "name": "user-agent", + "value": "got (https://github.com/sindresorhus/got)" + }, + { + "name": "data-query-id", + "value": "query-id" + }, + { + "name": "tj-workspace-id", + "value": "577ed2e5-3243-432a-8084-c90bf85a6afc" + }, + { + "name": "tableinfo", + "value": { + "e3f89568-858d-4d4b-a443-d863ad392e7f": "users" + } + }, + { + "name": "authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3NzEsImV4cCI6MTcyMDEyNjgzMX0.-LwnMhI-S-rWKIFXWAqURKY7qGVsMTV5eAoP6vLe9_k" + }, + { + "name": "prefer", + "value": "count=exact, return=representation" + }, + { + "name": "accept", + "value": "application/json" + }, + { + "name": "content-length", + "value": "46" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "host", + "value": "localhost:3001" + } + ], + "headersSize": 563, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "text/plain", + "params": [], + "text": "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}" + }, + "queryString": [], + "url": "http://localhost:3001/e3f89568-858d-4d4b-a443-d863ad392e7f" + }, + "response": { + "bodySize": 55, + "content": { + "mimeType": "application/json; charset=utf-8", + "size": 55, + "text": "[{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}]" + }, + "cookies": [], + "headers": [ + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "date", + "value": "Thu, 04 Jul 2024 20:59:31 GMT" + }, + { + "name": "server", + "value": "postgrest/12.0.3 (6a506d1)" + }, + { + "name": "content-range", + "value": "*/1" + }, + { + "name": "preference-applied", + "value": "return=representation, count=exact" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + } + ], + "headersSize": 226, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 201, + "statusText": "Created" + }, + "startedDateTime": "2024-07-04T20:59:31.529Z", + "time": 25, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 25 + } + }, + { + "_id": "e671061f70ab82499db975c0d090cc9a", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 19, + "cookies": [], + "headers": [ + { + "name": "user-agent", + "value": "got (https://github.com/sindresorhus/got)" + }, + { + "name": "data-query-id", + "value": "query-id" + }, + { + "name": "tj-workspace-id", + "value": "577ed2e5-3243-432a-8084-c90bf85a6afc" + }, + { + "name": "tableinfo", + "value": { + "e3f89568-858d-4d4b-a443-d863ad392e7f": "users" + } + }, + { + "name": "authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3NzEsImV4cCI6MTcyMDEyNjgzMX0.-LwnMhI-S-rWKIFXWAqURKY7qGVsMTV5eAoP6vLe9_k" + }, + { + "name": "prefer", + "value": "count=exact, return=representation" + }, + { + "name": "accept", + "value": "application/json" + }, + { + "name": "content-length", + "value": "19" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "host", + "value": "localhost:3001" + } + ], + "headersSize": 592, + "httpVersion": "HTTP/1.1", + "method": "PATCH", + "postData": { + "mimeType": "text/plain", + "params": [], + "text": "{\"name\":\"Jane Doe\"}" + }, + "queryString": [ + { + "name": "name", + "value": "eq.John Doe" + }, + { + "name": "order", + "value": "id" + } + ], + "url": "http://localhost:3001/e3f89568-858d-4d4b-a443-d863ad392e7f?name=eq.John%20Doe&order=id" + }, + "response": { + "bodySize": 55, + "content": { + "mimeType": "application/json; charset=utf-8", + "size": 55, + "text": "[{\"id\":1,\"name\":\"Jane Doe\",\"email\":\"john@example.com\"}]" + }, + "cookies": [], + "headers": [ + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "date", + "value": "Thu, 04 Jul 2024 20:59:31 GMT" + }, + { + "name": "server", + "value": "postgrest/12.0.3 (6a506d1)" + }, + { + "name": "content-range", + "value": "0-0/1" + }, + { + "name": "preference-applied", + "value": "return=representation, count=exact" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + } + ], + "headersSize": 228, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2024-07-04T20:59:31.599Z", + "time": 30, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 30 + } + }, + { + "_id": "a201948f8b1cb6b14aa3987eeb19b1f5", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 0, + "cookies": [], + "headers": [ + { + "name": "user-agent", + "value": "got (https://github.com/sindresorhus/got)" + }, + { + "name": "data-query-id", + "value": "query-id" + }, + { + "name": "tj-workspace-id", + "value": "577ed2e5-3243-432a-8084-c90bf85a6afc" + }, + { + "name": "tableinfo", + "value": { + "e3f89568-858d-4d4b-a443-d863ad392e7f": "users" + } + }, + { + "name": "authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3NzEsImV4cCI6MTcyMDEyNjgzMX0.-LwnMhI-S-rWKIFXWAqURKY7qGVsMTV5eAoP6vLe9_k" + }, + { + "name": "prefer", + "value": "count=exact, return=representation" + }, + { + "name": "accept", + "value": "application/json" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "host", + "value": "localhost:3001" + } + ], + "headersSize": 542, + "httpVersion": "HTTP/1.1", + "method": "GET", + "queryString": [], + "url": "http://localhost:3001/e3f89568-858d-4d4b-a443-d863ad392e7f" + }, + "response": { + "bodySize": 55, + "content": { + "mimeType": "application/json; charset=utf-8", + "size": 55, + "text": "[{\"id\":1,\"name\":\"Jane Doe\",\"email\":\"john@example.com\"}]" + }, + "cookies": [], + "headers": [ + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "date", + "value": "Thu, 04 Jul 2024 20:59:31 GMT" + }, + { + "name": "server", + "value": "postgrest/12.0.3 (6a506d1)" + }, + { + "name": "content-range", + "value": "0-0/1" + }, + { + "name": "content-location", + "value": "/e3f89568-858d-4d4b-a443-d863ad392e7f" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "preference-applied", + "value": "count=exact" + } + ], + "headersSize": 262, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2024-07-04T20:59:31.695Z", + "time": 25, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 25 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/server/test/jest-e2e.json b/server/test/jest-e2e.json index 05e8d9937b..85ba363de5 100644 --- a/server/test/jest-e2e.json +++ b/server/test/jest-e2e.json @@ -1,5 +1,5 @@ { - "moduleFileExtensions": ["js", "json", "ts"], + "moduleFileExtensions": ["js", "json", "ts", "node"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", @@ -11,6 +11,7 @@ "@plugins/(.*)": "/../plugins/$1", "@dto/(.*)": "/../src/dto/$1", "@services/(.*)": "/../src/services/$1", + "@entities/(.*)": "/src/entities/$1", "@controllers/(.*)": "/../src/controllers/$1", "@ee/(.*)": "/../ee/$1" } diff --git a/server/test/services/tooljet_db_operations.service.spec.ts b/server/test/services/tooljet_db_operations.service.spec.ts new file mode 100644 index 0000000000..9279357667 --- /dev/null +++ b/server/test/services/tooljet_db_operations.service.spec.ts @@ -0,0 +1,377 @@ +/** @jest-environment setup-polly-jest/jest-environment-node */ + +import { INestApplication } from '@nestjs/common'; +import { getManager, getConnection, EntityManager } from 'typeorm'; +import { TooljetDbOperationsService } from '../../src/services/tooljet_db_operations.service'; +import { TooljetDbService } from '../../src/services/tooljet_db.service'; +import { setupPolly } from 'setup-polly-jest'; +import * as NodeHttpAdapter from '@pollyjs/adapter-node-http'; +import * as FSPersister from '@pollyjs/persister-fs'; +import * as path from 'path'; +import { clearDB, createUser } from '../test.helper'; +import { setupTestTables } from '../tooljet-db-test.helper'; +import { InternalTable } from '@entities/internal_table.entity'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ormconfig, tooljetDbOrmconfig } from '../../ormconfig'; +import { PostgrestProxyService } from '@services/postgrest_proxy.service'; +import { getEnvVars } from '../../scripts/database-config-utils'; +import { User } from '@entities/user.entity'; +import { Organization } from '@entities/organization.entity'; +import { OrganizationUser } from '@entities/organization_user.entity'; +import { AppVersion } from '@entities/app_version.entity'; +import { GroupPermission } from '@entities/group_permission.entity'; +import { UserGroupPermission } from '@entities/user_group_permission.entity'; +import { App } from '@entities/app.entity'; + +describe('TooljetDbOperationsService', () => { + let app: INestApplication; + let appManager: EntityManager; + let tjDbManager: EntityManager; + let service: TooljetDbOperationsService; + let tooljetDbService: TooljetDbService; + let organizationId: string; + let usersTableId: string; + + const context = setupPolly({ + adapters: [NodeHttpAdapter], + persister: FSPersister, + recordFailedRequests: true, + matchRequestsBy: { + method: true, + headers: { + exclude: ['tj-workspace-id', 'authorization', 'tableinfo'], // Exclude headers as they contain dynamic information + }, + body: true, + url: { + protocol: true, + username: true, + password: true, + hostname: true, + port: true, + pathname: false, // Don't match by pathname as it contains the dynamic table ID + query: true, + }, + }, + persisterOptions: { + fs: { + recordingsDir: path.resolve(__dirname, '../__fixtures__'), + }, + }, + }); + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + envFilePath: ['../.env.test'], + load: [() => getEnvVars()], + }), + TypeOrmModule.forRoot(ormconfig), + TypeOrmModule.forRoot(tooljetDbOrmconfig), + TypeOrmModule.forFeature([ + User, + Organization, + OrganizationUser, + App, + AppVersion, + GroupPermission, + UserGroupPermission, + InternalTable, + ]), + ], + providers: [TooljetDbOperationsService, TooljetDbService, PostgrestProxyService], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + + appManager = getManager(); + tjDbManager = getConnection('tooljetDb').manager; + + service = moduleFixture.get(TooljetDbOperationsService); + tooljetDbService = moduleFixture.get(TooljetDbService); + }); + + beforeEach(async () => { + await clearDB(); + + const adminUserData = await createUser(app, { + email: 'admin@tooljet.io', + groups: ['all_users', 'admin'], + }); + organizationId = adminUserData.organization.id; + + await setupTestTables(appManager, tjDbManager, tooljetDbService, organizationId); + const usersTable = await appManager.findOneOrFail(InternalTable, { organizationId, tableName: 'users' }); + usersTableId = usersTable.id; + }); + + afterEach(async () => { + context.polly.stop(); + }); + + afterAll(async () => { + await app.close(); + await clearDB(); + }); + + describe('.createRow', () => { + it('should create a new row and verify its content', async () => { + const queryOptions = { + table_id: usersTableId, + create_row: { + name: { column: 'name', value: 'John Doe' }, + email: { column: 'email', value: 'john@example.com' }, + }, + id: 'query-id', + organization_id: organizationId, + }; + + const result = await service.createRow(queryOptions); + + expect(result).toBeDefined(); + expect(result.status).toBe('ok'); + expect(Array.isArray(result.data)).toBe(true); + + const data = result.data as Array>; + + expect(data.length).toBeGreaterThan(0); + expect(data[0]).toEqual( + expect.objectContaining({ + id: expect.any(Number), + name: 'John Doe', + email: 'john@example.com', + }) + ); + }); + }); + + describe('.listRows', () => { + it('should list rows correctly', async () => { + // Create a test row + await service.createRow({ + table_id: usersTableId, + create_row: { + name: { column: 'name', value: 'John Doe' }, + email: { column: 'email', value: 'john@example.com' }, + }, + id: 'query-id', + organization_id: organizationId, + }); + + const queryOptions = { + table_id: usersTableId, + list_rows: { + limit: 10, + offset: 0, + }, + id: 'query-id', + organization_id: organizationId, + }; + + const result = await service.listRows(queryOptions); + + expect(result).toBeDefined(); + expect(result.status).toBe('ok'); + + const data = result.data as Array>; + + expect(Array.isArray(data)).toBe(true); + expect(data.length).toBe(1); + expect(data[0]).toEqual( + expect.objectContaining({ + name: 'John Doe', + email: 'john@example.com', + }) + ); + }); + }); + + describe('.updateRows', () => { + it('should update rows and verify the changes', async () => { + // Create a test row + await service.createRow({ + table_id: usersTableId, + create_row: { + name: { column: 'name', value: 'John Doe' }, + email: { column: 'email', value: 'john@example.com' }, + }, + id: 'query-id', + organization_id: organizationId, + }); + + const queryOptions = { + table_id: usersTableId, + update_rows: { + where_filters: { + filter1: { column: 'name', operator: 'eq', value: 'John Doe' }, + }, + columns: { + name: { column: 'name', value: 'Jane Doe' }, + }, + }, + id: 'query-id', + organization_id: organizationId, + }; + + const result = await service.updateRows(queryOptions); + + expect(result).toBeDefined(); + expect(result.status).toBe('ok'); + + // Verify the update + const listResult = await service.listRows({ + table_id: usersTableId, + list_rows: { limit: 1 }, + id: 'query-id', + organization_id: organizationId, + }); + + const data = listResult.data as Array>; + + expect(Array.isArray(data)).toBe(true); + expect(data).toEqual([ + { + id: 1, + name: 'Jane Doe', + email: 'john@example.com', + }, + ]); + }); + }); + + describe('.deleteRows', () => { + it('should delete rows and verify the deletion', async () => { + await service.createRow({ + table_id: usersTableId, + create_row: { + name: { column: 'name', value: 'John Doe' }, + email: { column: 'email', value: 'john@example.com' }, + }, + id: 'query-id', + organization_id: organizationId, + }); + + const queryOptions = { + table_id: usersTableId, + delete_rows: { + where_filters: { + filter1: { column: 'name', operator: 'eq', value: 'John Doe' }, + }, + }, + id: 'query-id', + organization_id: organizationId, + }; + + const result = await service.deleteRows(queryOptions); + + expect(result).toBeDefined(); + expect(result.status).toBe('ok'); + }); + }); + + describe('.joinTables', () => { + it('should join tables and verify the result', async () => { + // Check if tables exist + const usersTable = await appManager.findOne(InternalTable, { organizationId, tableName: 'users' }); + const ordersTable = await appManager.findOne(InternalTable, { organizationId, tableName: 'orders' }); + + expect(usersTable).toBeDefined(); + expect(ordersTable).toBeDefined(); + + const insertUserResult = await tjDbManager + .createQueryBuilder() + .insert() + .into(usersTable.id) + .values({ + name: 'John Doe', + email: 'john@example.com', + }) + .returning('id') + .execute(); + + const userId = insertUserResult.raw[0].id; + + await tjDbManager + .createQueryBuilder() + .insert() + .into(ordersTable.id) + .values({ + user_id: userId, + total: 100.5, + }) + .execute(); + + // Perform join + const queryOptions = { + join_table: { + joins: [ + { + id: Date.now(), // unique ID + conditions: { + operator: 'AND', + conditionsList: [ + { + operator: '=', + leftField: { + table: usersTable.id, + columnName: 'id', + type: 'Column', + }, + rightField: { + table: ordersTable.id, + columnName: 'user_id', + type: 'Column', + }, + }, + ], + }, + joinType: 'INNER', + table: ordersTable.id, + }, + ], + from: { + name: usersTable.id, + type: 'Table', + }, + fields: [ + { name: 'id', table: usersTable.id }, + { name: 'name', table: usersTable.id }, + { name: 'email', table: usersTable.id }, + { name: 'id', table: ordersTable.id }, + { name: 'user_id', table: ordersTable.id }, + { name: 'total', table: ordersTable.id }, + ], + conditions: { + conditionsList: [], + }, + order_by: [], + }, + organization_id: organizationId, + }; + + const joinResult = await service.joinTables(queryOptions); + + const expectedResponse = { + status: 'ok', + data: { + result: [ + { + users_id: expect.any(Number), + users_name: 'John Doe', + users_email: 'john@example.com', + orders_id: expect.any(Number), + orders_user_id: expect.any(Number), + orders_total: 100.5, + }, + ], + }, + }; + + expect(joinResult).toEqual(expectedResponse); + }); + }); +}); diff --git a/server/test/test.helper.ts b/server/test/test.helper.ts index 513744fe85..c6097d096b 100644 --- a/server/test/test.helper.ts +++ b/server/test/test.helper.ts @@ -36,6 +36,7 @@ import { AppEnvironment } from 'src/entities/app_environments.entity'; import { defaultAppEnvironments } from 'src/helpers/utils.helper'; import { DataSourceOptions } from 'src/entities/data_source_options.entity'; import * as cookieParser from 'cookie-parser'; +import { InternalTable } from '@entities/internal_table.entity'; export async function createNestAppInstance(): Promise { let app: INestApplication; @@ -103,13 +104,27 @@ export function authHeaderForUser(user: User, organizationId?: string, isPasswor } export async function clearDB() { - const entities = getConnection().entityMetadatas; - for (const entity of entities) { - const repository = getConnection().getRepository(entity.name); + if (process.env.NODE_ENV !== 'test') return; + if (process.env.ENABLE_TOOLJET_DB === 'true') await dropTooljetDbTables(); + + const connection = getConnection(); + for (const entity of connection.entityMetadatas) { + const repository = connection.getRepository(entity.name); await repository.query(`TRUNCATE ${entity.tableName} RESTART IDENTITY CASCADE;`); } } +async function dropTooljetDbTables() { + const connection = getConnection(); + const tooljetDbConnection = getConnection('tooljetDb'); + + const internalTables = (await connection.manager.find(InternalTable, { select: ['id'] })) as InternalTable[]; + + for (const table of internalTables) { + await tooljetDbConnection.query(`DROP TABLE IF EXISTS "${table.id}" CASCADE`); + } +} + export async function createApplication(nestApp, { name, user, isPublic, slug }: any, shouldCreateEnvs = true) { let appRepository: Repository; appRepository = nestApp.get('AppRepository'); diff --git a/server/test/tooljet-db-test.helper.ts b/server/test/tooljet-db-test.helper.ts new file mode 100644 index 0000000000..47be576a7e --- /dev/null +++ b/server/test/tooljet-db-test.helper.ts @@ -0,0 +1,159 @@ +import { EntityManager } from 'typeorm'; +import { TooljetDbService } from '@services/tooljet_db.service'; +import { InternalTable } from '@entities/internal_table.entity'; +import { + TooljetDatabaseColumn, + TooljetDatabaseForeignKey, + TooljetDatabaseTable, +} from 'src/modules/tooljet_db/tooljet-db.types'; + +const mockTableSchemas: Array = [ + { + id: 'user_table_uuid', + table_name: 'users', + schema: { + columns: [ + { + column_name: 'id', + data_type: 'integer', + column_default: "nextval('users_id_seq'::regclass)", + character_maximum_length: null, + numeric_precision: 32, + constraints_type: { + is_not_null: true, + is_primary_key: true, + is_unique: true, + }, + keytype: 'PRIMARY KEY', + }, + { + column_name: 'name', + data_type: 'character varying', + column_default: null, + character_maximum_length: null, + numeric_precision: null, + constraints_type: { + is_not_null: true, + is_primary_key: false, + is_unique: false, + }, + keytype: '', + }, + { + column_name: 'email', + data_type: 'character varying', + column_default: null, + character_maximum_length: null, + numeric_precision: null, + constraints_type: { + is_not_null: true, + is_primary_key: false, + is_unique: true, + }, + keytype: '', + }, + ], + foreign_keys: [], + }, + }, + { + id: 'orders_table_uuid', + table_name: 'orders', + schema: { + columns: [ + { + column_name: 'id', + data_type: 'integer', + column_default: "nextval('orders_id_seq'::regclass)", + character_maximum_length: null, + numeric_precision: 32, + constraints_type: { + is_not_null: true, + is_primary_key: true, + is_unique: true, + }, + keytype: 'PRIMARY KEY', + }, + { + column_name: 'user_id', + data_type: 'integer', + column_default: null, + character_maximum_length: null, + numeric_precision: 32, + constraints_type: { + is_not_null: true, + is_primary_key: false, + is_unique: false, + }, + keytype: '', + }, + { + column_name: 'total', + data_type: 'double precision', + column_default: null, + character_maximum_length: null, + numeric_precision: null, + constraints_type: { + is_not_null: true, + is_primary_key: false, + is_unique: false, + }, + keytype: '', + }, + ], + foreign_keys: [ + { + referenced_table_name: 'users', + constraint_name: 'fk_orders_user_id', + column_names: ['user_id'], + referenced_column_names: ['id'], + on_update: 'NO ACTION', + on_delete: 'CASCADE', + referenced_table_id: 'user_table_id', + }, + ], + }, + }, +]; + +export async function setupTestTables( + appManager: EntityManager, + tjdbManager: EntityManager, + tooljetDbService: TooljetDbService, + organizationId: string, + tableSchemas: Array = mockTableSchemas +): Promise { + const createTableParams = tableSchemas.map((table) => ({ ...table.schema, table_name: table.table_name })); + + for (const params of createTableParams) { + await createTable(appManager, tjdbManager, tooljetDbService, organizationId, params); + } + + // Wait for the tables to be created and postgrest to reload the schema + // when running tests in record mode + if (process.env.POLLY_MODE === 'record') { + await new Promise((resolve) => setTimeout(resolve, 2000)); + } +} + +async function createTable( + appManager: EntityManager, + tjdbManager: EntityManager, + tooljetDbService: TooljetDbService, + organizationId: string, + params: { table_name: string; columns: TooljetDatabaseColumn[]; foreign_keys: TooljetDatabaseForeignKey[] } +) { + await tooljetDbService.perform(organizationId, 'create_table', params, { appManager, tjdbManager }); +} + +export async function dropTable( + appManager: EntityManager, + tjdbManager: EntityManager, + tooljetDbService: TooljetDbService, + organizationId: string, + tableName: string +) { + await tooljetDbService.perform(organizationId, 'drop_table', { table_name: tableName }, { appManager, tjdbManager }); + + await appManager.delete(InternalTable, { organizationId, tableName }); +} diff --git a/server/tsconfig.json b/server/tsconfig.json index d7ffc05161..af2a208a43 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -14,6 +14,7 @@ "incremental": true, "paths": { "@ee/*": ["ee/*"], + "@entities/*": ["src/entities/*"], "@services/*": ["src/services/*"], "@controllers/*": ["src/controllers/*"], "@repositories/*": ["src/repositories/*"],