diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..35b27c2 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,69 @@ +name: Run App and Test + +on: + pull_request: + branches: + - develop + +jobs: + test: + timeout-minutes: 15 + runs-on: ubuntu-latest + strategy: + fail-fast: false + + steps: + + - name: Install Node + uses: actions/setup-node@v3 + with: + node-version: '16' + + - name: Checkout self + uses: actions/checkout@v3 + + - name: Install Test dependencies + run: | + npm ci + + - name: Install API + uses: actions/checkout@v3 + with: + repository: nicolo-riboni-sp/qa-app-api + path: './temp/api' + ref: 'develop' + + - name: Install API dependencies and run + run: | + cd ./temp/api + npm ci + npm run start & npx wait-on http://localhost:8080 + env: + CI: true + + - name: Install UI + uses: actions/checkout@v3 + with: + repository: nicolo-riboni-sp/qa-app-ui + path: './temp/ui' + ref: 'develop' + + + - name: Install UI dependencies and run + run: | + cd ./temp/ui + npm ci + npm run dev & sleep 2 + env: + CI: true + + - name: Run API tests + continue-on-error: true + run: | + npm run test:api + + + - name: Run UI tests + run: | + npm run test:ui + \ No newline at end of file diff --git a/jest-puppeteer.config.js b/jest-puppeteer.config.js index d379d04..6d5e291 100644 --- a/jest-puppeteer.config.js +++ b/jest-puppeteer.config.js @@ -1,6 +1,6 @@ module.exports = { launch: { - headless: false, + headless: true, slowMo: 10, defaultViewport: null, args: ['--window-size=1920,1080', '--no-sandbox', '--disable-setuid-sandbox', '--disable-web-security', '--disable-dev-shm-usage', '--shm-size=6gb', diff --git a/package-lock.json b/package-lock.json index 0d534f4..633a19a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "jest": "^29.2.1", "jest-puppeteer": "6.1.0", "lint": "^0.7.0", - "puppeteer": "^19.1.0" + "puppeteer": "^20.8.3" } }, "node_modules/@ampproject/remapping": { @@ -813,12 +813,12 @@ } }, "node_modules/@jest/expect-utils": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.2.1.tgz", - "integrity": "sha512-yr4aHNg5Z1CjKby5ozm7sKjgBlCOorlAoFcvrOQ/4rbZRfgZQdnmh7cth192PYIgiPZo2bBXvqdOApnAMWFJZg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.1.tgz", + "integrity": "sha512-o319vIf5pEMx0LmzSxxkYYxo4wrRLKHq9dP1yJU7FoPTB0LfAKSz8SWD6D/6U3v/O52t9cF5t+MeJiRsfk7zMw==", "dev": true, "dependencies": { - "jest-get-type": "^29.2.0" + "jest-get-type": "^29.4.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -900,12 +900,12 @@ } }, "node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", + "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.24.1" + "@sinclair/typebox": "^0.27.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -982,12 +982,12 @@ } }, "node_modules/@jest/types": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.2.1.tgz", - "integrity": "sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", "dev": true, "dependencies": { - "@jest/schemas": "^29.0.0", + "@jest/schemas": "^29.6.0", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -1092,6 +1092,38 @@ "typescript": "^3 || ^4" } }, + "node_modules/@puppeteer/browsers": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.5.tgz", + "integrity": "sha512-a0gpUa+XlxZHotoOklh99X6RC5R+hQGcVcYOH+oOIEBfQXPp8Z5c765XAu/zhxsjRuAZN4Xx4vZNlwN4wJro2A==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "7.0.0", + "https-proxy-agent": "7.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "socks-proxy-agent": "8.0.1", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -1114,9 +1146,9 @@ "dev": true }, "node_modules/@sinclair/typebox": { - "version": "0.24.47", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.47.tgz", - "integrity": "sha512-J4Xw0xYK4h7eC34MNOPQi6IkNxGRck6n4VJpWDzXIFVTW8I/D43Gf+NfWz/v/7NHlzWOPd3+T4PJ4OqklQ2u7A==", + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, "node_modules/@sinonjs/commons": { @@ -1229,12 +1261,6 @@ "integrity": "sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==", "dev": true }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, "node_modules/@types/prettier": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", @@ -1471,15 +1497,15 @@ } }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", "dev": true, "dependencies": { - "debug": "4" + "debug": "^4.3.4" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/ajv": { @@ -1662,6 +1688,12 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", + "dev": true + }, "node_modules/babel-jest": { "version": "29.2.1", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.2.1.tgz", @@ -1788,17 +1820,6 @@ "tweetnacl": "^0.14.3" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1981,11 +2002,17 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true + "node_modules/chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "dev": true, + "dependencies": { + "mitt": "3.0.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } }, "node_modules/ci-info": { "version": "3.5.0", @@ -2165,28 +2192,48 @@ "dev": true }, "node_modules/cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", + "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", "dev": true, "dependencies": { - "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "path-type": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + } + }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", "dev": true, "dependencies": { - "node-fetch": "2.6.7" + "node-fetch": "^2.6.12" } }, "node_modules/cross-spawn": { @@ -2321,15 +2368,15 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1045489", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1045489.tgz", - "integrity": "sha512-D+PTmWulkuQW4D1NTiCRCFxF7pQPn0hgp4YyX4wAQ6xYXKOadSWPR3ENGDQ47MW/Ewc9v2rpC/UEEGahgBYpSQ==", + "version": "0.0.1135028", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1135028.tgz", + "integrity": "sha512-jEcNGrh6lOXNRJvZb9RjeevtZGrgugPKSMJZxfyxWQnhlKawMPhMtk/dfC+Z/6xNXExlzTKlY5LzIAK/fRpQIw==", "dev": true }, "node_modules/diff-sequences": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.2.0.tgz", - "integrity": "sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -2937,16 +2984,17 @@ } }, "node_modules/expect": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.2.1.tgz", - "integrity": "sha512-BJtA754Fba0YWRWHgjKUMTA3ltWarKgITXHQnbZ2mTxTXC4yMQlR0FI7HkB3fJYkhWBf4qjNiqvg3LDtXCcVRQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.1.tgz", + "integrity": "sha512-XEdDLonERCU1n9uR56/Stx9OqojaLAQtZf9PrCHH9Hl8YXiEIka3H4NXJ3NOIBmQJTg7+j7buh34PMHfJujc8g==", "dev": true, "dependencies": { - "@jest/expect-utils": "^29.2.1", - "jest-get-type": "^29.2.0", - "jest-matcher-utils": "^29.2.1", - "jest-message-util": "^29.2.1", - "jest-util": "^29.2.1" + "@jest/expect-utils": "^29.6.1", + "@types/node": "*", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3040,6 +3088,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-fifo": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.0.tgz", + "integrity": "sha512-IgfweLvEpwyA4WgiQe9Nx6VV2QkML2NkvZnk1oKnIzXgXdWxuhF7zw4DvLTPZJn6PIUneiAXPF24QmoEqHTjyw==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", @@ -3285,12 +3339,6 @@ "node": ">= 6" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, "node_modules/fs-exists-sync": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", @@ -3654,6 +3702,19 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -3670,16 +3731,16 @@ } }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz", + "integrity": "sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ==", "dev": true, "dependencies": { - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/human-signals": { @@ -3980,6 +4041,12 @@ "node": ">= 0.4" } }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -4499,15 +4566,15 @@ } }, "node_modules/jest-diff": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.2.1.tgz", - "integrity": "sha512-gfh/SMNlQmP3MOUgdzxPOd4XETDJifADpT937fN1iUGz+9DgOu2eUPHH25JDkLVcLwwqxv3GzVyK4VBUr9fjfA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.1.tgz", + "integrity": "sha512-FsNCvinvl8oVxpNLttNQX7FAq7vR+gMDGj90tiP7siWw1UdakWUGqrylpsYrpvj908IYckm5Y0Q7azNAozU1Kg==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^29.2.0", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.2.1" + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -4737,9 +4804,9 @@ "dev": true }, "node_modules/jest-get-type": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -4784,33 +4851,33 @@ } }, "node_modules/jest-matcher-utils": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.2.1.tgz", - "integrity": "sha512-hUTBh7H/Mnb6GTpihbLh8uF5rjAMdekfW/oZNXUMAXi7bbmym2HiRpzgqf/zzkjgejMrVAkPdVSQj+32enlUww==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.1.tgz", + "integrity": "sha512-SLaztw9d2mfQQKHmJXKM0HCbl2PPVld/t9Xa6P9sgiExijviSp7TnZZpw2Fpt+OI3nwUO/slJbOfzfUMKKC5QA==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.2.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.2.1" + "jest-diff": "^29.6.1", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.2.1.tgz", - "integrity": "sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.1.tgz", + "integrity": "sha512-KoAW2zAmNSd3Gk88uJ56qXUWbFk787QKmjjJVOjtGFmmGSZgDBrlIL4AfQw1xyMYPNVD7dNInfIbur9B2rd/wQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.2.1", + "@jest/types": "^29.6.1", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.2.1", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -5035,12 +5102,12 @@ } }, "node_modules/jest-util": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.2.1.tgz", - "integrity": "sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "@jest/types": "^29.2.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -5646,6 +5713,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mitt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", + "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "dev": true + }, "node_modules/mixin-object": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", @@ -5714,9 +5787,9 @@ "dev": true }, "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", "dev": true, "dependencies": { "whatwg-url": "^5.0.0" @@ -6191,12 +6264,12 @@ } }, "node_modules/pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { - "@jest/schemas": "^29.0.0", + "@jest/schemas": "^29.6.0", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -6269,41 +6342,43 @@ } }, "node_modules/puppeteer": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.1.0.tgz", - "integrity": "sha512-UyJ5gz5JNjuFo6VJzIf+qDNjbSWGSoAMLuW990eErcrH6sZP85EbpLi6yG50euTMudxO/lsj4w1VNDNogHv6dA==", + "version": "20.8.3", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-20.8.3.tgz", + "integrity": "sha512-NaJEZkhRmcIR0KTLfJktLF2aRpf+Nvr/YQi1geUSUkANWKRmIj+u+Fh9Us9tEzuuuuk56okJ+fhV4vrimQzI3g==", "dev": true, "hasInstallScript": true, "dependencies": { - "cosmiconfig": "7.0.1", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "puppeteer-core": "19.1.0" + "@puppeteer/browsers": "1.4.5", + "cosmiconfig": "8.2.0", + "puppeteer-core": "20.8.3" }, "engines": { - "node": ">=14.1.0" + "node": ">=16.3.0" } }, "node_modules/puppeteer-core": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.1.0.tgz", - "integrity": "sha512-xIIJJuvqWbUwNzaB7l0TyChJYHdLvLhcHQiBLLKsMfvaQXnVa0Fzooq3Zb5bc01Q/b7XiP9pqDvUcYWSmzZQHA==", + "version": "20.8.3", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.8.3.tgz", + "integrity": "sha512-/57/tKtjjRyPP/FgZMG+nkmyajm4OXms6Qu0r8sbTu5tyIbCQTqx82yZj859Rx0Zf0enOVEnN1pD9cqVc9i78g==", "dev": true, "dependencies": { - "cross-fetch": "3.1.5", + "@puppeteer/browsers": "1.4.5", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", "debug": "4.3.4", - "devtools-protocol": "0.0.1045489", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.9.0" + "devtools-protocol": "0.0.1135028", + "ws": "8.13.0" }, "engines": { - "node": ">=14.1.0" + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/qs": { @@ -6335,26 +6410,18 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -7004,6 +7071,44 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.1.tgz", + "integrity": "sha512-59EjPbbgg8U3x62hhKOFVAmySQUcfRQ4C7Q/D5sEHnZTQRrQlNKINks44DMR1gwXp0p4LaVIeccX2KHTTcHVqQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -7086,13 +7191,14 @@ "node": ">=8" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/streamx": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.0.tgz", + "integrity": "sha512-HcxY6ncGjjklGs1xsP1aR71INYcsXFJet5CU1CHqihQ2J5nOsbd4OjgjHO42w/4QNv9gZb3BueV+Vxok5pLEXg==", "dev": true, "dependencies": { - "safe-buffer": "~5.2.0" + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" } }, "node_modules/string-length": { @@ -7217,31 +7323,25 @@ } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", "dev": true, "dependencies": { - "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "tar-stream": "^3.1.5" } }, "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", + "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", "dev": true, "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, "node_modules/test-exclude": { @@ -7534,12 +7634,6 @@ "punycode": "^2.1.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, "node_modules/uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -7771,16 +7865,16 @@ } }, "node_modules/ws": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", - "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "dev": true, "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -7806,19 +7900,10 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/yargs": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz", - "integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==", + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, "dependencies": { "cliui": "^8.0.1", @@ -7827,7 +7912,7 @@ "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" + "yargs-parser": "^21.1.1" }, "engines": { "node": ">=12" @@ -8467,12 +8552,12 @@ } }, "@jest/expect-utils": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.2.1.tgz", - "integrity": "sha512-yr4aHNg5Z1CjKby5ozm7sKjgBlCOorlAoFcvrOQ/4rbZRfgZQdnmh7cth192PYIgiPZo2bBXvqdOApnAMWFJZg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.1.tgz", + "integrity": "sha512-o319vIf5pEMx0LmzSxxkYYxo4wrRLKHq9dP1yJU7FoPTB0LfAKSz8SWD6D/6U3v/O52t9cF5t+MeJiRsfk7zMw==", "dev": true, "requires": { - "jest-get-type": "^29.2.0" + "jest-get-type": "^29.4.3" } }, "@jest/fake-timers": { @@ -8534,12 +8619,12 @@ } }, "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", + "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", "dev": true, "requires": { - "@sinclair/typebox": "^0.24.1" + "@sinclair/typebox": "^0.27.8" } }, "@jest/source-map": { @@ -8601,12 +8686,12 @@ } }, "@jest/types": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.2.1.tgz", - "integrity": "sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", "dev": true, "requires": { - "@jest/schemas": "^29.0.0", + "@jest/schemas": "^29.6.0", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -8687,6 +8772,24 @@ "esquery": "^1.0.1" } }, + "@puppeteer/browsers": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.5.tgz", + "integrity": "sha512-a0gpUa+XlxZHotoOklh99X6RC5R+hQGcVcYOH+oOIEBfQXPp8Z5c765XAu/zhxsjRuAZN4Xx4vZNlwN4wJro2A==", + "dev": true, + "requires": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "7.0.0", + "https-proxy-agent": "7.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "socks-proxy-agent": "8.0.1", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + } + }, "@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -8709,9 +8812,9 @@ "dev": true }, "@sinclair/typebox": { - "version": "0.24.47", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.47.tgz", - "integrity": "sha512-J4Xw0xYK4h7eC34MNOPQi6IkNxGRck6n4VJpWDzXIFVTW8I/D43Gf+NfWz/v/7NHlzWOPd3+T4PJ4OqklQ2u7A==", + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, "@sinonjs/commons": { @@ -8824,12 +8927,6 @@ "integrity": "sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==", "dev": true }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, "@types/prettier": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", @@ -8991,12 +9088,12 @@ "requires": {} }, "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", "dev": true, "requires": { - "debug": "4" + "debug": "^4.3.4" } }, "ajv": { @@ -9133,6 +9230,12 @@ "proxy-from-env": "^1.1.0" } }, + "b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", + "dev": true + }, "babel-jest": { "version": "29.2.1", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.2.1.tgz", @@ -9224,17 +9327,6 @@ "tweetnacl": "^0.14.3" } }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -9353,11 +9445,14 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true + "chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "dev": true, + "requires": { + "mitt": "3.0.0" + } }, "ci-info": { "version": "3.5.0", @@ -9503,25 +9598,41 @@ "dev": true }, "cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", + "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", "dev": true, "requires": { - "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "path-type": "^4.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + } } }, "cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", "dev": true, "requires": { - "node-fetch": "2.6.7" + "node-fetch": "^2.6.12" } }, "cross-spawn": { @@ -9618,15 +9729,15 @@ "dev": true }, "devtools-protocol": { - "version": "0.0.1045489", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1045489.tgz", - "integrity": "sha512-D+PTmWulkuQW4D1NTiCRCFxF7pQPn0hgp4YyX4wAQ6xYXKOadSWPR3ENGDQ47MW/Ewc9v2rpC/UEEGahgBYpSQ==", + "version": "0.0.1135028", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1135028.tgz", + "integrity": "sha512-jEcNGrh6lOXNRJvZb9RjeevtZGrgugPKSMJZxfyxWQnhlKawMPhMtk/dfC+Z/6xNXExlzTKlY5LzIAK/fRpQIw==", "dev": true }, "diff-sequences": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.2.0.tgz", - "integrity": "sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", "dev": true }, "dir-glob": { @@ -10091,16 +10202,17 @@ } }, "expect": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.2.1.tgz", - "integrity": "sha512-BJtA754Fba0YWRWHgjKUMTA3ltWarKgITXHQnbZ2mTxTXC4yMQlR0FI7HkB3fJYkhWBf4qjNiqvg3LDtXCcVRQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.1.tgz", + "integrity": "sha512-XEdDLonERCU1n9uR56/Stx9OqojaLAQtZf9PrCHH9Hl8YXiEIka3H4NXJ3NOIBmQJTg7+j7buh34PMHfJujc8g==", "dev": true, "requires": { - "@jest/expect-utils": "^29.2.1", - "jest-get-type": "^29.2.0", - "jest-matcher-utils": "^29.2.1", - "jest-message-util": "^29.2.1", - "jest-util": "^29.2.1" + "@jest/expect-utils": "^29.6.1", + "@types/node": "*", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1" } }, "expect-puppeteer": { @@ -10170,6 +10282,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "fast-fifo": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.0.tgz", + "integrity": "sha512-IgfweLvEpwyA4WgiQe9Nx6VV2QkML2NkvZnk1oKnIzXgXdWxuhF7zw4DvLTPZJn6PIUneiAXPF24QmoEqHTjyw==", + "dev": true + }, "fast-glob": { "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", @@ -10360,12 +10478,6 @@ "mime-types": "^2.1.12" } }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, "fs-exists-sync": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", @@ -10627,6 +10739,16 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -10639,12 +10761,12 @@ } }, "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz", + "integrity": "sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ==", "dev": true, "requires": { - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" } }, @@ -10869,6 +10991,12 @@ "side-channel": "^1.0.4" } }, + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -11259,15 +11387,15 @@ } }, "jest-diff": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.2.1.tgz", - "integrity": "sha512-gfh/SMNlQmP3MOUgdzxPOd4XETDJifADpT937fN1iUGz+9DgOu2eUPHH25JDkLVcLwwqxv3GzVyK4VBUr9fjfA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.1.tgz", + "integrity": "sha512-FsNCvinvl8oVxpNLttNQX7FAq7vR+gMDGj90tiP7siWw1UdakWUGqrylpsYrpvj908IYckm5Y0Q7azNAozU1Kg==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^29.2.0", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.2.1" + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.1" } }, "jest-docblock": { @@ -11457,9 +11585,9 @@ } }, "jest-get-type": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", "dev": true }, "jest-haste-map": { @@ -11493,30 +11621,30 @@ } }, "jest-matcher-utils": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.2.1.tgz", - "integrity": "sha512-hUTBh7H/Mnb6GTpihbLh8uF5rjAMdekfW/oZNXUMAXi7bbmym2HiRpzgqf/zzkjgejMrVAkPdVSQj+32enlUww==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.1.tgz", + "integrity": "sha512-SLaztw9d2mfQQKHmJXKM0HCbl2PPVld/t9Xa6P9sgiExijviSp7TnZZpw2Fpt+OI3nwUO/slJbOfzfUMKKC5QA==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^29.2.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.2.1" + "jest-diff": "^29.6.1", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.1" } }, "jest-message-util": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.2.1.tgz", - "integrity": "sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.1.tgz", + "integrity": "sha512-KoAW2zAmNSd3Gk88uJ56qXUWbFk787QKmjjJVOjtGFmmGSZgDBrlIL4AfQw1xyMYPNVD7dNInfIbur9B2rd/wQ==", "dev": true, "requires": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.2.1", + "@jest/types": "^29.6.1", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.2.1", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "stack-utils": "^2.0.3" } @@ -11696,12 +11824,12 @@ } }, "jest-util": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.2.1.tgz", - "integrity": "sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "requires": { - "@jest/types": "^29.2.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -12157,6 +12285,12 @@ "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", "dev": true }, + "mitt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", + "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "dev": true + }, "mixin-object": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", @@ -12215,9 +12349,9 @@ "dev": true }, "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", "dev": true, "requires": { "whatwg-url": "^5.0.0" @@ -12560,12 +12694,12 @@ "dev": true }, "pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "requires": { - "@jest/schemas": "^29.0.0", + "@jest/schemas": "^29.6.0", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -12622,34 +12756,28 @@ "dev": true }, "puppeteer": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.1.0.tgz", - "integrity": "sha512-UyJ5gz5JNjuFo6VJzIf+qDNjbSWGSoAMLuW990eErcrH6sZP85EbpLi6yG50euTMudxO/lsj4w1VNDNogHv6dA==", + "version": "20.8.3", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-20.8.3.tgz", + "integrity": "sha512-NaJEZkhRmcIR0KTLfJktLF2aRpf+Nvr/YQi1geUSUkANWKRmIj+u+Fh9Us9tEzuuuuk56okJ+fhV4vrimQzI3g==", "dev": true, "requires": { - "cosmiconfig": "7.0.1", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "puppeteer-core": "19.1.0" + "@puppeteer/browsers": "1.4.5", + "cosmiconfig": "8.2.0", + "puppeteer-core": "20.8.3" } }, "puppeteer-core": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.1.0.tgz", - "integrity": "sha512-xIIJJuvqWbUwNzaB7l0TyChJYHdLvLhcHQiBLLKsMfvaQXnVa0Fzooq3Zb5bc01Q/b7XiP9pqDvUcYWSmzZQHA==", + "version": "20.8.3", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.8.3.tgz", + "integrity": "sha512-/57/tKtjjRyPP/FgZMG+nkmyajm4OXms6Qu0r8sbTu5tyIbCQTqx82yZj859Rx0Zf0enOVEnN1pD9cqVc9i78g==", "dev": true, "requires": { - "cross-fetch": "3.1.5", + "@puppeteer/browsers": "1.4.5", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", "debug": "4.3.4", - "devtools-protocol": "0.0.1045489", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.9.0" + "devtools-protocol": "0.0.1135028", + "ws": "8.13.0" } }, "qs": { @@ -12664,23 +12792,18 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, "regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -13173,6 +13296,33 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true + }, + "socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.1.tgz", + "integrity": "sha512-59EjPbbgg8U3x62hhKOFVAmySQUcfRQ4C7Q/D5sEHnZTQRrQlNKINks44DMR1gwXp0p4LaVIeccX2KHTTcHVqQ==", + "dev": true, + "requires": { + "agent-base": "^7.0.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -13240,13 +13390,14 @@ } } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "streamx": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.0.tgz", + "integrity": "sha512-HcxY6ncGjjklGs1xsP1aR71INYcsXFJet5CU1CHqihQ2J5nOsbd4OjgjHO42w/4QNv9gZb3BueV+Vxok5pLEXg==", "dev": true, "requires": { - "safe-buffer": "~5.2.0" + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" } }, "string-length": { @@ -13335,28 +13486,25 @@ "dev": true }, "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", "dev": true, "requires": { - "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "tar-stream": "^3.1.5" } }, "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", + "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", "dev": true, "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, "test-exclude": { @@ -13572,12 +13720,6 @@ "punycode": "^2.1.0" } }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -13768,9 +13910,9 @@ } }, "ws": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", - "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "dev": true, "requires": {} }, @@ -13786,16 +13928,10 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true - }, "yargs": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz", - "integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==", + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, "requires": { "cliui": "^8.0.1", @@ -13804,7 +13940,7 @@ "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" + "yargs-parser": "^21.1.1" } }, "yargs-parser": { diff --git a/package.json b/package.json index ade6cfa..c050af2 100644 --- a/package.json +++ b/package.json @@ -20,17 +20,17 @@ }, "homepage": "https://github.com/nicolo-riboni-sp/qa-assessment#readme", "devDependencies": { - "jest": "^29.2.1", - "lint": "^0.7.0", - "puppeteer": "^19.1.0", - "jest-puppeteer": "6.1.0", "eslint": "8.9.0", "eslint-config-airbnb-base": "15.0.0", "eslint-plugin-etc": "2.0.2", "eslint-plugin-import": "2.25.4", - "eslint-plugin-sonarjs": "0.12.0" + "eslint-plugin-sonarjs": "0.12.0", + "jest": "^29.2.1", + "jest-puppeteer": "6.1.0", + "lint": "^0.7.0", + "puppeteer": "^20.8.3" }, "dependencies": { "axios": "^1.1.3" } -} \ No newline at end of file +} diff --git a/src/page-objects/createTransactionObject.js b/src/page-objects/createTransactionObject.js new file mode 100644 index 0000000..5bfc23d --- /dev/null +++ b/src/page-objects/createTransactionObject.js @@ -0,0 +1,45 @@ + + +exports.CreateTransaction = class CreateTransaction { + constructor(page) { + this.page = page + this.url = `http://localhost:8081/transfer/1` + this.amountInput = page.locator('#amount') + this.receiverSelect = page.locator('#receiver') + this.receiverOption = page.locator('#receiver-1') + this.makeTransactionButton = page.locator('#createTransaction') + this.goBackButton = page.locator('#goBack') + } + + async visit() { + await this.page.goto(this.url) + } + + async createTransaction(amount) { + await this.receiverSelect.click() + this.page.evaluate(() => { + document.querySelector('#receiver-1').selected = true + }) + await this.amountInput.fill(amount) + await this.makeTransactionButton.click() + } + + async submitEmptyTransaction() { + await this.makeTransactionButton.click() + } + + async navigateBackToUsers() { + await this.goBackButton.click() + } + + async assertSuccess() { + const isSuccessMessageVisible = await this.page.waitForSelector('#message', { visible: true }); + expect(isSuccessMessageVisible).toBeTruthy(); + } + + async assertError() { + const isFailureMessageVisible = await this.page.waitForSelector('text=AxiosError: Request failed with status code 400', { visible: true }) + + expect(isFailureMessageVisible).toBeTruthy() + } +} diff --git a/src/page-objects/createUserObject.js b/src/page-objects/createUserObject.js new file mode 100644 index 0000000..4734cce --- /dev/null +++ b/src/page-objects/createUserObject.js @@ -0,0 +1,39 @@ + + +exports.CreateUser = class CreateUser { + constructor(page) { + this.page = page + this.url = 'http://localhost:8081/create' + this.nameInput = page.locator('#name') + this.amountInput = page.locator('#balance') + this.submitButton = page.locator('#createUser') + this.addButton = page.locator('#newUser') + } + + async visit() { + await this.page.goto(this.url) + } + + async createUser(name, amount) { + await this.nameInput.fill(name) + await this.amountInput.fill(amount) + await this.submitButton.click() + } + + async addUser() { + await this.addButton.click() + } + + async assertSuccess() { + const isSuccessMessageVisible = await page.waitForSelector('#successMessage', { visible: true }); + expect(isSuccessMessageVisible).toBeTruthy(); + } + + async assertError() { + const isMessageAbsent = this.page.evaluate(() => { + return document.querySelector('#successMessage') == null + }) + + expect(isMessageAbsent).toBeTruthy() + } +} diff --git a/src/page-objects/topUpBalanceObject.js b/src/page-objects/topUpBalanceObject.js new file mode 100644 index 0000000..fbdceed --- /dev/null +++ b/src/page-objects/topUpBalanceObject.js @@ -0,0 +1,38 @@ + +exports.TopUpBalance = class TopUpBalance { + constructor(page) { + this.page = page + this.url = `http://localhost:8081/users/1` + this.topupInput = page.locator('#topup') + this.addBalanceButton = page.locator('#updateBalance') + this.goBackButton = page.locator('#goBack') + } + + async visit() { + await this.page.goto(this.url) + } + + async updateBalance(topup) { + await this.topupInput.fill(topup) + await this.addBalanceButton.click() + } + + async submitEmptyTransaction() { + await this.addBalanceButton.click() + } + + async navigateBackToUsers() { + await this.goBackButton.click() + } + + async assertSuccess() { + const isSuccessMessageVisible = await page.waitForSelector('text=The User was updated successfully!', { visible: true , timeout: 2000}) + expect(isSuccessMessageVisible).toBeTruthy(); + } + + async assertError() { + const isFailureMessageVisible = await this.page.waitForSelector('text=AxiosError: Request failed with status code 400', { visible: true, timeout: 2000 }) + + expect(isFailureMessageVisible).toBeTruthy() + } +} \ No newline at end of file diff --git a/src/page-objects/usersObject.js b/src/page-objects/usersObject.js new file mode 100644 index 0000000..4bc732a --- /dev/null +++ b/src/page-objects/usersObject.js @@ -0,0 +1,77 @@ +exports.HomePage = class HomePage { + constructor(page) { + this.page = page + this.url = 'http://localhost:8081/users' + this.userList = page.locator('#userList') + this.infoMessage = page.locator('#infoMessage') + this.listItem = page.locator(`#user-1`) + this.addBalanceButton = page.locator('#addBalance') + this.makeTransactionButton = page.locator('#makeTransaction') + this.name = page.locator('#name') + this.balance = page.locator('#balance') + this.transactionList = page.locator('#transactionList') + this.transactionItems = page.locator('id^=transaction-') + + } + + getListItemLocator(index) { + page.locator(`#user-${index}`) + } + + async visit() { + await this.page.goto(this.url) + } + + async navigateToUserId() { + await this.addBalanceButton.click() + } + + async navigateToTransfer() { + await this.makeTransactionButton.click() + } + + async openUserDetail() { + await this.listItem.click() + } + + + async assertInitialPageLoadSuccess() { + const isListVisible = await this.page.waitForSelector('#userList', { visible: true, timeout: 2000 }); + const isInfoMessageVisible = await this.page.waitForSelector('#infoMessage', { visible: true, timeout: 2000 }); + expect(isListVisible).toBeTruthy() + expect(isInfoMessageVisible).toBeTruthy() + } + + async assertUserDetailsLoadSuccess() { + const isAddBalanceButtonVisible = await this.page.waitForSelector('#addBalance', { visible: true, timeout: 2000 }); + const isMakeTransactionButtonVisible = await this.page.waitForSelector('#makeTransaction', { visible: true, timeout: 2000 }); + const isTransactionListVisible = await this.page.waitForSelector('#transactionList', { visible: true, timeout: 2000 }); + + expect(isAddBalanceButtonVisible).toBeTruthy() + expect(isMakeTransactionButtonVisible).toBeTruthy() + expect(isTransactionListVisible).toBeTruthy() + } + + async assertUserDetailsCorrectness(expectedName, expectedBalance, expectedTransactionsCount) { + + const name = await this.page.evaluate(() => { + const name = document.querySelector('#name').innerText + return name + }) + + const balance = await this.page.evaluate(() => { + const balance = document.querySelector('#balance').innerText.slice(0, -1) + return +balance + }) + + const transactionsCount = await this.page.evaluate(() => { + const transactionsCount = document.querySelectorAll('#transactionList > li').length + return transactionsCount + }) + + expect(name).toBe(expectedName) + expect(balance).toBe(expectedBalance) + expect(transactionsCount).toBe(expectedTransactionsCount) + } + +} diff --git a/src/test/api/createUser.test.js b/src/test/api/createUser.test.js deleted file mode 100644 index 5097f1e..0000000 --- a/src/test/api/createUser.test.js +++ /dev/null @@ -1,38 +0,0 @@ -const { makeAxiosRequestWithExpectedStatus } = require('../../utilities/common') - -describe('GIVEN valid data for a user', () => { - const name = 'Alex'; - const balance = 1000; - let userId; - - describe('WHEN I create a new user using POST', () => { - const expectedStatus = 201; - const requestParams = { - url: 'http://localhost:8080/api/users', - method: 'POST', - data: { - name, - balance, - } - }; - let createUserApiResponse; - beforeAll(async () => { - createUserApiResponse = await makeAxiosRequestWithExpectedStatus(requestParams, expectedStatus); - }); - - it('THEN the user created is returned in the response', () => { - expect(createUserApiResponse.data.name).toBe(name); - expect(createUserApiResponse.data.balance).toBe(balance); - userId = createUserApiResponse.data.id; - }); - }); - - afterAll(async () => { - const expectedStatus = 200; - const requestParams = { - url: `http://localhost:8080/api/users/${userId}`, - method: 'DELETE', - }; - await makeAxiosRequestWithExpectedStatus(requestParams, expectedStatus) - }); -}); \ No newline at end of file diff --git a/src/test/api/transactions/createTransaction.test.js b/src/test/api/transactions/createTransaction.test.js new file mode 100644 index 0000000..5c9e150 --- /dev/null +++ b/src/test/api/transactions/createTransaction.test.js @@ -0,0 +1,484 @@ +const { createTransaction } = require('../../../utilities/transactions') +const { createUser, deleteUser, getUser } = require('../../../utilities/users') + + +// VALID TRANSACTION + +describe('GIVEN valid data for a new transaction', () => { + const amount = 10 + let sender + let receiver + + describe('WHEN I create a new transaction using POST', () => { + const expectedStatus = 201 + + let createTransactionApiResponse + let senderAfterTrx + let receiverAfterTrx + beforeAll(async () => { + sender = await createUser('Sasha', 100) + receiver = await createUser('Pan', 100) + + createTransactionApiResponse = await createTransaction(amount, sender.id, receiver.id, expectedStatus) + + senderAfterTrx = await getUser(sender.id) + receiverAfterTrx = await getUser(receiver.id) + }) + + + it("THEN the transaction created is returned in the response" + + "AND users' balances are modified accordingly", () => { + + expect(senderAfterTrx.balance - sender.balance).toBe(-amount) + expect(receiverAfterTrx.balance - receiver.balance).toBe(amount) + + expect(createTransactionApiResponse.data.amount).toBe(amount) + expect(createTransactionApiResponse.data.sender).toBe(sender.id) + expect(createTransactionApiResponse.data.receiver).toBe(receiver.id) + }) + }) + + + afterAll(async () => { + [sender.id, receiver.id].forEach((id) => deleteUser(id)) + }); +}) + +/* +* ============================================================================= +* Warning! The below test will break the app and will require a server restart! +* +// _._ _,-'""`-._ +// (,-.`._,'( |\`-/| +// `-.-' \ )-`( , o o) +// `- \`_`"'- +* ============================================================================= +*/ + +// // AMOUNT = NON NUMERIC + +// describe('GIVEN invalid data for a new transaction with non-numeric amount', () => { +// const amount = 'string' +// let sender +// let receiver + +// describe('WHEN I create a new transaction using POST', () => { +// const expectedStatus = 422 // unprocessable content + +// let createTransactionApiResponse +// beforeAll(async () => { +// sender = await createUser('Sasha', 100) +// receiver = await createUser('Pan', 100) + +// createTransactionApiResponse = await createTransaction(amount, sender.id, receiver.id, expectedStatus) }) + +// it('THEN an error message is returned in the response', () => { +// expect(createTransactionApiResponse.data.message).toBe('Amount field must be numeric') +// }) +// }) + +// afterAll(async () => { +// [sender.id, receiver.id].forEach((id) => deleteUser(id)) +// }); +// }) + +// AMOUNT = 0 + +describe('GIVEN invalid data for a new transaction with amount 0', () => { + const amount = 0 + let sender + let receiver + + describe('WHEN I create a new transaction using POST', () => { + const expectedStatus = 400 + + let createTransactionApiResponse + beforeAll(async () => { + sender = await createUser('Sasha', 100) + receiver = await createUser('Pan', 100) + + createTransactionApiResponse = await createTransaction(amount, sender.id, receiver.id, expectedStatus) + }) + + + it('THEN an error message is returned in the response', () => { + expect(createTransactionApiResponse.data.message).toBe('Amount field must be present in the body') + }) + }) + + + afterAll(async () => { + [sender.id, receiver.id].forEach((id) => deleteUser(id)) + }); +}) + +// AMOUNT > BALANCE + +describe("GIVEN invalid data for a new transaction with amount > sender's balance", () => { + let amount + let sender + let receiver + + describe('WHEN I create a new transaction using POST', () => { + const expectedStatus = 409 + + let createTransactionApiResponse + + beforeAll(async () => { + sender = await createUser('Sasha', 100) + receiver = await createUser('Pan', 100) + + amount = sender.balance + 10 + createTransactionApiResponse = await createTransaction(amount, sender.id, receiver.id, expectedStatus) + senderAfterTrx = await getUser(sender.id) + receiverAfterTrx = await getUser(receiver.id) + }) + + + it('THEN an error message is returned in the response', () => { + expect(createTransactionApiResponse.data.message).not.toBe("Sender doesn't have sufficient funds") + }) + }) + + + afterAll(async () => { + [sender.id, receiver.id].forEach((id) => deleteUser(id)) + }); +}) + + +// NEGATIVE AMOUNT + +describe('GIVEN invalid data for a new transaction with negative amount', () => { + const amount = -10 + let sender + let receiver + + describe('WHEN I create a new transaction using POST', () => { + const expectedStatus = 422 + + let createTransactionApiResponse + + beforeAll(async () => { + sender = await createUser('Sasha', 100) + receiver = await createUser('Pan', 100) + + createTransactionApiResponse = await createTransaction(amount, sender.id, receiver.id, expectedStatus) + senderAfterTrx = await getUser(sender.id) + receiverAfterTrx = await getUser(receiver.id) + }) + + + it('THEN an error message is returned in the response', () => { + expect(createTransactionApiResponse.data.message).toBe('Amount field must be a positive number') + }) + }) + + + afterAll(async () => { + [sender.id, receiver.id].forEach((id) => deleteUser(id)) + }); +}) + + +// AMOUNT EXCEEDS MAX SAFE INTEGER + +describe('GIVEN invalid data for a new transaction with amount > MAX_SAFE_INTEGER ', () => { + const maxSafeInteger = Number.MAX_SAFE_INTEGER + const amount = maxSafeInteger + 1 + let sender + let receiver + + describe('WHEN I create a new transaction using POST', () => { + const expectedStatus = 422 + + let createTransactionApiResponse + + beforeAll(async () => { + sender = await createUser('Sasha', 100) + receiver = await createUser('Pan', 100) + + createTransactionApiResponse = await createTransaction(amount, sender.id, receiver.id, expectedStatus) + senderAfterTrx = await getUser(sender.id) + receiverAfterTrx = await getUser(receiver.id) + }) + + + it('THEN an error message is returned in the response', () => { + expect(createTransactionApiResponse.data.message).toBe('Amount field must not exceed: ' + Number.MAX_SAFE_INTEGER) + // expect(senderAfterTrx.balance).toBe(sender.balance) + // expect(receiverAfterTrx.balance).toBe(receiver.balance) + }) + }) + + + afterAll(async () => { + [sender.id, receiver.id].forEach((id) => deleteUser(id)) + }); +}) + +// AMOUNT + BALANCE EXCEEDS MAX SAFE INTEGER + +describe('GIVEN invalid data for a new transaction with amount + receiver-balance > MAX_SAFE_INTEGER ', () => { + const maxSafeInteger = Number.MAX_SAFE_INTEGER + const delta = 100 + const amount = maxSafeInteger - delta + let sender + let receiver + + describe('WHEN I create a new transaction using POST', () => { + const expectedStatus = 422 + + let createTransactionApiResponse + + beforeAll(async () => { + sender = await createUser('Sasha', 100) + receiver = await createUser('Pan', delta + 1) + + createTransactionApiResponse = await createTransaction(amount, sender.id, receiver.id, expectedStatus) + senderAfterTrx = await getUser(sender.id) + receiverAfterTrx = await getUser(receiver.id) + }) + + + it('THEN an error message is returned in the response', () => { + expect(createTransactionApiResponse.data.message).toBe('Amount field must not exceed: ' + Number.MAX_SAFE_INTEGER) + }) + }) + + + afterAll(async () => { + [sender.id, receiver.id].forEach((id) => deleteUser(id)) + }); +}) + + +// NON-INTEGER AMOUNT + +describe('GIVEN invalid data for a new transaction with non-integer amount', () => { + const amount = 10.10 + let sender + let receiver + + describe('WHEN I create a new transaction using POST', () => { + const expectedStatus = 400 + + let createTransactionApiResponse + beforeAll(async () => { + sender = await createUser('Sasha', 100) + receiver = await createUser('Pan', 100) + + createTransactionApiResponse = await createTransaction(amount, sender.id, receiver.id, expectedStatus) + }) + + + it('THEN an error message is returned in the response', () => { + expect(createTransactionApiResponse.data.message).toBe('Amount field must be an integer number') + }) + }) + + + afterAll(async () => { + [sender.id, receiver.id].forEach((id) => deleteUser(id)) + }); +}) + +// ABSENT AMOUNT + +describe('GIVEN invalid data for a new transaction with absent amount', () => { + const amount = undefined + let sender + let receiver + + describe('WHEN I create a new transaction using POST', () => { + const expectedStatus = 400 + + let createTransactionApiResponse + beforeAll(async () => { + sender = await createUser('Sasha', 100) + receiver = await createUser('Pan', 100) + + createTransactionApiResponse = await createTransaction(amount, sender.id, receiver.id, expectedStatus) + }) + + + it('THEN an error message is returned in the response', () => { + expect(createTransactionApiResponse.data.message).toBe('Amount field must be present in the body') + }) + }) + + + afterAll(async () => { + [sender.id, receiver.id].forEach((id) => deleteUser(id)) + }); +}) + +// ABSENT SENDER + +describe('GIVEN invalid data for a new transaction with absent sender', () => { + const amount = 10 + const senderId = undefined + let receiver + + describe('WHEN I create a new transaction using POST', () => { + const expectedStatus = 400 + + let createTransactionApiResponse + let receiverAfterTrx + beforeAll(async () => { + receiver = await createUser('Pan', 100) + + createTransactionApiResponse = await createTransaction(amount, senderId, receiver.id, expectedStatus) + receiverAfterTrx = await getUser(receiver.id) + }) + + + it('THEN an error message is returned in the response', () => { + expect(createTransactionApiResponse.data.message).toBe('Sender field must be present in the body') + }) + }) + + + afterAll(async () => { + deleteUser(receiver.id) + }); +}) + +// ABSENT RECEIVER + +describe('GIVEN invalid data for a new transaction with absent receiver', () => { + const amount = 10 + const receiverId = undefined + let sender + + describe('WHEN I create a new transaction using POST', () => { + const expectedStatus = 400 + + let createTransactionApiResponse + let senderAfterTrx + beforeAll(async () => { + sender = await createUser('Sasha', 100) + + createTransactionApiResponse = await createTransaction(amount, sender.id, receiverId, expectedStatus) + senderAfterTrx = await getUser(sender.id) + }) + + + it('THEN an error message is returned in the response', () => { + expect(createTransactionApiResponse.data.message).toBe('Receiver field must be present in the body') + expect(senderAfterTrx.balance).toBe(sender.balance) + + }) + }) + + + afterAll(async () => { + deleteUser(sender.id) + }); +}) + +// SENDER = RECEIVER + +describe('GIVEN invalid data for a new transaction with the sender equal to the receiver', () => { + const amount = 10 + let receiver + let sender + + describe('WHEN I create a new transaction using POST', () => { + const expectedStatus = 400 + + let createTransactionApiResponse + let senderAfterTrx + let receiverAfterTrx + beforeAll(async () => { + sender = await createUser('Sasha', 100) + receiver = sender + createTransactionApiResponse = await createTransaction(amount, sender.id, receiver.id, expectedStatus) + senderAfterTrx = await getUser(sender.id) + receiverAfterTrx = await getUser(receiver.id) + }) + + + it('THEN an error message is returned in the response', () => { + expect(createTransactionApiResponse.data.message).toBe('Receiver must be different from sender') + expect(senderAfterTrx.balance).toBe(sender.balance) + expect(receiverAfterTrx.balance).toBe(receiver.balance) + }) + }) + + + afterAll(async () => { + [sender.id, receiver.id].forEach((id) => deleteUser(id)) + }); +}) + +// SENDER DOEN'T EXIST + +describe('GIVEN invalid data for a new transaction with a sender that does not exist ', () => { + const amount = 10 + let receiver + let sender + + describe('WHEN I create a new transaction using POST', () => { + const expectedStatus = 500 + + let createTransactionApiResponse + + let receiverAfterTrx + beforeAll(async () => { + sender = await createUser('Sasha', 100) + receiver = await createUser('Pan', 100) + await deleteUser(sender.id) + createTransactionApiResponse = await createTransaction(amount, sender.id, receiver.id, expectedStatus) + + receiverAfterTrx = await getUser(receiver.id) + }) + + + it('THEN an error message is returned in the response', () => { + expect(receiverAfterTrx.balance).toBe(receiver.balance) + expect(createTransactionApiResponse.data.message).toBe("Error updating balance of sender User with id=" + sender.id) + + }) + }) + + + afterAll(async () => { + deleteUser(receiver) + }); +}) + +// RECEIVER DOENS'T EXIST + +describe('GIVEN invalid data for a new transaction with a receiver that does not exist ', () => { + const amount = 10 + let receiver + let sender + + describe('WHEN I create a new transaction using POST', () => { + const expectedStatus = 500 + + let createTransactionApiResponse + + let senderAfterTrx + beforeAll(async () => { + sender = await createUser('Sasha', 100) + receiver = await createUser('Pan', 100) + await deleteUser(receiver.id) + createTransactionApiResponse = await createTransaction(amount, sender.id, receiver.id, expectedStatus) + + senderAfterTrx = await getUser(sender.id) + }) + + + it('THEN an error message is returned in the response', () => { + expect(senderAfterTrx.balance).toBe(sender.balance) + expect(createTransactionApiResponse.data.message).toBe("Error updating balance of receiver User with id=" + receiver.id) + }) + }) + + + afterAll(async () => { + deleteUser(sender) + }); +}) \ No newline at end of file diff --git a/src/test/api/transactions/getTransactions.test.js b/src/test/api/transactions/getTransactions.test.js new file mode 100644 index 0000000..beef5a5 --- /dev/null +++ b/src/test/api/transactions/getTransactions.test.js @@ -0,0 +1,188 @@ +const { containsAll } = require('../../../utilities/common'); +const { createTransaction, getTransactions, getTransactionsByUserId } = require('../../../utilities/transactions'); +const { createUser, deleteUser } = require('../../../utilities/users') + + +// SORT + +function isSorted(transactions) { + for (let i = 0; i < transactions.length - 1; i++) { + if (transactions[i].createdAt < transactions[i + 1].createdAt) { + return false + } + } + + return true +} + +// LIST ALL TRANSACTIONS + +describe('GIVEN there are transactions in persistence', () => { + let firstTransaction + let secondTransaction + let thirdTransaction + let sender + let receiver + let transactions + + describe('WHEN I request all transactions using GET', () => { + const amount = 10 + + beforeAll(async () => { + sender = await createUser('Sasha', 100) + receiver = await createUser('Pan', 100) + + firstTransaction = await createTransaction(amount, sender.id, receiver.id) + secondTransaction = await createTransaction(amount, sender.id, receiver.id) + thirdTransaction = await createTransaction(amount, sender.id, receiver.id) + + transactions = await getTransactions() + }); + + it('THEN at least 3 transactions are returned AND transactions are sorted in descending order by createdAt', () => { + expect(transactions.length >= 3).toBe(true) + const createdTransactions = [ firstTransaction, secondTransaction, thirdTransaction].map(transaction => transaction.data) + expect(containsAll(transactions, createdTransactions)).toBe(true) + }); + }); + + afterAll(async () => { + deleteUser(sender.id) + deleteUser(receiver.id) + }); +}); + +// LIST TRANSACTIONS BY USER ID + +describe('GIVEN there are transactions for a user in persistence', () => { + let firstTransaction + let secondTransaction + let thirdTransaction + let sender + let receiver + let user + let transactions + + describe("WHEN I request a user's transactions using GET", () => { + const amount = 10 + + beforeAll(async () => { + sender = await createUser('Sasha', 100) + receiver = await createUser('Pan', 100) + user = await createUser('User', 100) + + firstTransaction = await createTransaction(amount, user.id, receiver.id) + secondTransaction = await createTransaction(amount, sender.id, user.id) + thirdTransaction = await createTransaction(amount, user.id, receiver.id) + await createTransaction(amount, sender.id, receiver.id) + + transactions = await getTransactionsByUserId(user.id) + }); + + it('THEN exactly 3 transactions are returned AND transactions are sorted in descending order by createdAt', () => { + + const stranger = transactions.some( + trx => trx.sender !== user.id && trx.receiver !== user.id + ) + + expect(stranger).toBe(false) + expect(isSorted(transactions)).toBe(true) + expect(transactions.length).toBe(3) + const createdTransactions = [ firstTransaction, secondTransaction, thirdTransaction].map(transaction => transaction.data) + expect(containsAll(transactions, createdTransactions)).toBe(true) + }) + + }); + + afterAll(async () => { + deleteUser(sender.id) + deleteUser(receiver.id) + deleteUser(user.id) + }); +}); + + +// NO TRANSACTIONS + +describe('GIVEN there are no transactions for a user in persistence', () => { + let sender + let receiver + let user + let transactions + + describe("WHEN I request a user's transactions using GET", () => { + const amount = 10 + + beforeAll(async () => { + sender = await createUser('Sasha', 100) + receiver = await createUser('Pan', 100) + user = await createUser('User', 100) + + + await createTransaction(amount, sender.id, receiver.id) + + transactions = await getTransactionsByUserId(user.id) + }); + + it('THEN no transactions are returned', () => { + expect(transactions.length).toBe(0) + }) + + }); + + afterAll(async () => { + deleteUser(sender.id) + deleteUser(receiver.id) + deleteUser(user.id) + }); +}); + +// NON-EXISTING USER + +describe('GIVEN there are transactions for a user in persistence', () => { + let firstTransaction + let secondTransaction + let thirdTransaction + let sender + let receiver + let user + let transactions + + describe("WHEN I delete the user AND request the user's transactions using GET", () => { + const amount = 10 + + beforeAll(async () => { + sender = await createUser('Sasha', 100) + receiver = await createUser('Pan', 100) + user = await createUser('User', 100) + + firstTransaction = await createTransaction(amount, user.id, receiver.id) + secondTransaction = await createTransaction(amount, sender.id, user.id) + thirdTransaction = await createTransaction(amount, user.id, receiver.id) + await createTransaction(amount, sender.id, receiver.id) + + await deleteUser(user.id) + + transactions = await getTransactionsByUserId(user.id) + }); + + it('THEN exactly 3 transactions are returned AND transactions are sorted in descending order by createdAt', () => { + + const stranger = transactions.some( + trx => trx.sender !== user.id && trx.receiver !== user.id + ) + + expect(stranger).toBe(false) + expect(isSorted(transactions)).toBe(true) + expect(transactions.length).toBe(3) + const createdTransactions = [ firstTransaction, secondTransaction, thirdTransaction ].map(transaction => transaction.data) + expect(containsAll(transactions, createdTransactions)).toBe(true) + }) + + }); + + afterAll(async () => { + deleteUser(sender.id) + deleteUser(receiver.id) + }); +}); \ No newline at end of file diff --git a/src/test/api/users/createUser.test.js b/src/test/api/users/createUser.test.js new file mode 100644 index 0000000..fe8f1f6 --- /dev/null +++ b/src/test/api/users/createUser.test.js @@ -0,0 +1,138 @@ +const { createUser, deleteUser } = require('../../../utilities/users'); + +// CREATE VALID USER + +describe('GIVEN valid data for a user', () => { + const name = 'Alex'; + const balance = 1000; + const expectedStatus = 201; + + let createdUser; + + describe('WHEN I create a new user using POST', () => { + beforeAll(async () => { + createdUser = await createUser(name, balance, expectedStatus); + }); + + it('THEN the user created is returned in the response', () => { + expect(createdUser.name).toBe(name); + expect(createdUser.balance).toBe(balance); + }); + }); + + afterAll(async () => { + deleteUser(createdUser.id) + }); +}); + + +// EMPTY NAME + +describe('GIVEN an empty name for a user', () => { + const name = ''; + const balance = 1000; + const expectedStatus = 400; + + let createdUser; + + describe('WHEN I create a new user using POST', () => { + + beforeAll(async () => { + createdUser = await createUser(name, balance, expectedStatus); + }); + + it('THEN an error message is returned', () => { + expect(createdUser.message).toBe("Name field must be present in the body"); + }); + }); +}); + +// UNDEFINED BALANCE + +describe('GIVEN an undefined balance for a user', () => { + const name = 'Sasha'; + const balance = undefined; + const expectedStatus = 201; + const expectedBalance = 0; + + let createdUser; + + describe('WHEN I create a new user using POST', () => { + + beforeAll(async () => { + createdUser = await createUser(name, balance, expectedStatus); + }); + + it('THEN the user created is returned in the response', () => { + expect(createdUser.name).toBe(name); + expect(createdUser.balance).toBe(expectedBalance); + }); + }); + + afterAll(async () => { + deleteUser(createdUser.id) + }); +}); + +// NEGATIVE BALANCE + +describe('GIVEN a negative balance for a user', () => { + const name = 'Sasha'; + const balance = -100; + const expectedStatus = 422; + + let createdUser; + + describe('WHEN I create a new user using POST', () => { + + beforeAll(async () => { + + createdUser = await createUser(name, balance, expectedStatus); + + }); + + it('THEN an error message is returned', () => { + expect(createdUser.message).toBe("Some error occurred while creating the User."); + }); + }); + + afterAll(async () => { + deleteUser(createdUser.id) + }); +}); + +/* +* ============================================================================= +* Warning! The below test will break the app and will require a server restart! +* +// _._ _,-'""`-._ +// (,-.`._,'( |\`-/| +// `-.-' \ )-`( , o o) +// `- \`_`"'- +* ============================================================================= +*/ + +// // NON-NUMERIC BALANCE + +// describe('GIVEN a non-numeric balance for a user', () => { +// const name = 'Sasha'; +// const balance = 'string'; +// const expectedStatus = 422; + +// let createdUser; + +// describe('WHEN I create a new user using POST', () => { + +// beforeAll(async () => { +// createdUser = await createUser(name, balance, expectedStatus); +// }); + +// it('THEN an error message is returned', () => { +// expect(createdUser.message).toBe("Some error occurred while creating the User."); +// }); +// }); + +// afterAll(async () => { + +// }); +// }); \ No newline at end of file diff --git a/src/test/api/users/deleteUser.test.js b/src/test/api/users/deleteUser.test.js new file mode 100644 index 0000000..efd40df --- /dev/null +++ b/src/test/api/users/deleteUser.test.js @@ -0,0 +1,58 @@ +const { containsAll} = require("../../../utilities/common"); +const { getUsers, getUser, createUser, deleteUser } = require("../../../utilities/users"); + +// DELETE USER + +describe('GIVEN there is a user in persistence', () => { + let user + let users + let fetchedUser + let deleteResponse + + const balance = 100 + const expectedStatus = 404 + + describe('WHEN I delete the user by id using DELETE', () => { + + beforeAll(async () => { + user = await createUser('Sasha', balance) + + deleteResponse = await deleteUser(user.id) + + users = await getUsers() + fetchedUser = await getUser(user.id, expectedStatus) + + }); + + it('THEN the user is not in persistence anymore', () => { + expect(containsAll(users, [user])).toBe(false) + expect(fetchedUser.message).toBe(`Cannot find User with id=${user.id}.`) + expect(deleteResponse.message).toBe("User was deleted successfully.") + }); + }); +}); + +// DELETE NON-EXISTING USER + +describe('GIVEN there is no user in persistence', () => { + let user + let deleteResponse + + const balance = 100 + const expectedStatus = 404 + + describe('WHEN I delete the user by id using DELETE', () => { + + beforeAll(async () => { + user = await createUser('Sasha', balance) + + await deleteUser(user.id) + + deleteResponse = await deleteUser(user.id, expectedStatus) + }); + + it('THEN an error message is returned', () => { + expect(deleteResponse.message).toBe(`Cannot find User with id=${user.id}.`) + }); + }); +}); \ No newline at end of file diff --git a/src/test/api/users/getUsers.test.js b/src/test/api/users/getUsers.test.js new file mode 100644 index 0000000..9265a81 --- /dev/null +++ b/src/test/api/users/getUsers.test.js @@ -0,0 +1,87 @@ +const { containsAll} = require("../../../utilities/common"); +const { getUsers, getUser, createUser, deleteUser } = require("../../../utilities/users"); + +// LIST ALL USERS + +describe('GIVEN there are users in persistence', () => { + let firstUser + let secondUser + let thirdUser + let users + const balance = 100 + + describe('WHEN I request all users using GET', () => { + + beforeAll(async () => { + firstUser = await createUser('Sasha', balance) + secondUser = await createUser('Pan', balance) + thirdUser = await createUser('Lyra', balance) + + users = await getUsers() + + }); + + it('THEN at least 3 users are returned', () => { + expect(users.length >= 3).toBe(true) + const createdUsers= [ firstUser, secondUser, thirdUser ] + expect(containsAll(users, createdUsers)).toBe(true) + + }); + }); + + afterAll(async () => { + [ firstUser, secondUser, thirdUser ].forEach((id) => deleteUser(id)) + }); +}); + +// FIND A USER BY ID + +describe('GIVEN a user exists in persistence', () => { + let user + const balance = 100 + + describe('WHEN I request the user by id using GET', () => { + + beforeAll(async () => { + user = await createUser('Sasha', balance) + + fetchedUser = await getUser(user.id) + + }); + + it('THEN the user is returned', () => { + expect(user.id).toBe(fetchedUser.id) + expect(user.name).toBe(fetchedUser.name) + expect(user.createdAt).toBe(fetchedUser.createdAt) + expect(user.updatedAt).toBe(fetchedUser.updatedAt) + }); + }); + + afterAll(async () => { + deleteUser(user.id) + }); +}); + +// NON-EXISTING USER + +describe('GIVEN a user does not exist in persistence', () => { + let user + let fetchedUser + + const balance = 100 + const expectedStatus = 404 + + describe('WHEN I request the user by id using GET', () => { + + beforeAll(async () => { + user = await createUser('Sasha', balance) + await deleteUser(user.id) + fetchedUser = await getUser(user.id, expectedStatus) + + }); + + it('THEN an error message is returned', () => { + expect(fetchedUser.message).toBe(`Cannot find User with id=${user.id}.`) + }); + }); +}); diff --git a/src/test/api/users/topUpBalance.test.js b/src/test/api/users/topUpBalance.test.js new file mode 100644 index 0000000..caa2639 --- /dev/null +++ b/src/test/api/users/topUpBalance.test.js @@ -0,0 +1,156 @@ +const { getUser, createUser, topUpBalance, deleteUser } = require("../../../utilities/users"); + + +// TOPUP USER BALANCE BY ID + +describe('GIVEN a user exists in persistence AND a valid topup value is supplied', () => { + let user + let updateResponse + let updatedUser + + const expectedStatus = 201 + const balance = 0 + const topup = 1 + + describe("WHEN I request to update the users balance by id using PUT", () => { + + beforeAll(async () => { + user = await createUser('Sasha', balance) + updateResponse = await topUpBalance(user.id, topup, expectedStatus) + updatedUser = await getUser(user.id) + }); + + it("THEN the user's balance is updated", () => { + expect(updatedUser.balance - user.balance).toBe(topup) + expect(updateResponse.message).toBe("User was updated successfully.") + }); + }); + + afterAll(async () => { + deleteUser(user.id) + }); +}); + +// NON-EXISTING USER + +describe('GIVEN a user does not exist in persistence AND a valid topup value is supplied', () => { + let user + let updateResponse + const expectedStatus = 500 + const balance = 0 + const topup = 1 + + describe("WHEN I request to update the users balance by id using PUT", () => { + + beforeAll(async () => { + user = await createUser('Sasha', balance) + await deleteUser(user.id) + updateResponse = await topUpBalance(user.id, topup, expectedStatus) + }); + + it("THEN an error message is returned", () => { + expect(updateResponse.message).toBe("Error retrieving User with id=" + user.id) + }); + }); +}); + +// EMPTY TOPUP + +describe('GIVEN a user exists in persistence AND an falsy topup value is supplied', () => { + let user + let updateResponse + let updatedUser + + const expectedStatus = 400 + const balance = 0 + const topup = undefined + + describe("WHEN I request to update the users balance by id using PUT", () => { + + beforeAll(async () => { + user = await createUser('Sasha', balance) + updateResponse = await topUpBalance(user.id, topup, expectedStatus) + updatedUser = await getUser(user.id) + }); + + it("THEN an error message is returned", () => { + expect(updatedUser.balance - user.balance).toBe(0) + expect(updateResponse.message).toBe("Content can not be empty!") + }); + }); + + afterAll(async () => { + deleteUser(user.id) + }); +}); + +/* +* ============================================================================= +* Warning! The below test will break the app and will require a server restart! +* +// _._ _,-'""`-._ +// (,-.`._,'( |\`-/| +// `-.-' \ )-`( , o o) +// `- \`_`"'- +* ============================================================================= +*/ + +// NON-NUMERIC TOPUP + +// describe('GIVEN a user exists in persistence AND a non-numeric topup value is supplied', () => { +// let user +// let updateResponse +// let updatedUser + +// const expectedStatus = 422 +// const balance = 100 +// const topup = 'string' + +// describe("WHEN I request to update the users balance by id using PUT", () => { + +// beforeAll(async () => { +// user = await createUser('Sasha', balance) +// updateResponse = await topUpBalance(user.id, topup, expectedStatus) +// updatedUser = await getUser(user.id) +// }); + +// it("THEN an error message is returned", () => { +// expect(updatedUser.balance - user.balance).toBe(0) +// expect(updateResponse.message).toBe("Value must be a number!") +// }); +// }); + +// afterAll(async () => { +// deleteUser(user.id) +// }); +// }); + +// NEGATIVE TOPUP + +describe('GIVEN a user exists in persistence AND a negative topup value is supplied', () => { + let user + let updateResponse + let updatedUser + + const expectedStatus = 422 + const balance = 100 + const topup = -10 + + describe("WHEN I request to update the users balance by id using PUT", () => { + + beforeAll(async () => { + user = await createUser('Sasha', balance) + updateResponse = await topUpBalance(user.id, topup, expectedStatus) + updatedUser = await getUser(user.id) + }); + + it("THEN an error message is returned", () => { + expect(updatedUser.balance - user.balance).toBe(0) + expect(updateResponse.message).toBe("Value must be a positive number!") + }); + }); + + afterAll(async () => { + deleteUser(user.id) + }); +}); diff --git a/src/test/ui/createTransaction.test.js b/src/test/ui/createTransaction.test.js new file mode 100644 index 0000000..4aa717e --- /dev/null +++ b/src/test/ui/createTransaction.test.js @@ -0,0 +1,107 @@ +const { CreateTransaction } = require("../../page-objects/createTransactionObject"); + +// VALID DATA + +describe('GIVEN valid data for a transaction', () => { + const amount = '1000'; + + describe('WHEN I create a transaction', () => { + let createTransactionPage + beforeAll(async () => { + createTransactionPage = new CreateTransaction(page) + await createTransactionPage.visit() + await createTransactionPage.createTransaction(amount) + }); + + it('THEN I can see the success message', async () => await createTransactionPage.assertSuccess()); + }); +}); + +// INVALID DATA: amount = 0 + +describe('GIVEN invalid data for a transaction', () => { + const amount = ''; + + describe('WHEN I try to create a transaction', () => { + let createTransactionPage + beforeAll(async () => { + createTransactionPage = new CreateTransaction(page) + await createTransactionPage.visit() + await createTransactionPage.createTransaction(amount) + }); + + it('THEN I can see the error message', async () => await createTransactionPage.assertError()); + }); +}); + +// INVALID DATA: negative amount + +describe('GIVEN negative amount for a transaction', () => { + const amount = '-100'; + + describe('WHEN I try to create a transaction', () => { + let createTransactionPage + beforeAll(async () => { + createTransactionPage = new CreateTransaction(page) + await createTransactionPage.visit() + await createTransactionPage.createTransaction(amount) + }); + + it('THEN I can see the error message', async () => await createTransactionPage.assertError()); + }); +}); + +// INVALID DATA: amount = string + +describe('GIVEN non numeric amount', () => { + const amount = 'string'; + + describe("WHEN I try to insert the amount, it doens't write in the input AND amount is considered equal to zero", () => { + let createTransactionPage + beforeAll(async () => { + createTransactionPage = new CreateTransaction(page) + await createTransactionPage.visit() + await createTransactionPage.createTransaction(amount) + }); + + it('THEN I can see the error message', async () => { + await createTransactionPage.assertError()} + ); + }); +}); + +// BACK TO USERS + +describe('GIVEN I navigate to /transfer/{senderId}', () => { + + describe("WHEN I click on the 'go back' button", () => { + let createTransactionPage + beforeAll(async () => { + createTransactionPage = new CreateTransaction(page) + await createTransactionPage.visit() + await createTransactionPage.navigateBackToUsers() + }); + + it('THEN I navigate back to /users', async () => { + await page.waitForNavigation({ waitUntil: 'networkidle0'}) + await expect(page.url()).toMatch('/users') + }) + }); +}); + +// EMPTY DATA + +describe('GIVEN empty data for a transaction', () => { + + describe('WHEN I try to create a transaction', () => { + let createTransactionPage + beforeAll(async () => { + createTransactionPage = new CreateTransaction(page) + await createTransactionPage.visit() + await createTransactionPage.submitEmptyTransaction() + }); + + it('THEN I can see the error message', async () => await createTransactionPage.assertError()); + }); +}); + diff --git a/src/test/ui/createUser.test.js b/src/test/ui/createUser.test.js index 75a4ad8..c5f2840 100644 --- a/src/test/ui/createUser.test.js +++ b/src/test/ui/createUser.test.js @@ -1,34 +1,98 @@ +const { CreateUser } = require("../../page-objects/createUserObject"); + +// VALID DATA describe('GIVEN valid data for a user', () => { - const name = 'Alex'; + const name = 'Sasha'; const balance = '1000'; - describe('GIVEN that I can create a user', () => { - const createUserUrl = 'http://localhost:8081/create'; + describe('WHEN I create a user', () => { + let createUserPage + beforeAll(async () => { + createUserPage = new CreateUser(page) + await createUserPage.visit() + await createUserPage.createUser(name, balance) + }); + + it('THEN I can see the success message', async () => await createUserPage.assertSuccess()); + }); +}); + +// ADD NEW USER AFTER SUCCESS + +describe('GIVEN valid data for a user', () => { + const name = 'Sasha'; + const balance = '1000'; + describe('WHEN I create a user', () => { + let createUserPage beforeAll(async () => { - await page.goto(createUserUrl); - - // enter name - await page.waitForSelector('#name', { visible: true }); - const fieldName = await page.$('#name'); - await fieldName.click({ clickCount: 3 }) - await fieldName.type(name); - - // enter balance - await page.waitForSelector('#balance', { visible: true }); - const fieldBalance = await page.$('#balance'); - await fieldBalance.click({ clickCount: 3 }) - await fieldBalance.type(balance); - - // click submit - await page.waitForSelector('#createUser', { visible: true }); - await page.click('#createUser'); + createUserPage = new CreateUser(page) + await createUserPage.visit() + await createUserPage.createUser(name, balance) + await createUserPage.assertSuccess() + + await createUserPage.addUser() + + await createUserPage.createUser(name, balance) }); - it('THEN I can see the success message', async () => { - const isSuccessMessageVisible = await page.waitForSelector('#successMessage', { visible: true }); - expect(isSuccessMessageVisible).toBeTruthy(); + it('THEN I can add a new user', async () => await createUserPage.assertSuccess()); + }); +}); + +// ABSENT USER + +describe('GIVEN empty user', () => { + const name = ''; + const balance = '1000'; + + describe('WHEN I create a user', () => { + let createUserPage + beforeAll(async () => { + createUserPage = new CreateUser(page) + await createUserPage.visit() + await createUserPage.createUser(name, balance) }); + + it('THEN I cannot see the error message', async () => await createUserPage.assertError()); + }); +}); + +// EMPTY BALANCE + +describe('GIVEN an empty balance', () => { + const name = 'Sasha'; + const balance = ''; + + describe('WHEN I create a new user', () => { + let createUserPage + beforeAll(async () => { + createUserPage = new CreateUser(page) + await createUserPage.visit() + await createUserPage.createUser(name, balance) + }); + + it('THEN I can see the success message', async () => await createUserPage.assertSuccess()); + }); +}); + +// INVALID DATA: amount = string + +describe('GIVEN non numeric amount', () => { + const name = 'Sasha' + const balance = 'string'; + + describe("WHEN I try to insert the balance, it doens't write in the input AND balance is considered equal to zero", () => { + let createUserPage + beforeAll(async () => { + createUserPage = new CreateUser(page) + await createUserPage.visit() + await createUserPage.createUser(name, balance) + }); + + it('THEN I can see the success message', async () => { + await createUserPage.assertError()} + ); }); }); \ No newline at end of file diff --git a/src/test/ui/homePage.test.js b/src/test/ui/homePage.test.js new file mode 100644 index 0000000..a7e07e0 --- /dev/null +++ b/src/test/ui/homePage.test.js @@ -0,0 +1,83 @@ +const { HomePage } = require("../../page-objects/usersObject"); +const { getTransactionsByUserId } = require("../../utilities/transactions"); +const { getUser } = require("../../utilities/users") + +// INITIALIZATION + +describe('GIVEN the default route', () => { + + describe('WHEN I open the application', () => { + let homePage + + beforeAll(async () => { + homePage = new HomePage(page) + await homePage.visit() + + }); + + it('THEN I expect to see a list of users AND a message', async () => await homePage.assertInitialPageLoadSuccess()); + }); +}); + + +// DISPLAY USER DETAIL + +describe('GIVEN the list of users is visible', () => { + + describe('WHEN I click on a user', () => { + let homePage + let fetchedUser + beforeAll(async () => { + homePage = new HomePage(page) + await homePage.visit() + await homePage.openUserDetail() + fetchedUser = await getUser(1) + fetchedTransactions = await getTransactionsByUserId(1) + }); + + it("THEN I expect to see the user's detail", async () => { + await homePage.assertUserDetailsLoadSuccess() + await homePage.assertUserDetailsCorrectness(fetchedUser.name, fetchedUser.balance, fetchedTransactions.length) + }); + }); + }); + + // NAVIGATE TO USERS/{ID} + +describe('GIVEN I want to top up the user balance', () => { + + describe("WHEN I click on the 'add Balance' button", () => { + let homePage + beforeAll(async () => { + homePage = new HomePage(page) + await homePage.visit() + await homePage.openUserDetail() + await homePage.navigateToUserId() + }); + + it('THEN I navigate to users/id', async () => { + await page.waitForNavigation({ waitUntil: 'networkidle0'}) + await expect(page.url()).toMatch('/users/1') + }) + }); +}); + +// NAVIGATE TO TRANSFER/{ID} + +describe('GIVEN I want to make a transaction', () => { + + describe("WHEN I click on the 'make transaction' button", () => { + let homePage + beforeAll(async () => { + homePage = new HomePage(page) + await homePage.visit() + await homePage.openUserDetail() + await homePage.navigateToTransfer() + }); + + it('THEN I navigate back to /users', async () => { + await page.waitForNavigation({ waitUntil: 'networkidle0'}) + await expect(page.url()).toMatch('/transfer/1') + }) + }); +}); diff --git a/src/test/ui/topUpBalance.test.js b/src/test/ui/topUpBalance.test.js new file mode 100644 index 0000000..c5b1a55 --- /dev/null +++ b/src/test/ui/topUpBalance.test.js @@ -0,0 +1,75 @@ +const { TopUpBalance } = require("../../page-objects/topUpBalanceObject"); + +// VALID DATA + +describe('GIVEN valid data for a topup', () => { + const topup= '1000'; + + describe('WHEN I fill in amount', () => { + let topupBalancePage + beforeAll(async () => { + topupBalancePage = new TopUpBalance(page) + await topupBalancePage.visit() + await topupBalancePage.updateBalance(topup) + }); + + it('THEN I can see the success message', async () => await topupBalancePage.assertSuccess()); + }); +}); + + +// INVALID DATA + +describe('GIVEN an empty topup', () => { + const topup= ''; + + describe('WHEN I fill in amount', () => { + let topupBalancePage + beforeAll(async () => { + topupBalancePage = new TopUpBalance(page) + await topupBalancePage.visit() + await topupBalancePage.updateBalance(topup) + }); + + it('THEN I can see the error message', async () => await topupBalancePage.assertError()); + }); +}); + +// INVALID DATA: top up = string + +describe('GIVEN non numeric amount', () => { + const topup = 'string'; + + describe("WHEN I try to insert the amount, it doens't write in the input AND topup is considered equal to zero", () => { + let topupBalancePage + beforeAll(async () => { + topupBalancePage = new TopUpBalance(page) + await topupBalancePage.visit() + await topupBalancePage.updateBalance(topup) + }); + + it('THEN I can see the error message', async () => { + await topupBalancePage.assertError()} + ); + }); +}); + +// BACK TO USERS + +describe('GIVEN I navigate to /users/{id}', () => { + const amount = ''; + + describe("WHEN I click on the 'go back' button", () => { + let topupBalancePage + beforeAll(async () => { + topupBalancePage = new TopUpBalance(page) + await topupBalancePage.visit() + await topupBalancePage.navigateBackToUsers() + }); + + it('THEN I navigate back to /users', async () => { + await page.waitForNavigation({ waitUntil: 'networkidle0'}) + await expect(page.url()).toMatch('/users') + }) + }); +}); diff --git a/src/utilities/common.js b/src/utilities/common.js index 6a5efaf..fb440f0 100644 --- a/src/utilities/common.js +++ b/src/utilities/common.js @@ -5,21 +5,12 @@ exports.makeAxiosRequestWithExpectedStatus = async (requestParams, expectedStatu ...requestParams, headers: exports.removeUndefinedValuesFromObject(requestParams.headers), }; - try { - const response = await axios({ ...defaultAxiosRequestParams, ...cleanedRequestParams }); - if (response.status !== expectedStatus) { - // eslint-disable-next-line no-console - console.error(`Expected status: ${expectedStatus} but received status ${response.status}`); - throw response; - } - return response; - } catch (error) { - const attemptedRequestDetailsMessage = `Failed to make request with: ${JSON.stringify(cleanedRequestParams)}`; - const receivedErrorMessage = `Received error: ${JSON.stringify(error.data || error)}`; - // eslint-disable-next-line no-console - console.error(`${attemptedRequestDetailsMessage}\n${receivedErrorMessage}`); - expect(error.data || error).toBe(null); - } + + const response = await axios({ ...defaultAxiosRequestParams, ...cleanedRequestParams }); + + expect(response.status).toBe(expectedStatus) + + return response; }; const defaultAxiosRequestParams = { @@ -34,3 +25,16 @@ exports.removeUndefinedValuesFromObject = (object) => { } return Object.fromEntries(Object.entries(object).filter((entry) => entry[1] !== undefined)); }; + +exports.containsObject = (array, obj) => { + return array.some(element => element.id == obj.id) +} +exports.containsAll = (array, target) => { + for (let element of target) { + if (!this.containsObject(array, element)) { + return false + } + } + + return true +} \ No newline at end of file diff --git a/src/utilities/transactions.js b/src/utilities/transactions.js new file mode 100644 index 0000000..fb4c3d4 --- /dev/null +++ b/src/utilities/transactions.js @@ -0,0 +1,36 @@ +const { makeAxiosRequestWithExpectedStatus } = require('./common') + +exports.createTransaction = async (amount, senderId, receiverId, expectedStatus = 201) => { + const requestParams = { + url: 'http://localhost:8080/api/transactions', + method: 'POST', + data: { + amount, + sender: senderId, + receiver: receiverId + } + } + + createTransactionApiResponse = await makeAxiosRequestWithExpectedStatus(requestParams, expectedStatus) + return createTransactionApiResponse +} + +exports.getTransactions = async (expectedStatus = 200) => { + const requestParams = { + url: `http://localhost:8080/api/transactions`, + method: 'GET' + }; + + const getTrasactionsApiResponse = await makeAxiosRequestWithExpectedStatus(requestParams, expectedStatus); + return getTrasactionsApiResponse.data +} + +exports.getTransactionsByUserId = async (userId, expectedStatus = 200) => { + const requestParams = { + url: `http://localhost:8080/api/transactions/${userId}`, + method: 'GET' + }; + + const getTrasactionsApiResponse = await makeAxiosRequestWithExpectedStatus(requestParams, expectedStatus); + return getTrasactionsApiResponse.data +} \ No newline at end of file diff --git a/src/utilities/users.js b/src/utilities/users.js new file mode 100644 index 0000000..c665ea4 --- /dev/null +++ b/src/utilities/users.js @@ -0,0 +1,57 @@ +const { makeAxiosRequestWithExpectedStatus } = require('./common') + +exports.createUser = async (name, balance, expectedStatus = 201) => { + const requestParams = { + url: 'http://localhost:8080/api/users', + method: 'POST', + data: { + name, + balance, + } + }; + + const createUserApiResponse = await makeAxiosRequestWithExpectedStatus(requestParams, expectedStatus); + return createUserApiResponse.data +} + +exports.getUsers = async ( expectedStatus = 200) => { + const requestParams = { + url: `http://localhost:8080/api/users/`, + method: 'GET' + }; + + const getUserApiResponse = await makeAxiosRequestWithExpectedStatus(requestParams, expectedStatus); + return getUserApiResponse.data +} + +exports.getUser = async (id, expectedStatus = 200) => { + const requestParams = { + url: `http://localhost:8080/api/users/${id}`, + method: 'GET' + }; + + const getUserApiResponse = await makeAxiosRequestWithExpectedStatus(requestParams, expectedStatus); + return getUserApiResponse.data +} + +exports.topUpBalance = async (userId, topup, expectedStatus = 201) => { + const requestParams = { + url: `http://localhost:8080/api/users/${userId}`, + method: 'PUT', + data: { + topup + } + }; + + const updateUserApiResponse = await makeAxiosRequestWithExpectedStatus(requestParams, expectedStatus); + return updateUserApiResponse.data +} + +exports.deleteUser = async (userId, expectedStatus = 200) => { + const requestParams = { + url: `http://localhost:8080/api/users/${userId}`, + method: 'DELETE', + }; + const getUserApiResponse = await makeAxiosRequestWithExpectedStatus(requestParams, expectedStatus); + return getUserApiResponse.data +}