From f7826b386499fa4c44b7e65f99a3844e60c76015 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Mon, 5 Aug 2024 17:44:02 +0100 Subject: [PATCH 01/28] feat: add metrics for fx quotes --- .ncurc.yaml | 2 +- package-lock.json | 3583 +++-------------------------------------- package.json | 10 +- src/lib/util.js | 2 +- src/model/fxQuotes.js | 106 +- src/model/quotes.js | 81 +- 6 files changed, 388 insertions(+), 3396 deletions(-) diff --git a/.ncurc.yaml b/.ncurc.yaml index cc4674fd..891f9b69 100644 --- a/.ncurc.yaml +++ b/.ncurc.yaml @@ -3,5 +3,5 @@ reject: [ "json-rules-engine", "eslint", "@mojaloop/sdk-standard-components", # Version 17.4.0 introduced the bug: this.logger.isDebugEnabled is not a function - "@mojaloop/central-services-shared" # This should be removed as soon as all vlaidations on the fx feature has been completed and cs-shared fx feature merged to masin + "@mojaloop/central-services-shared" # This should be removed as soon as all validations on the fx feature has been completed and cs-shared fx feature merged to main ] diff --git a/package-lock.json b/package-lock.json index 44179ed0..ce75a0ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,17 +15,17 @@ "@hapi/vision": "7.0.3", "@mojaloop/central-services-error-handling": "13.0.1", "@mojaloop/central-services-health": "15.0.0", - "@mojaloop/central-services-logger": "11.4.5", + "@mojaloop/central-services-logger": "11.5.0", "@mojaloop/central-services-metrics": "12.0.8", "@mojaloop/central-services-shared": "18.5.0-snapshot.2", "@mojaloop/central-services-stream": "11.3.1", "@mojaloop/event-sdk": "14.1.1", - "@mojaloop/inter-scheme-proxy-cache-lib": "2.0.0-snapshot.1", + "@mojaloop/inter-scheme-proxy-cache-lib": "2.2.0", "@mojaloop/ml-number": "11.2.4", "@mojaloop/sdk-standard-components": "18.1.0", "ajv": "8.17.1", "ajv-keywords": "5.1.0", - "axios": "1.7.2", + "axios": "1.7.3", "blipp": "4.0.2", "commander": "12.1.0", "event-stream": "4.0.1", @@ -46,10 +46,10 @@ "audit-ci": "^7.1.0", "eslint": "8.16.0", "eslint-config-standard": "17.1.0", - "eslint-plugin-jest": "28.6.0", + "eslint-plugin-jest": "28.7.0", "jest": "29.7.0", "jest-junit": "16.0.0", - "npm-check-updates": "16.14.20", + "npm-check-updates": "17.0.3", "nyc": "17.0.0", "pre-commit": "1.2.2", "proxyquire": "2.1.3", @@ -690,16 +690,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/@dabh/diagnostics": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", @@ -793,12 +783,6 @@ "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==" }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true - }, "node_modules/@grpc/grpc-js": { "version": "1.10.9", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.9.tgz", @@ -1407,73 +1391,6 @@ "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1956,9 +1873,9 @@ } }, "node_modules/@mojaloop/central-services-logger": { - "version": "11.4.5", - "resolved": "https://registry.npmjs.org/@mojaloop/central-services-logger/-/central-services-logger-11.4.5.tgz", - "integrity": "sha512-nCKEIinB/Zx3routZhcGd+//IKd9oThpGggTde4rNLJ6O4nVJgHSW6pZIzd1T+Mj34yBhOPidhBa0piLBKcZtQ==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/@mojaloop/central-services-logger/-/central-services-logger-11.5.0.tgz", + "integrity": "sha512-pH73RiJ5fKTBTSdLocp1vPBad1D+Kh0HufdcfjLaBQj3dIBq72si0k+Z3L1MeOmMqMzpj+8M/he/izlgqJjVJA==", "dependencies": { "parse-strings-in-object": "2.0.0", "rc": "1.2.8", @@ -2085,6 +2002,16 @@ "@hapi/hoek": "9.x.x" } }, + "node_modules/@mojaloop/central-services-shared/node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/@mojaloop/central-services-stream": { "version": "11.3.1", "resolved": "https://registry.npmjs.org/@mojaloop/central-services-stream/-/central-services-stream-11.3.1.tgz", @@ -2147,11 +2074,11 @@ "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "node_modules/@mojaloop/inter-scheme-proxy-cache-lib": { - "version": "2.0.0-snapshot.1", - "resolved": "https://registry.npmjs.org/@mojaloop/inter-scheme-proxy-cache-lib/-/inter-scheme-proxy-cache-lib-2.0.0-snapshot.1.tgz", - "integrity": "sha512-3cIs1U0Ufk16YFw9eR4QrrkO+hSHLUkoTAoxN5wM09/HIfE9+upZCnn0R+roB52FWCOe4TgURFMMIKo0E6HPuw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mojaloop/inter-scheme-proxy-cache-lib/-/inter-scheme-proxy-cache-lib-2.2.0.tgz", + "integrity": "sha512-QrbJlhy7f7Tf1DTjspxqtw0oN3eUAm5zKfCm7moQIYFEV3MYF3rsbODLpgxyzmAO8FFi2Dky/ff7QMVnlA/P9A==", "dependencies": { - "@mojaloop/central-services-logger": "11.4.5", + "@mojaloop/central-services-logger": "11.5.0", "ajv": "^8.17.1", "convict": "^6.2.4", "fast-safe-stringify": "^2.1.1", @@ -2216,200 +2143,6 @@ "node": ">= 8" } }, - "node_modules/@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", - "dev": true, - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/git": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", - "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==", - "dev": true, - "dependencies": { - "@npmcli/promise-spawn": "^6.0.0", - "lru-cache": "^7.4.4", - "npm-pick-manifest": "^8.0.0", - "proc-log": "^3.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/git/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/installed-package-contents": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", - "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", - "dev": true, - "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "bin": { - "installed-package-contents": "bin/index.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/move-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "dev": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/@npmcli/node-gyp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/promise-spawn": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", - "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", - "dev": true, - "dependencies": { - "which": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/run-script": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz", - "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==", - "dev": true, - "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/promise-spawn": "^6.0.0", - "node-gyp": "^9.0.0", - "read-package-json-fast": "^3.0.0", - "which": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pnpm/config.env-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", - "dev": true, - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", - "dev": true, - "dependencies": { - "graceful-fs": "4.2.10" - }, - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "node_modules/@pnpm/npm-conf": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz", - "integrity": "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==", - "dev": true, - "dependencies": { - "@pnpm/config.env-replace": "^1.1.0", - "@pnpm/network.ca-file": "^1.0.1", - "config-chain": "^1.1.11" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -2482,72 +2215,12 @@ "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" }, - "node_modules/@sigstore/bundle": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-1.1.0.tgz", - "integrity": "sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog==", - "dev": true, - "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@sigstore/protobuf-specs": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.2.1.tgz", - "integrity": "sha512-XTWVxnWJu+c1oCshMLwnKvz8ZQJJDVOlciMfgpJBQbThVjKTCG8dwyhgLngBD2KN0ap9F/gOV8rFDEx8uh7R2A==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@sigstore/sign": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-1.0.0.tgz", - "integrity": "sha512-INxFVNQteLtcfGmcoldzV6Je0sbbfh9I16DM4yJPw3j5+TFP8X6uIiA18mvpEa9yyeycAKgPmOA3X9hVdVTPUA==", - "dev": true, - "dependencies": { - "@sigstore/bundle": "^1.1.0", - "@sigstore/protobuf-specs": "^0.2.0", - "make-fetch-happen": "^11.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@sigstore/tuf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-1.0.3.tgz", - "integrity": "sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg==", - "dev": true, - "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0", - "tuf-js": "^1.1.7" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/@sinclair/typebox": { "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/@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -2592,73 +2265,6 @@ "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", "dev": true }, - "node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "dev": true, - "dependencies": { - "defer-to-connect": "^2.0.1" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tufjs/canonical-json": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz", - "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@tufjs/models": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz", - "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==", - "dev": true, - "dependencies": { - "@tufjs/canonical-json": "1.0.0", - "minimatch": "^9.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@tufjs/models/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2709,12 +2315,6 @@ "@types/node": "*" } }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "dev": true - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -2770,12 +2370,6 @@ "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true }, - "node_modules/@types/semver-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@types/semver-utils/-/semver-utils-1.1.3.tgz", - "integrity": "sha512-T+YwkslhsM+CeuhYUxyAjWm7mJ5am/K10UX40RuA6k6Lc7eGtq8iY2xOzy7Vq0GOqhl/xZl5l2FwURZMTPTUww==", - "dev": true - }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -2907,12 +2501,6 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -2952,30 +2540,6 @@ "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", "dev": true }, - "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==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", - "dev": true, - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -3055,15 +2619,6 @@ "node": ">=0.10.0" } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "dependencies": { - "string-width": "^4.1.0" - } - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -3137,46 +2692,12 @@ "node": ">=8" } }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "deprecated": "This package is no longer supported.", - "dev": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/are-we-there-yet/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3430,9 +2951,9 @@ } }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -3682,114 +3203,6 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, - "node_modules/boxen": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", - "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", - "dev": true, - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^7.0.1", - "chalk": "^5.2.0", - "cli-boxes": "^3.0.0", - "string-width": "^5.1.2", - "type-fest": "^2.13.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/boxen/node_modules/camelcase": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", - "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/boxen/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/boxen/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/boxen/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3897,139 +3310,6 @@ "node": ">= 0.8" } }, - "node_modules/cacache": { - "version": "17.1.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz", - "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==", - "dev": true, - "dependencies": { - "@npmcli/fs": "^3.1.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^7.7.1", - "minipass": "^7.0.3", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/cacache/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/cacache/node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/cacache/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "dev": true, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", - "dev": true, - "dependencies": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - } - }, "node_modules/caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -4271,15 +3551,6 @@ "node": ">= 6" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -4310,33 +3581,6 @@ "node": ">=6" } }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -4440,15 +3684,6 @@ "simple-swizzle": "^0.2.2" } }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "bin": { - "color-support": "bin.js" - } - }, "node_modules/color/node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -4585,103 +3820,23 @@ "typedarray": "^0.0.6" } }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dev": true, - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/config-chain/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/configstore": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", - "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", - "dev": true, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dependencies": { - "dot-prop": "^6.0.1", - "graceful-fs": "^4.2.6", - "unique-string": "^3.0.0", - "write-file-atomic": "^3.0.3", - "xdg-basedir": "^5.0.1" + "safe-buffer": "5.2.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/yeoman/configstore?sponsor=1" + "node": ">= 0.6" } }, - "node_modules/configstore/node_modules/dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/configstore/node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/configstore/node_modules/xdg-basedir": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" + "node": ">= 0.6" } }, "node_modules/conventional-changelog": { @@ -5100,33 +4255,6 @@ "node": ">= 8" } }, - "node_modules/crypto-random-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", - "dev": true, - "dependencies": { - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/crypto-random-string/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", @@ -5284,33 +4412,6 @@ "node": ">=0.10.0" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -5383,15 +4484,6 @@ "node": ">=0.8" } }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -5433,12 +4525,6 @@ "node": ">=0.4.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true - }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -5726,12 +4812,6 @@ "node": ">= 6" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, "node_modules/easy-table": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.2.0.tgz", @@ -5811,6 +4891,7 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "optional": true, + "peer": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -5834,15 +4915,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/env-var": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/env-var/-/env-var-7.5.0.tgz", @@ -5851,12 +4923,6 @@ "node": ">=10" } }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true - }, "node_modules/error-callsites": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/error-callsites/-/error-callsites-2.0.4.tgz", @@ -6050,18 +5116,6 @@ "node": ">=6" } }, - "node_modules/escape-goat": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", - "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -6372,18 +5426,18 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "28.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.6.0.tgz", - "integrity": "sha512-YG28E1/MIKwnz+e2H7VwYPzHUYU4aMa19w0yGcwXnnmJH6EfgHahTJ2un3IyraUxNfnz/KUhJAFXNNwWPo12tg==", + "version": "28.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.7.0.tgz", + "integrity": "sha512-fzPGN7awL2ftVRQh/bsCi+16ArUZWujZnD1b8EGJqy8nr4//7tZ3BIdc/9edcJBtB3hpci3GtdMNFVDwHU0Eag==", "dev": true, "dependencies": { - "@typescript-eslint/utils": "^6.0.0 || ^7.0.0" + "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "engines": { "node": "^16.10.0 || ^18.12.0 || >=20.0.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0", + "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0 || ^8.0.0", "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", "jest": "*" }, @@ -6767,12 +5821,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/exponential-backoff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", - "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", - "dev": true - }, "node_modules/express": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", @@ -6903,12 +5951,6 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, - "node_modules/fast-memoize": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", - "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", - "dev": true - }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", @@ -7224,15 +6266,6 @@ "node": ">= 6" } }, - "node_modules/form-data-encoder": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", - "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", - "dev": true, - "engines": { - "node": ">= 14.17" - } - }, "node_modules/format-util": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", @@ -7258,15 +6291,6 @@ "node": ">= 0.6" } }, - "node_modules/fp-and-or": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/fp-and-or/-/fp-and-or-0.1.4.tgz", - "integrity": "sha512-+yRYRhpnFPWXSly/6V4Lw9IfOV26uu30kynGJ03PW+MnjOEQe45RZ141QcS0aJehYBYA50GfCDnsRbFJdhssRw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -7300,27 +6324,6 @@ } ] }, - "node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "dev": true, - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/fs-readfile-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fs-readfile-promise/-/fs-readfile-promise-2.0.1.tgz", @@ -7420,26 +6423,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "deprecated": "This package is no longer supported.", - "dev": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -7759,30 +6742,6 @@ "node": ">=10.13.0" } }, - "node_modules/global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", - "dev": true, - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/global-dirs/node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -7895,31 +6854,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -8099,24 +7033,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true - }, - "node_modules/has-yarn": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", - "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/hash-it": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hash-it/-/hash-it-4.1.0.tgz", @@ -8181,18 +7097,6 @@ "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==", "deprecated": "This module has moved and is now available at @hapi/hoek. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues." }, - "node_modules/hosted-git-info": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.2.1.tgz", - "integrity": "sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw==", - "dev": true, - "dependencies": { - "lru-cache": "^7.5.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -8217,12 +7121,6 @@ "entities": "^4.4.0" } }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -8238,63 +7136,11 @@ "node": ">= 0.8" } }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/http2-client": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==" }, - "node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "dev": true, - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/http2-wrapper/node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/httpsnippet": { "version": "1.25.0", "resolved": "https://registry.npmjs.org/httpsnippet/-/httpsnippet-1.25.0.tgz", @@ -8454,20 +7300,12 @@ "node": ">=10.17.0" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "optional": true, + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -8484,59 +7322,23 @@ "node": ">= 4" } }, - "node_modules/ignore-walk": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", - "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", - "dev": true, + "node_modules/ilp-packet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ilp-packet/-/ilp-packet-2.2.0.tgz", + "integrity": "sha512-QEGqY0HzGrue4r+4GWWe7lB7Xvjij4cyc2XeOTHYmwkO0BjgwzJW85mZJzR9q5HmK8zdFkN6C0CfedAaYiUv9w==", "dependencies": { - "minimatch": "^9.0.0" - }, + "bignumber.js": "^5.0.0", + "extensible-error": "^1.0.2", + "long": "^3.2.0", + "oer-utils": "^1.3.2" + } + }, + "node_modules/ilp-packet/node_modules/bignumber.js": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-5.0.0.tgz", + "integrity": "sha512-KWTu6ZMVk9sxlDJQh2YH1UOnfDP8O8TpxUxgQG/vKASoSnEjK9aVuOueFaPcQEYQ5fyNXNTOYwYw3099RYebWg==", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/ignore-walk/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/ignore-walk/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ilp-packet": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ilp-packet/-/ilp-packet-2.2.0.tgz", - "integrity": "sha512-QEGqY0HzGrue4r+4GWWe7lB7Xvjij4cyc2XeOTHYmwkO0BjgwzJW85mZJzR9q5HmK8zdFkN6C0CfedAaYiUv9w==", - "dependencies": { - "bignumber.js": "^5.0.0", - "extensible-error": "^1.0.2", - "long": "^3.2.0", - "oer-utils": "^1.3.2" - } - }, - "node_modules/ilp-packet/node_modules/bignumber.js": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-5.0.0.tgz", - "integrity": "sha512-KWTu6ZMVk9sxlDJQh2YH1UOnfDP8O8TpxUxgQG/vKASoSnEjK9aVuOueFaPcQEYQ5fyNXNTOYwYw3099RYebWg==", - "engines": { - "node": "*" + "node": "*" } }, "node_modules/ilp-packet/node_modules/long": { @@ -8568,15 +7370,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -8614,12 +7407,6 @@ "node": ">=8" } }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -8636,15 +7423,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/ini": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", - "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -8698,19 +7476,6 @@ "url": "https://opencollective.com/ioredis" } }, - "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "dev": true, - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, - "engines": { - "node": ">= 12" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -8828,18 +7593,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", @@ -8944,28 +7697,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true - }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -8990,18 +7721,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-npm": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz", - "integrity": "sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -9251,15 +7970,6 @@ "node": ">=4" } }, - "node_modules/is-yarn-global": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", - "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -9401,24 +8111,6 @@ "set-function-name": "^2.0.1" } }, - "node_modules/jackspeak": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", - "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", - "dev": true, - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/jake": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", @@ -10059,12 +8751,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true - }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -10095,15 +8781,6 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, - "node_modules/json-parse-helpfulerror": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", - "integrity": "sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg==", - "dev": true, - "dependencies": { - "jju": "^1.1.0" - } - }, "node_modules/json-pointer": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", @@ -10221,12 +8898,6 @@ "node": ">=6" } }, - "node_modules/jsonlines": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsonlines/-/jsonlines-0.1.1.tgz", - "integrity": "sha512-ekDrAGso79Cvf+dtm+mL8OBI2bmAOt3gssYs833De/C9NmIpWDWyUO4zPgB5x2/OhY366dkhgfPMYfwZF7yOZA==", - "dev": true - }, "node_modules/jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -10464,21 +9135,6 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, - "node_modules/latest-version": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", - "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", - "dev": true, - "dependencies": { - "package-json": "^8.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -10701,18 +9357,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -10736,32 +9380,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-fetch-happen": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -11119,18 +9737,6 @@ "node": ">=6" } }, - "node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -11173,263 +9779,85 @@ "node": ">= 6" } }, - "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" }, "engines": { - "node": ">= 8" + "node": ">=10" } }, - "node_modules/minipass-collect/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, + "node_modules/mkdirp-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-1.1.0.tgz", + "integrity": "sha512-xzB0UZFcW1UGS2xkXeDh39jzTP282lb3Vwp4QzCQYmkTn4ysaV5dBdbkOXmhkcE1TQlZebQlgTceaWvDr3oFgw==", + "deprecated": "This package is broken and no longer maintained. 'mkdirp' itself supports promises now, please switch to that.", "engines": { - "node": ">=8" + "node": ">=4" + }, + "peerDependencies": { + "mkdirp": ">=0.5.0" } }, - "node_modules/minipass-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", - "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", - "dev": true, + "node_modules/mock-json-schema": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/mock-json-schema/-/mock-json-schema-1.1.1.tgz", + "integrity": "sha512-YV23vlsLP1EEOy0EviUvZTluXjLR+rhMzeayP2rcDiezj3RW01MhOSQkbQskdtg0K2fnGas5LKbSXgNjAOSX4A==", "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" + "lodash": "^4.17.21" } }, - "node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "node_modules/modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", "dev": true, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=0.10.0" } }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } + "node_modules/module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g==", + "dev": true }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "engines": { - "node": ">=8" + "node": "*" } }, - "node_modules/minipass-json-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", - "dev": true, - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/minipass-json-stream/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "bin": { + "mustache": "bin/mustache" } }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, + "node_modules/mysql": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", "dependencies": { - "minipass": "^3.0.0" + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" }, "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mkdirp-promise": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-1.1.0.tgz", - "integrity": "sha512-xzB0UZFcW1UGS2xkXeDh39jzTP282lb3Vwp4QzCQYmkTn4ysaV5dBdbkOXmhkcE1TQlZebQlgTceaWvDr3oFgw==", - "deprecated": "This package is broken and no longer maintained. 'mkdirp' itself supports promises now, please switch to that.", - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "mkdirp": ">=0.5.0" - } - }, - "node_modules/mock-json-schema": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/mock-json-schema/-/mock-json-schema-1.1.1.tgz", - "integrity": "sha512-YV23vlsLP1EEOy0EviUvZTluXjLR+rhMzeayP2rcDiezj3RW01MhOSQkbQskdtg0K2fnGas5LKbSXgNjAOSX4A==", - "dependencies": { - "lodash": "^4.17.21" - } - }, - "node_modules/modify-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", - "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/module-not-found-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g==", - "dev": true - }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "bin": { - "mustache": "bin/mustache" - } - }, - "node_modules/mysql": { - "version": "2.18.1", - "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", - "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", - "dependencies": { - "bignumber.js": "9.0.0", - "readable-stream": "2.3.7", - "safe-buffer": "5.1.2", - "sqlstring": "2.3.1" - }, - "engines": { - "node": ">= 0.6" + "node": ">= 0.6" } }, "node_modules/mysql/node_modules/bignumber.js": { @@ -11458,806 +9886,184 @@ { "type": "github", "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "node_modules/nise": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-6.0.0.tgz", - "integrity": "sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", - "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" - } - }, - "node_modules/nise/node_modules/@sinonjs/fake-timers": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", - "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/nise/node_modules/path-to-regexp": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", - "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", - "dev": true - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/node-fetch-h2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", - "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", - "dependencies": { - "http2-client": "^1.2.5" - }, - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/node-gyp": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", - "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", - "dev": true, - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^10.0.3", - "nopt": "^6.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^12.13 || ^14.13 || >=16" - } - }, - "node_modules/node-gyp/node_modules/@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", - "dev": true, - "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/node-gyp/node_modules/cacache": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", - "dev": true, - "dependencies": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/node_modules/cacache/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/node-gyp/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/node-gyp/node_modules/make-fetch-happen": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-gyp/node_modules/minipass-fetch": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", - "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", - "dev": true, - "dependencies": { - "minipass": "^3.1.6", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/node-gyp/node_modules/ssri": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", - "dev": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/node_modules/unique-filename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", - "dev": true, - "dependencies": { - "unique-slug": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/node_modules/unique-slug": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node_modules/node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "dependencies": { - "process-on-spawn": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-rdkafka": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/node-rdkafka/-/node-rdkafka-2.18.0.tgz", - "integrity": "sha512-jYkmO0sPvjesmzhv1WFOO4z7IMiAFpThR6/lcnFDWgSPkYL95CtcuVNo/R5PpjujmqSgS22GMkL1qvU4DTAvEQ==", - "hasInstallScript": true, - "dependencies": { - "bindings": "^1.3.1", - "nan": "^2.17.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/node-readfiles": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", - "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", - "dependencies": { - "es6-promise": "^3.2.1" - } - }, - "node_modules/node-readfiles/node_modules/es6-promise": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==" - }, - "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true - }, - "node_modules/nopt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", - "dev": true, - "dependencies": { - "abbrev": "^1.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/normalize-package-data": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", - "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", - "dev": true, - "dependencies": { - "hosted-git-info": "^6.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/normalize-package-data/node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", - "dev": true, - "dependencies": { - "lru-cache": "^7.5.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-bundled": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", - "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", - "dev": true, - "dependencies": { - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-check-updates": { - "version": "16.14.20", - "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.20.tgz", - "integrity": "sha512-sYbIhun4DrjO7NFOTdvs11nCar0etEhZTsEjL47eM0TuiGMhmYughRCxG2SpGRmGAQ7AkwN7bw2lWzoE7q6yOQ==", - "dev": true, - "dependencies": { - "@types/semver-utils": "^1.1.1", - "chalk": "^5.3.0", - "cli-table3": "^0.6.3", - "commander": "^10.0.1", - "fast-memoize": "^2.5.2", - "find-up": "5.0.0", - "fp-and-or": "^0.1.4", - "get-stdin": "^8.0.0", - "globby": "^11.0.4", - "hosted-git-info": "^5.1.0", - "ini": "^4.1.1", - "js-yaml": "^4.1.0", - "json-parse-helpfulerror": "^1.0.3", - "jsonlines": "^0.1.1", - "lodash": "^4.17.21", - "make-fetch-happen": "^11.1.1", - "minimatch": "^9.0.3", - "p-map": "^4.0.0", - "pacote": "15.2.0", - "parse-github-url": "^1.0.2", - "progress": "^2.0.3", - "prompts-ncu": "^3.0.0", - "rc-config-loader": "^4.1.3", - "remote-git-tags": "^3.0.0", - "rimraf": "^5.0.5", - "semver": "^7.5.4", - "semver-utils": "^1.1.4", - "source-map-support": "^0.5.21", - "spawn-please": "^2.0.2", - "strip-ansi": "^7.1.0", - "strip-json-comments": "^5.0.1", - "untildify": "^4.0.0", - "update-notifier": "^6.0.2" - }, - "bin": { - "ncu": "build/src/bin/cli.js", - "npm-check-updates": "build/src/bin/cli.js" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/npm-check-updates/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/npm-check-updates/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/npm-check-updates/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/npm-check-updates/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/npm-check-updates/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-check-updates/node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm-check-updates/node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - }, + } + ], "bin": { - "glob": "dist/esm/bin.mjs" + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/npm-check-updates/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true }, - "node_modules/npm-check-updates/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 0.6" } }, - "node_modules/npm-check-updates/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true }, - "node_modules/npm-check-updates/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "node_modules/nise": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.0.0.tgz", + "integrity": "sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==", "dev": true, "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" } }, - "node_modules/npm-check-updates/node_modules/rimraf": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", - "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==", + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", "dev": true, "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "@sinonjs/commons": "^3.0.0" } }, - "node_modules/npm-check-updates/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "node_modules/nise/node_modules/path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", + "dev": true }, - "node_modules/npm-check-updates/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" } }, - "node_modules/npm-check-updates/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/npm-check-updates/node_modules/strip-json-comments": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", - "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", - "dev": true, "engines": { - "node": ">=14.16" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, - "node_modules/npm-install-checks": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", - "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", - "dev": true, + "node_modules/node-fetch-h2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", + "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", "dependencies": { - "semver": "^7.1.1" + "http2-client": "^1.2.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "4.x || >=6.0.0" } }, - "node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true }, - "node_modules/npm-package-arg": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", - "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", "dev": true, "dependencies": { - "hosted-git-info": "^6.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" + "process-on-spawn": "^1.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/npm-package-arg/node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", - "dev": true, + "node_modules/node-rdkafka": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/node-rdkafka/-/node-rdkafka-2.18.0.tgz", + "integrity": "sha512-jYkmO0sPvjesmzhv1WFOO4z7IMiAFpThR6/lcnFDWgSPkYL95CtcuVNo/R5PpjujmqSgS22GMkL1qvU4DTAvEQ==", + "hasInstallScript": true, "dependencies": { - "lru-cache": "^7.5.1" + "bindings": "^1.3.1", + "nan": "^2.17.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=6.0.0" } }, - "node_modules/npm-packlist": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", - "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", - "dev": true, + "node_modules/node-readfiles": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", + "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", "dependencies": { - "ignore-walk": "^6.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "es6-promise": "^3.2.1" } }, - "node_modules/npm-pick-manifest": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.2.tgz", - "integrity": "sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==", - "dev": true, - "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^10.0.0", - "semver": "^7.3.5" - }, + "node_modules/node-readfiles/node_modules/es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==" + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/npm-registry-fetch": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz", - "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==", + "node_modules/npm-check-updates": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-17.0.3.tgz", + "integrity": "sha512-3UWnsnijmx4u9GnICHVCChz6JnhVLmYWqazoedWjLSY6hZB/QhMCps07vBbDmjWnHMhpl6YseAtFlvGbUq9Yrw==", "dev": true, - "dependencies": { - "make-fetch-happen": "^11.0.0", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^10.0.0", - "proc-log": "^3.0.0" + "bin": { + "ncu": "build/cli.js", + "npm-check-updates": "build/cli.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0", + "npm": ">=8.12.1" } }, "node_modules/npm-run-path": { @@ -12272,22 +10078,6 @@ "node": ">=8" } }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "deprecated": "This package is no longer supported.", - "dev": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -12935,15 +10725,6 @@ "node": ">= 0.4.0" } }, - "node_modules/p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "dev": true, - "engines": { - "node": ">=12.20" - } - }, "node_modules/p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -13010,21 +10791,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -13035,67 +10801,17 @@ }, "node_modules/package-hash": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", - "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", - "dev": true, - "dependencies": { - "got": "^12.1.0", - "registry-auth-token": "^5.0.1", - "registry-url": "^6.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pacote": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.2.0.tgz", - "integrity": "sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==", - "dev": true, - "dependencies": { - "@npmcli/git": "^4.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^6.0.1", - "@npmcli/run-script": "^6.0.0", - "cacache": "^17.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^5.0.0", - "npm-package-arg": "^10.0.0", - "npm-packlist": "^7.0.0", - "npm-pick-manifest": "^8.0.0", - "npm-registry-fetch": "^14.0.0", - "proc-log": "^3.0.0", - "promise-retry": "^2.0.1", - "read-package-json": "^6.0.0", - "read-package-json-fast": "^3.0.0", - "sigstore": "^1.3.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "lib/bin.js" + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, "node_modules/parent-module": { @@ -13110,18 +10826,6 @@ "node": ">=6" } }, - "node_modules/parse-github-url": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz", - "integrity": "sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==", - "dev": true, - "bin": { - "parse-github-url": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -13213,31 +10917,6 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } - }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -13547,15 +11226,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -13573,15 +11243,6 @@ "node": ">=8" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/prom-client": { "version": "14.2.0", "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-14.2.0.tgz", @@ -13593,25 +11254,6 @@ "node": ">=10" } }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -13625,28 +11267,6 @@ "node": ">= 6" } }, - "node_modules/prompts-ncu": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prompts-ncu/-/prompts-ncu-3.0.0.tgz", - "integrity": "sha512-qyz9UxZ5MlPKWVhWrCmSZ1ahm2GVYdjLb8og2sg0IPth1KRuhcggHGuijz0e41dkx35p1t1q3GRISGH7QGALFA==", - "dev": true, - "dependencies": { - "kleur": "^4.0.1", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/prompts-ncu/node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -13664,12 +11284,6 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", - "dev": true - }, "node_modules/protobufjs": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.0.tgz", @@ -13754,21 +11368,6 @@ "node": ">=6" } }, - "node_modules/pupa": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", - "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==", - "dev": true, - "dependencies": { - "escape-goat": "^4.0.0" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -13903,18 +11502,6 @@ "rc": "cli.js" } }, - "node_modules/rc-config-loader": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.3.tgz", - "integrity": "sha512-kD7FqML7l800i6pS6pvLyIE2ncbk9Du8Q0gp/4hMPhJU6ZxApkoLcGD8ZeqgiAlfwZ6BlETq6qqe+12DUL207w==", - "dev": true, - "dependencies": { - "debug": "^4.3.4", - "js-yaml": "^4.1.0", - "json5": "^2.2.2", - "require-from-string": "^2.0.2" - } - }, "node_modules/rc/node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", @@ -13934,136 +11521,6 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, - "node_modules/read-package-json": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz", - "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==", - "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", - "dev": true, - "dependencies": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^5.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-json-fast": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", - "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", - "dev": true, - "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-json/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/read-package-json/node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/read-package-json/node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/read-package-json/node_modules/json-parse-even-better-errors": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-json/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/read-package-json/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/read-package-json/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -14384,33 +11841,6 @@ "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/registry-auth-token": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", - "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==", - "dev": true, - "dependencies": { - "@pnpm/npm-conf": "^2.1.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/registry-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", - "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", - "dev": true, - "dependencies": { - "rc": "1.2.8" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -14423,15 +11853,6 @@ "node": ">=4" } }, - "node_modules/remote-git-tags": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remote-git-tags/-/remote-git-tags-3.0.0.tgz", - "integrity": "sha512-C9hAO4eoEsX+OXA4rla66pXZQ+TLQ8T9dttgQj18yuKlPMTVkIkdYXvlMC55IuUsIkV6DpmQYi10JKFLaU+l7w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -14665,12 +12086,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true - }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -14720,21 +12135,6 @@ "node": ">=10" } }, - "node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "dev": true, - "dependencies": { - "lowercase-keys": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ret": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", @@ -14744,15 +12144,6 @@ "node": ">=4" } }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -14910,27 +12301,6 @@ "node": ">=10" } }, - "node_modules/semver-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", - "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", - "dev": true, - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/semver-utils": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/semver-utils/-/semver-utils-1.1.4.tgz", - "integrity": "sha512-EjnoLE5OGmDAVV/8YDoN5KiajNadjzIp9BAHOhYeQHt7j0UWxjmgsx4YD48wp4Ue1Qogq38F1GNUJNqF1kKKxA==", - "dev": true - }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -15166,25 +12536,6 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, - "node_modules/sigstore": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.9.0.tgz", - "integrity": "sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==", - "dev": true, - "dependencies": { - "@sigstore/bundle": "^1.1.0", - "@sigstore/protobuf-specs": "^0.2.0", - "@sigstore/sign": "^1.0.0", - "@sigstore/tuf": "^1.0.3", - "make-fetch-happen": "^11.0.1" - }, - "bin": { - "sigstore": "bin/sigstore.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -15237,45 +12588,7 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "engines": { - "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.8.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", - "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", - "dev": true, - "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", - "dev": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" + "node": ">=8" } }, "node_modules/source-map": { @@ -15305,18 +12618,6 @@ "source-map": "^0.6.0" } }, - "node_modules/spawn-please": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/spawn-please/-/spawn-please-2.0.2.tgz", - "integrity": "sha512-KM8coezO6ISQ89c1BzyWNtcn2V2kAVtwIXd3cN/V5a0xPYc1F/vydrRc01wsKFEQ/p+V1a4sw4z2yMITIXrgGw==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/spawn-sync": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", @@ -15435,12 +12736,6 @@ "node": ">= 6" } }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true - }, "node_modules/sqlstring": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", @@ -15449,27 +12744,6 @@ "node": ">= 0.6" } }, - "node_modules/ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", - "dev": true, - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/ssri/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -16048,21 +13322,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/string.prototype.matchall": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", @@ -16177,19 +13436,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -16369,47 +13615,6 @@ "node": ">=6.x" } }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dev": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tarn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", @@ -16624,20 +13829,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/tuf-js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz", - "integrity": "sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==", - "dev": true, - "dependencies": { - "@tufjs/models": "1.0.4", - "debug": "^4.3.4", - "make-fetch-happen": "^11.1.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -16878,45 +14069,6 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, - "node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", - "dev": true, - "dependencies": { - "unique-slug": "^4.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/unique-string": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", - "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", - "dev": true, - "dependencies": { - "crypto-random-string": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -16925,15 +14077,6 @@ "node": ">= 0.8" } }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/update-browserslist-db": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", @@ -16964,58 +14107,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/update-notifier": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", - "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", - "dev": true, - "dependencies": { - "boxen": "^7.0.0", - "chalk": "^5.0.1", - "configstore": "^6.0.0", - "has-yarn": "^3.0.0", - "import-lazy": "^4.0.0", - "is-ci": "^3.0.1", - "is-installed-globally": "^0.4.0", - "is-npm": "^6.0.0", - "is-yarn-global": "^0.4.0", - "latest-version": "^7.0.0", - "pupa": "^3.1.0", - "semver": "^7.3.7", - "semver-diff": "^4.0.0", - "xdg-basedir": "^5.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/xdg-basedir": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -17086,15 +14177,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/validate-npm-package-name": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/validator": { "version": "10.11.0", "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", @@ -17514,80 +14596,6 @@ "yargs-parser": "^11.1.1" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/widest-line": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", - "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", - "dev": true, - "dependencies": { - "string-width": "^5.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/widest-line/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/widest-line/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/widest-line/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/widest-line/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", @@ -17679,103 +14687,6 @@ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index f3bcb501..b65df10d 100644 --- a/package.json +++ b/package.json @@ -107,17 +107,17 @@ "@hapi/vision": "7.0.3", "@mojaloop/central-services-error-handling": "13.0.1", "@mojaloop/central-services-health": "15.0.0", - "@mojaloop/central-services-logger": "11.4.5", + "@mojaloop/central-services-logger": "11.5.0", "@mojaloop/central-services-metrics": "12.0.8", "@mojaloop/central-services-shared": "18.5.0-snapshot.2", "@mojaloop/central-services-stream": "11.3.1", "@mojaloop/event-sdk": "14.1.1", - "@mojaloop/inter-scheme-proxy-cache-lib": "2.0.0-snapshot.1", + "@mojaloop/inter-scheme-proxy-cache-lib": "2.2.0", "@mojaloop/ml-number": "11.2.4", "@mojaloop/sdk-standard-components": "18.1.0", "ajv": "8.17.1", "ajv-keywords": "5.1.0", - "axios": "1.7.2", + "axios": "1.7.3", "blipp": "4.0.2", "commander": "12.1.0", "event-stream": "4.0.1", @@ -138,10 +138,10 @@ "audit-ci": "^7.1.0", "eslint": "8.16.0", "eslint-config-standard": "17.1.0", - "eslint-plugin-jest": "28.6.0", + "eslint-plugin-jest": "28.7.0", "jest": "29.7.0", "jest-junit": "16.0.0", - "npm-check-updates": "16.14.20", + "npm-check-updates": "17.0.3", "nyc": "17.0.0", "pre-commit": "1.2.2", "proxyquire": "2.1.3", diff --git a/src/lib/util.js b/src/lib/util.js index 91a4a43d..34b70ec8 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -224,7 +224,7 @@ const fetchParticipantInfo = async (source, destination, cache) => { if (!cachedPayee) { requestPayee = await axios.request({ url: `${url}/${destination}` }) cache && cache.put(`fetchParticipantInfo_${destination}`, requestPayee, Config.participantDataCacheExpiresInMs) - Logger.isDebugEnabled && Logger.debug(`${new Date().toISOString()}, [fetchParticipantInfo]: cache miss for payer ${source}`) + Logger.isDebugEnabled && Logger.debug(`${new Date().toISOString()}, [fetchParticipantInfo]: cache miss for payee ${source}`) } else { Logger.isDebugEnabled && Logger.debug(`${new Date().toISOString()}, [fetchParticipantInfo]: cache hit for payee ${destination}`) } diff --git a/src/model/fxQuotes.js b/src/model/fxQuotes.js index 1efba8d1..02202081 100644 --- a/src/model/fxQuotes.js +++ b/src/model/fxQuotes.js @@ -25,6 +25,7 @@ const EventSdk = require('@mojaloop/event-sdk') const LibUtil = require('@mojaloop/central-services-shared').Util const Logger = require('@mojaloop/central-services-logger') const JwsSigner = require('@mojaloop/sdk-standard-components').Jws.signer +const Metrics = require('@mojaloop/central-services-metrics') const Config = require('../lib/config') const { httpRequest } = require('../lib/http') @@ -48,15 +49,31 @@ class FxQuotesModel { * @returns {promise} - promise will reject if request is not valid */ async validateFxQuoteRequest (fspiopDestination, fxQuoteRequest) { - const currencies = [fxQuoteRequest.conversionTerms.sourceAmount.currency, fxQuoteRequest.conversionTerms.targetAmount.currency] - - // Ensure the proxy client is connected - if (this.proxyClient?.isConnected === false) await this.proxyClient.connect() - // if the payee dfsp has a proxy cache entry, we do not validate the dfsp here - if (!(await this.proxyClient?.lookupProxyByDfspId(fspiopDestination))) { - await Promise.all(currencies.map(async (currency) => { - await this.db.getParticipant(fspiopDestination, LOCAL_ENUM.COUNTERPARTY_FSP, currency, ENUM.Accounts.LedgerAccountType.POSITION) - })) + const histTimer = Metrics.getHistogram( + 'model_fxquote', + 'validateFxQuoteRequest - Metrics for fx quote model', + ['success', 'queryName'] + ).startTimer() + const appConfig = new Config() + try { + const currencies = [fxQuoteRequest.conversionTerms.sourceAmount.currency, fxQuoteRequest.conversionTerms.targetAmount.currency] + + // Ensure the proxy client is connected + if (this.proxyClient?.isConnected === false) await this.proxyClient.connect() + // if the payee dfsp has a proxy cache entry, we do not validate the dfsp here + if (!(await this.proxyClient?.lookupProxyByDfspId(fspiopDestination))) { + await Promise.all(currencies.map(async (currency) => this.db.getParticipant(fspiopDestination, LOCAL_ENUM.COUNTERPARTY_FSP, currency, ENUM.Accounts.LedgerAccountType.POSITION))) + } + + if (appConfig.simpleRoutingMode) { + // TODO: should we validate initiatingFsp (if not source) and counterPartyFsp (if not destination) here? + // also check proxy mapping before validing the fsp + } + + histTimer({ success: true, queryName: 'validateFxQuoteRequest' }) + } catch (error) { + histTimer({ success: false, queryName: 'validateFxQuoteRequest' }) + throw error } } @@ -66,6 +83,11 @@ class FxQuotesModel { * @returns {undefined} */ async handleFxQuoteRequest (headers, fxQuoteRequest, span) { + const histTimer = Metrics.getHistogram( + 'model_fxquote', + 'handleFxQuoteRequest - Metrics for fx quote model', + ['success', 'queryName'] + ).startTimer() let fspiopSource const childSpan = span.getChild('qs_fxquote_forwardFxQuoteRequest') try { @@ -75,11 +97,12 @@ class FxQuotesModel { const fspiopDestination = headers[ENUM.Http.Headers.FSPIOP.DESTINATION] await this.validateFxQuoteRequest(fspiopDestination, fxQuoteRequest) - await this.forwardFxQuoteRequest(headers, fxQuoteRequest.conversionRequestId, fxQuoteRequest, childSpan) + histTimer({ success: true, queryName: 'handleFxQuoteRequest' }) } catch (err) { this.writeLog(`Error forwarding fx quote request: ${getStackOrInspect(err)}. Attempting to send error callback to ${fspiopSource}`) await this.handleException(fspiopSource, fxQuoteRequest.conversionRequestId, err, headers, childSpan) + histTimer({ success: false, queryName: 'handleFxQuoteRequest' }) } finally { if (childSpan && !childSpan.isFinished) { await childSpan.finish() @@ -93,6 +116,11 @@ class FxQuotesModel { * @returns {undefined} */ async forwardFxQuoteRequest (headers, conversionRequestId, originalFxQuoteRequest, span) { + const histTimer = Metrics.getHistogram( + 'model_fxquote', + 'forwardFxQuoteRequest - Metrics for fx quote model', + ['success', 'queryName'] + ).startTimer() let endpoint const fspiopSource = headers[ENUM.Http.Headers.FSPIOP.SOURCE] const fspiopDest = headers[ENUM.Http.Headers.FSPIOP.DESTINATION] @@ -128,8 +156,10 @@ class FxQuotesModel { this.writeLog(`Forwarding request : ${util.inspect(opts)}`) await httpRequest(opts, fspiopSource) + histTimer({ success: true, queryName: 'forwardFxQuoteRequest' }) } catch (err) { this.writeLog(`Error forwarding fxQuote request to endpoint ${endpoint}: ${getStackOrInspect(err)}`) + histTimer({ success: false, queryName: 'forwardFxQuoteRequest' }) throw ErrorHandler.ReformatFSPIOPError(err) } } @@ -140,6 +170,12 @@ class FxQuotesModel { * @returns {undefined} */ async handleFxQuoteUpdate (headers, conversionRequestId, fxQuoteUpdateRequest, span) { + const histTimer = Metrics.getHistogram( + 'model_fxquote', + 'handleFxQuoteUpdate - Metrics for fx quote model', + ['success', 'queryName'] + ).startTimer() + if ('accept' in headers) { throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, `Update for fx quote ${conversionRequestId} failed: "accept" header should not be sent in callbacks.`, null, headers['fspiop-source']) @@ -149,10 +185,12 @@ class FxQuotesModel { try { await childSpan.audit({ headers, params: { conversionRequestId }, payload: fxQuoteUpdateRequest }, EventSdk.AuditEventAction.start) await this.forwardFxQuoteUpdate(headers, conversionRequestId, fxQuoteUpdateRequest, childSpan) + histTimer({ success: true, queryName: 'handleFxQuoteUpdate' }) } catch (err) { const fspiopSource = headers[ENUM.Http.Headers.FSPIOP.SOURCE] this.writeLog(`Error forwarding fx quote update: ${getStackOrInspect(err)}. Attempting to send error callback to ${fspiopSource}`) await this.handleException(fspiopSource, conversionRequestId, err, headers, childSpan) + histTimer({ success: false, queryName: 'handleFxQuoteUpdate' }) } finally { if (childSpan && !childSpan.isFinished) { await childSpan.finish() @@ -166,6 +204,12 @@ class FxQuotesModel { * @returns {undefined} */ async forwardFxQuoteUpdate (headers, conversionRequestId, originalFxQuoteResponse, span) { + const histTimer = Metrics.getHistogram( + 'model_fxquote', + 'forwardFxQuoteUpdate - Metrics for fx quote model', + ['success', 'queryName'] + ).startTimer() + let endpoint = null const fspiopSource = headers[ENUM.Http.Headers.FSPIOP.SOURCE] const fspiopDest = headers[ENUM.Http.Headers.FSPIOP.DESTINATION] @@ -202,8 +246,10 @@ class FxQuotesModel { } await httpRequest(opts, fspiopSource) + histTimer({ success: true, queryName: 'forwardFxQuoteUpdate' }) } catch (err) { this.writeLog(`Error forwarding fx quote callback to endpoint ${endpoint}: ${getStackOrInspect(err)}`) + histTimer({ success: false, queryName: 'forwardFxQuoteUpdate' }) throw ErrorHandler.ReformatFSPIOPError(err) } } @@ -214,14 +260,21 @@ class FxQuotesModel { * @returns {undefined} */ async handleFxQuoteGet (headers, conversionRequestId, span) { + const histTimer = Metrics.getHistogram( + 'model_fxquote', + 'handleFxQuoteGet - Metrics for fx quote model', + ['success', 'queryName'] + ).startTimer() const fspiopSource = headers[ENUM.Http.Headers.FSPIOP.SOURCE] const childSpan = span.getChild('qs_quote_forwardFxQuoteGet') try { await childSpan.audit({ headers, params: { conversionRequestId } }, EventSdk.AuditEventAction.start) await this.forwardFxQuoteGet(headers, conversionRequestId, childSpan) + histTimer({ success: true, queryName: 'handleFxQuoteGet' }) } catch (err) { this.writeLog(`Error forwarding fx quote get: ${getStackOrInspect(err)}. Attempting to send error callback to ${fspiopSource}`) await this.handleException(fspiopSource, conversionRequestId, err, headers, childSpan) + histTimer({ success: false, queryName: 'handleFxQuoteGet' }) } finally { if (childSpan && !childSpan.isFinished) { await childSpan.finish() @@ -235,9 +288,13 @@ class FxQuotesModel { * @returns {undefined} */ async forwardFxQuoteGet (headers, conversionRequestId, span) { + const histTimer = Metrics.getHistogram( + 'model_fxquote', + 'forwardFxQuoteGet - Metrics for fx quote model', + ['success', 'queryName'] + ).startTimer() let endpoint try { - // lookup fxp callback endpoint const fspiopSource = headers[ENUM.Http.Headers.FSPIOP.SOURCE] const fspiopDest = headers[ENUM.Http.Headers.FSPIOP.DESTINATION] endpoint = await this._getParticipantEndpoint(fspiopDest) @@ -265,8 +322,10 @@ class FxQuotesModel { } await httpRequest(opts, fspiopSource) + histTimer({ success: true, queryName: 'forwardFxQuoteGet' }) } catch (err) { this.writeLog(`Error forwarding fx quote get request: ${getStackOrInspect(err)}`) + histTimer({ success: false, queryName: 'forwardFxQuoteGet' }) throw ErrorHandler.ReformatFSPIOPError(err) } } @@ -277,17 +336,22 @@ class FxQuotesModel { * @returns {undefined} */ async handleFxQuoteError (headers, conversionRequestId, error, span) { - let newError + const histTimer = Metrics.getHistogram( + 'model_fxquote', + 'handleFxQuoteError - Metrics for fx quote model', + ['success', 'queryName'] + ).startTimer() const fspiopSource = headers[ENUM.Http.Headers.FSPIOP.SOURCE] const childSpan = span.getChild('qs_quote_forwardFxQuoteError') try { const fspiopError = ErrorHandler.CreateFSPIOPErrorFromErrorInformation(error) await childSpan.audit({ headers, params: { conversionRequestId } }, EventSdk.AuditEventAction.start) await this.sendErrorCallback(headers[ENUM.Http.Headers.FSPIOP.DESTINATION], fspiopError, conversionRequestId, headers, childSpan, false) - return newError + histTimer({ success: true, queryName: 'handleFxQuoteError' }) } catch (err) { this.writeLog(`Error in handleFxQuoteError: ${getStackOrInspect(err)}`) await this.handleException(fspiopSource, conversionRequestId, err, headers, childSpan) + histTimer({ success: false, queryName: 'handleFxQuoteError' }) } finally { if (childSpan && !childSpan.isFinished) { await childSpan.finish() @@ -300,14 +364,21 @@ class FxQuotesModel { * dfsp that initiated the request. */ async handleException (fspiopSource, conversionRequestId, error, headers, span) { + const histTimer = Metrics.getHistogram( + 'model_fxquote', + 'handleException - Metrics for fx quote model', + ['success', 'queryName'] + ).startTimer() const fspiopError = ErrorHandler.ReformatFSPIOPError(error) const childSpan = span.getChild('qs_fxQuote_sendErrorCallback') try { await childSpan.audit({ headers, params: { conversionRequestId } }, EventSdk.AuditEventAction.start) - return await this.sendErrorCallback(fspiopSource, fspiopError, conversionRequestId, headers, childSpan, true) + await this.sendErrorCallback(fspiopSource, fspiopError, conversionRequestId, headers, childSpan, true) + histTimer({ success: true, queryName: 'handleException' }) } catch (err) { this.writeLog(`Error occurred while handling error. Check service logs as this error may not have been propagated successfully to any other party: ${getStackOrInspect(err)}`) + histTimer({ success: false, queryName: 'handleException' }) } finally { if (!childSpan.isFinished) { await childSpan.finish() @@ -323,6 +394,11 @@ class FxQuotesModel { * @returns {promise} */ async sendErrorCallback (fspiopSource, fspiopError, conversionRequestId, headers, span, modifyHeaders = true) { + const histTimer = Metrics.getHistogram( + 'model_fxquote', + 'sendErrorCallback - Metrics for fx quote model', + ['success', 'queryName'] + ).startTimer() const envConfig = new Config() const fspiopDest = headers[ENUM.Http.Headers.FSPIOP.DESTINATION] try { @@ -394,6 +470,7 @@ class FxQuotesModel { } res = await axios.request(opts) + histTimer({ success: true, queryName: 'sendErrorCallback' }) } catch (err) { throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_COMMUNICATION_ERROR, `network error in sendErrorCallback: ${err.message}`, { error: err, @@ -424,6 +501,7 @@ class FxQuotesModel { await span.error(fspiopError, state) await span.finish(fspiopError.message, state) } + histTimer({ success: false, queryName: 'sendErrorCallback' }) throw fspiopError } } diff --git a/src/model/quotes.js b/src/model/quotes.js index cbeb5c47..a8e39db6 100644 --- a/src/model/quotes.js +++ b/src/model/quotes.js @@ -162,50 +162,53 @@ class QuotesModel { // throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.NOT_IMPLEMENTED, 'Only PAYER initiated transactions are supported', null, fspiopSource) // } - // Any quoteRequest specific validations to be added here - if (!quoteRequest) { - // internal-error - histTimer({ success: false, queryName: 'quote_validateQuoteRequest' }) - throw ErrorHandler.CreateInternalServerFSPIOPError('Missing quoteRequest', null, fspiopSource) - } - - // Ensure the proxy client is connected if we need to use it down the road - if (this.proxyClient?.isConnected === false) await this.proxyClient.connect() - - // In fspiop api spec 2.0, to support FX, `supportedCurrencies` can be optionally passed in via the payer property. - // If `supportedCurrencies` is present, then payer FSP must have position accounts for all those currencies. - if (quoteRequest.payer.supportedCurrencies && quoteRequest.payer.supportedCurrencies.length > 0) { - await Promise.all(quoteRequest.payer.supportedCurrencies.map(currency => - this.db.getParticipant(fspiopSource, LOCAL_ENUM.PAYER_DFSP, currency, ENUM.Accounts.LedgerAccountType.POSITION) - )) - } else { - // If it is not passed in, then we validate payee against the `amount` currency. - // if the payee dfsp has a proxy cache entry, we do not validate the dfsp here - if (!(await this.proxyClient?.lookupProxyByDfspId(fspiopDestination))) { - await this.db.getParticipant(fspiopDestination, LOCAL_ENUM.PAYEE_DFSP, quoteRequest.amount.currency, ENUM.Accounts.LedgerAccountType.POSITION) + try { + // Any quoteRequest specific validations to be added here + if (!quoteRequest) { + // internal-error + throw ErrorHandler.CreateInternalServerFSPIOPError('Missing quoteRequest', null, fspiopSource) } - } - histTimer({ success: true, queryName: 'quote_validateQuoteRequest' }) + // Ensure the proxy client is connected if we need to use it down the road + if (this.proxyClient?.isConnected === false) await this.proxyClient.connect() - // Following is the validation to make sure valid fsp's are used in the payload for simple routing mode - if (envConfig.simpleRoutingMode) { - // Lets make sure the optional fspId exists in the payer's partyIdInfo before we validate it - if ( - quoteRequest.payer?.partyIdInfo?.fspId && - quoteRequest.payer.partyIdInfo.fspId !== fspiopSource - ) { - await this.db.getParticipant(quoteRequest.payer.partyIdInfo.fspId, LOCAL_ENUM.PAYER_DFSP, quoteRequest.amount.currency, ENUM.Accounts.LedgerAccountType.POSITION) - } - // Lets make sure the optional fspId exists in the payee's partyIdInfo before we validate it - if ( - quoteRequest.payee?.partyIdInfo?.fspId && - quoteRequest.payee.partyIdInfo.fspId !== fspiopDestination && + // In fspiop api spec 2.0, to support FX, `supportedCurrencies` can be optionally passed in via the payer property. + // If `supportedCurrencies` is present, then payer FSP must have position accounts for all those currencies. + if (quoteRequest.payer.supportedCurrencies && quoteRequest.payer.supportedCurrencies.length > 0) { + await Promise.all(quoteRequest.payer.supportedCurrencies.map(currency => + this.db.getParticipant(fspiopSource, LOCAL_ENUM.PAYER_DFSP, currency, ENUM.Accounts.LedgerAccountType.POSITION) + )) + } else { + // If it is not passed in, then we validate payee against the `amount` currency. // if the payee dfsp has a proxy cache entry, we do not validate the dfsp here - !(await this.proxyClient?.lookupProxyByDfspId(quoteRequest.payee.partyIdInfo.fspId)) - ) { - await this.db.getParticipant(quoteRequest.payee.partyIdInfo.fspId, LOCAL_ENUM.PAYEE_DFSP, quoteRequest.amount.currency, ENUM.Accounts.LedgerAccountType.POSITION) + if (!(await this.proxyClient?.lookupProxyByDfspId(fspiopDestination))) { + await this.db.getParticipant(fspiopDestination, LOCAL_ENUM.PAYEE_DFSP, quoteRequest.amount.currency, ENUM.Accounts.LedgerAccountType.POSITION) + } + } + + // Following is the validation to make sure valid fsp's are used in the payload for simple routing mode + if (envConfig.simpleRoutingMode) { + // Lets make sure the optional fspId exists in the payer's partyIdInfo before we validate it + if ( + quoteRequest.payer?.partyIdInfo?.fspId && + quoteRequest.payer.partyIdInfo.fspId !== fspiopSource + ) { + await this.db.getParticipant(quoteRequest.payer.partyIdInfo.fspId, LOCAL_ENUM.PAYER_DFSP, quoteRequest.amount.currency, ENUM.Accounts.LedgerAccountType.POSITION) + } + // Lets make sure the optional fspId exists in the payee's partyIdInfo before we validate it + if ( + quoteRequest.payee?.partyIdInfo?.fspId && + quoteRequest.payee.partyIdInfo.fspId !== fspiopDestination && + // if the payee dfsp has a proxy cache entry, we do not validate the dfsp here + !(await this.proxyClient?.lookupProxyByDfspId(quoteRequest.payee.partyIdInfo.fspId)) + ) { + await this.db.getParticipant(quoteRequest.payee.partyIdInfo.fspId, LOCAL_ENUM.PAYEE_DFSP, quoteRequest.amount.currency, ENUM.Accounts.LedgerAccountType.POSITION) + } } + histTimer({ success: true, queryName: 'quote_validateQuoteRequest' }) + } catch (err) { + histTimer({ success: false, queryName: 'quote_validateQuoteRequest' }) + throw err } } From fd4f935786695dcc8cb610eb56ccc86292e8a47b Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 6 Aug 2024 11:41:17 +0100 Subject: [PATCH 02/28] refactor: minor refactor --- src/lib/util.js | 2 +- src/model/fxQuotes.js | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/lib/util.js b/src/lib/util.js index ebfbd4e9..7f364426 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -225,7 +225,7 @@ const fetchParticipantInfo = async (source, destination, cache) => { if (!cachedPayee) { requestPayee = await axios.request({ url: `${url}/${destination}` }) cache && cache.put(`fetchParticipantInfo_${destination}`, requestPayee, Config.participantDataCacheExpiresInMs) - Logger.isDebugEnabled && Logger.debug(`${new Date().toISOString()}, [fetchParticipantInfo]: cache miss for payee ${source}`) + Logger.isDebugEnabled && Logger.debug(`${new Date().toISOString()}, [fetchParticipantInfo]: cache miss for payee ${destination}`) } else { Logger.isDebugEnabled && Logger.debug(`${new Date().toISOString()}, [fetchParticipantInfo]: cache hit for payee ${destination}`) } diff --git a/src/model/fxQuotes.js b/src/model/fxQuotes.js index e55d8901..593ba271 100644 --- a/src/model/fxQuotes.js +++ b/src/model/fxQuotes.js @@ -406,6 +406,9 @@ class FxQuotesModel { try { const endpoint = await this._getParticipantEndpoint(fspiopSource) + + log.debug(`Resolved participant '${fspiopSource}' FSPIOP_CALLBACK_URL_FX_QUOTES to: '${endpoint}'`) + if (!endpoint) { throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.PARTY_NOT_FOUND, `No FSPIOP_CALLBACK_URL_FX_QUOTES endpoint found for FSP '${fspiopSource}', unable to make error callback`, null, fspiopSource) } @@ -457,12 +460,11 @@ class FxQuotesModel { try { // If JWS is enabled and the 'fspiop-source' matches the configured jws header value('switch') // that means it's a switch generated message and we need to sign it - if (envConfig.jws?.jwsSign && opts.headers['fspiop-source'] === envConfig.jws.fspiopSourceToSign) { - const logger = Logger - logger.log = logger.info + const needToSign = !opts.headers['fspiop-signature'] && envConfig.jws?.jwsSign && opts.headers['fspiop-source'] === envConfig.jws.fspiopSourceToSign + if (needToSign) { this.writeLog('Getting the JWS Signer to sign the switch generated message') const jwsSigner = new JwsSigner({ - logger, + logger: this.log, signingKey: envConfig.jws.jwsSigningKey }) opts.headers['fspiop-signature'] = jwsSigner.getSignature(opts) @@ -476,7 +478,7 @@ class FxQuotesModel { url: fullCallbackUrl, sourceFsp: fspiopSource, destinationFsp: fspiopDest, - method: opts && opts.method, + method: opts?.method, request: JSON.stringify(opts, LibUtil.getCircularReplacer()) }, fspiopSource) } @@ -487,7 +489,7 @@ class FxQuotesModel { url: fullCallbackUrl, sourceFsp: fspiopSource, destinationFsp: fspiopDest, - method: opts && opts.method, + method: opts?.method, request: JSON.stringify(opts, LibUtil.getCircularReplacer()), response: JSON.stringify(res, LibUtil.getCircularReplacer()) }, fspiopSource) @@ -523,7 +525,8 @@ class FxQuotesModel { } /** - * Writes a formatted message to the console + * Sends HTTP request + * * @param {AxiosRequestConfig} options * @returns {AxiosResponse} */ From 2b0b45e040f79a96ffb29af841154f600b8370e7 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 6 Aug 2024 15:07:25 +0100 Subject: [PATCH 03/28] ci: update redis-cluster config for cross env dev, refactor for envConfig --- docker-compose.yml | 37 +++++++++++++++-- package.json | 4 +- src/handlers/init.js | 4 +- src/model/bulkQuotes.js | 13 +++--- src/model/fxQuotes.js | 16 +++----- src/model/quotes.js | 21 +++++----- test/unit/mocks.js | 69 +++++++++++++++++++++++++++++++- test/unit/model/fxQuotes.test.js | 28 +++++++++++++ 8 files changed, 156 insertions(+), 36 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a7cf89ca..77794922 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,17 +25,23 @@ x-quoting-service: "ingServiceBase - ./secrets/:/opt/app/secrets/ depends_on: <<: *dependsOnMysqlAndKafka + extra_hosts: + - "redis-node-0:host-gateway" # central-ledger: # condition: service_healthy # to perform test dfsp onboarding +# @see https://uninterrupted.tech/blog/hassle-free-redis-cluster-deployment-using-docker/ x-redis-node: &REDIS_NODE image: docker.io/bitnami/redis-cluster:6.2.14 environment: &REDIS_ENVS ALLOW_EMPTY_PASSWORD: yes - REDIS_NODES: redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5 + REDIS_CLUSTER_DYNAMIC_IPS: no + REDIS_CLUSTER_ANNOUNCE_IP: ${REDIS_CLUSTER_ANNOUNCE_IP} + REDIS_NODES: localhost:6379 localhost:6380 localhost:6381 localhost:6382 localhost:6383 localhost:6384 healthcheck: test: [ "CMD", "redis-cli", "ping" ] timeout: 2s + network_mode: host x-healthcheck-params: &healthcheckParams interval: 30s @@ -113,25 +119,48 @@ services: environment: <<: *REDIS_ENVS REDIS_CLUSTER_CREATOR: yes + REDIS_PORT_NUMBER: 6379 depends_on: - redis-node-1 - redis-node-2 - redis-node-3 - redis-node-4 - redis-node-5 - ports: - - "6379:6379" - redis-node-1: <<: *REDIS_NODE + environment: + <<: *REDIS_ENVS + REDIS_PORT_NUMBER: 6380 + ports: + - "16380:16380" redis-node-2: <<: *REDIS_NODE + environment: + <<: *REDIS_ENVS + REDIS_PORT_NUMBER: 6381 + ports: + - "16381:16381" redis-node-3: <<: *REDIS_NODE + environment: + <<: *REDIS_ENVS + REDIS_PORT_NUMBER: 6382 + ports: + - "16382:16382" redis-node-4: <<: *REDIS_NODE + environment: + <<: *REDIS_ENVS + REDIS_PORT_NUMBER: 6383 + ports: + - "16383:16383" redis-node-5: <<: *REDIS_NODE + environment: + <<: *REDIS_ENVS + REDIS_PORT_NUMBER: 6384 + ports: + - "16384:16384" ## To use with proxyCache.type === 'redis' # redis: diff --git a/package.json b/package.json index 50ec770b..82be5c5c 100644 --- a/package.json +++ b/package.json @@ -66,10 +66,10 @@ "package-lock": "docker run --rm -it quoting-service:local cat package-lock.json > package-lock.json", "run": "docker run -p 3002:3002 --rm --link db:mysql quoting-service:local", "docker:build": "docker build --build-arg NODE_VERSION=\"$(cat .nvmrc)-alpine\" -t mojaloop/quoting-service:local -f ./Dockerfile .", - "docker:up": "docker-compose up", + "docker:up": ". ./scripts/env.sh && docker-compose up -d", "docker:stop": "docker-compose stop", "docker:rm": "docker-compose rm -f -v", - "docker:down": "docker-compose down -v", + "docker:down": ". ./scripts/env.sh && docker-compose down -v", "docker:clean": "docker-compose down --rmi local", "generate-docs": "npx jsdoc -c jsdoc.json", "audit:fix": "npm audit fix", diff --git a/src/handlers/init.js b/src/handlers/init.js index 0ab5b7f1..9961f685 100644 --- a/src/handlers/init.js +++ b/src/handlers/init.js @@ -24,8 +24,8 @@ const startFn = async (handlerList) => { if (!isDbOk) throw new Error('DB is not connected') if (config.proxyCache.enabled) { - const isProxyOk = proxyClient = createProxyClient({ proxyCacheConfig: config.proxyCache }) - await proxyClient.connect() + proxyClient = createProxyClient({ proxyCacheConfig: config.proxyCache }) + const isProxyOk = await proxyClient.connect() if (!isProxyOk) throw new Error('Proxy is not connected') Logger.isInfoEnabled && Logger.info('Proxy cache is connected') } diff --git a/src/model/bulkQuotes.js b/src/model/bulkQuotes.js index 7dd68c29..896b1cb5 100644 --- a/src/model/bulkQuotes.js +++ b/src/model/bulkQuotes.js @@ -59,6 +59,7 @@ class BulkQuotesModel { this.db = deps.db this.requestId = deps.requestId this.proxyClient = deps.proxyClient + this.envConfig = deps.config || new Config() } /** @@ -139,7 +140,7 @@ class BulkQuotesModel { } const fullCallbackUrl = `${endpoint}${ENUM.EndPoints.FspEndpointTemplates.BULK_QUOTES_POST}` - const newHeaders = generateRequestHeaders(headers, this.db.config.protocolVersions) + const newHeaders = generateRequestHeaders(headers, this.envConfig.protocolVersions) this.writeLog(`Forwarding quote request to endpoint: ${fullCallbackUrl}`) this.writeLog(`Forwarding quote request headers: ${JSON.stringify(newHeaders)}`) @@ -223,7 +224,7 @@ class BulkQuotesModel { // we need to strip off the 'accept' header // for all PUT requests as per the API Specification Document // https://github.com/mojaloop/mojaloop-specification/blob/main/documents/v1.1-document-set/fspiop-v1.1-openapi2.yaml - const newHeaders = generateRequestHeaders(headers, this.db.config.protocolVersions, true) + const newHeaders = generateRequestHeaders(headers, this.envConfig.protocolVersions, true) this.writeLog(`Forwarding bulk quote response to endpoint: ${fullCallbackUrl}`) this.writeLog(`Forwarding bulk quote response headers: ${JSON.stringify(newHeaders)}`) @@ -297,7 +298,7 @@ class BulkQuotesModel { } const fullCallbackUrl = `${endpoint}/bulkQuotes/${bulkQuoteId}` - const newHeaders = generateRequestHeaders(headers, this.db.config.protocolVersions) + const newHeaders = generateRequestHeaders(headers, this.envConfig.protocolVersions) this.writeLog(`Forwarding quote get request to endpoint: ${fullCallbackUrl}`) @@ -378,7 +379,7 @@ class BulkQuotesModel { * @returns {promise} */ async sendErrorCallback (fspiopSource, fspiopError, bulkQuoteId, headers, span, modifyHeaders = true) { - const envConfig = new Config() + const { envConfig } = this const fspiopDest = headers[ENUM.Http.Headers.FSPIOP.DESTINATION] try { // look up the callback base url @@ -421,9 +422,9 @@ class BulkQuotesModel { // JWS Signer expects headers in lowercase if (envConfig.jws && envConfig.jws.jwsSign && fromSwitchHeaders['fspiop-source'] === envConfig.jws.fspiopSourceToSign) { - formattedHeaders = generateRequestHeadersForJWS(fromSwitchHeaders, this.db.config.protocolVersions, true) + formattedHeaders = generateRequestHeadersForJWS(fromSwitchHeaders, envConfig.protocolVersions, true) } else { - formattedHeaders = generateRequestHeaders(fromSwitchHeaders, this.db.config.protocolVersions, true) + formattedHeaders = generateRequestHeaders(fromSwitchHeaders, envConfig.protocolVersions, true) } let opts = { diff --git a/src/model/fxQuotes.js b/src/model/fxQuotes.js index 593ba271..8086f21d 100644 --- a/src/model/fxQuotes.js +++ b/src/model/fxQuotes.js @@ -55,10 +55,8 @@ class FxQuotesModel { 'validateFxQuoteRequest - Metrics for fx quote model', ['success', 'queryName'] ).startTimer() - const appConfig = new Config() try { const currencies = [fxQuoteRequest.conversionTerms.sourceAmount.currency, fxQuoteRequest.conversionTerms.targetAmount.currency] - // Ensure the proxy client is connected if (this.proxyClient?.isConnected === false) await this.proxyClient.connect() // if the payee dfsp has a proxy cache entry, we do not validate the dfsp here @@ -68,10 +66,6 @@ class FxQuotesModel { return this.db.getParticipant(fspiopDestination, LOCAL_ENUM.COUNTERPARTY_FSP, currency, ENUM.Accounts.LedgerAccountType.POSITION) })) } - if (appConfig.simpleRoutingMode) { - // TODO: should we validate initiatingFsp (if not source) and counterPartyFsp (if not destination) here? - // also check proxy mapping before validing the fsp - } histTimer({ success: true, queryName: 'validateFxQuoteRequest' }) } catch (error) { histTimer({ success: false, queryName: 'validateFxQuoteRequest' }) @@ -138,7 +132,7 @@ class FxQuotesModel { } const fullCallbackUrl = `${endpoint}${ENUM.EndPoints.FspEndpointTemplates.FX_QUOTES_POST}` - const newHeaders = generateRequestHeaders(headers, this.db.config.protocolVersions) + const newHeaders = generateRequestHeaders(headers, this.envConfig.protocolVersions) // TODO ??? this.writeLog(`Forwarding fx quote request to endpoint: ${fullCallbackUrl}`) this.writeLog(`Forwarding fx quote request headers: ${JSON.stringify(newHeaders)}`) @@ -229,7 +223,7 @@ class FxQuotesModel { // we need to strip off the 'accept' header // for all PUT requests as per the API Specification Document // https://github.com/mojaloop/mojaloop-specification/blob/main/documents/v1.1-document-set/fspiop-v1.1-openapi2.yaml - const newHeaders = generateRequestHeaders(headers, this.db.config.protocolVersions, true) + const newHeaders = generateRequestHeaders(headers, this.envConfig.protocolVersions, true) this.writeLog(`Forwarding fx quote response to endpoint: ${fullCallbackUrl}`) this.writeLog(`Forwarding fx quote response headers: ${JSON.stringify(newHeaders)}`) @@ -308,7 +302,7 @@ class FxQuotesModel { } const fullCallbackUrl = `${endpoint}/fxQuotes/${conversionRequestId}` - const newHeaders = generateRequestHeaders(headers, this.db.config.protocolVersions) + const newHeaders = generateRequestHeaders(headers, this.envConfig.protocolVersions) this.writeLog(`Forwarding fx quote get request to endpoint: ${fullCallbackUrl}`) @@ -441,8 +435,8 @@ class FxQuotesModel { // JWS Signer expects headers in lowercase const formattedHeaders = envConfig.jws?.jwsSign && fromSwitchHeaders['fspiop-source'] === envConfig.jws.fspiopSourceToSign - ? generateRequestHeadersForJWS(fromSwitchHeaders, this.db.config.protocolVersions, true) - : generateRequestHeaders(fromSwitchHeaders, this.db.config.protocolVersions, true) + ? generateRequestHeadersForJWS(fromSwitchHeaders, this.envConfig.protocolVersions, true) + : generateRequestHeaders(fromSwitchHeaders, this.envConfig.protocolVersions, true) let opts = { method: ENUM.Http.RestMethods.PUT, diff --git a/src/model/quotes.js b/src/model/quotes.js index 17c5ddc8..3844bf3a 100644 --- a/src/model/quotes.js +++ b/src/model/quotes.js @@ -66,6 +66,7 @@ class QuotesModel { this.db = deps.db this.requestId = deps.requestId this.proxyClient = deps.proxyClient + this.envConfig = deps.config || new Config() } async executeRules (headers, quoteRequest, payer, payee) { @@ -151,7 +152,7 @@ class QuotesModel { 'validateQuoteRequest - Metrics for quote model', ['success', 'queryName', 'duplicateResult'] ).startTimer() - const envConfig = new Config() + const { envConfig } = this // note that the framework should validate the form of the request // here we can do some hard-coded rule validations to ensure requests // do not lead to unsupported scenarios or use-cases. @@ -233,7 +234,7 @@ class QuotesModel { 'handleQuoteRequest - Metrics for quote model', ['success', 'queryName', 'duplicateResult'] ).startTimer() - const envConfig = new Config() + const { envConfig } = this // accumulate enum ids const refs = {} @@ -474,7 +475,7 @@ class QuotesModel { } const fullCallbackUrl = `${endpoint}/quotes` - const newHeaders = generateRequestHeaders(headers, this.db.config.protocolVersions, false, additionalHeaders) + const newHeaders = generateRequestHeaders(headers, this.envConfig.protocolVersions, false, additionalHeaders) this.writeLog(`Forwarding quote request to endpoint: ${fullCallbackUrl}`) this.writeLog(`Forwarding quote request headers: ${JSON.stringify(newHeaders)}`) @@ -562,7 +563,7 @@ class QuotesModel { let txn = null let payeeParty = null const fspiopSource = headers[ENUM.Http.Headers.FSPIOP.SOURCE] - const envConfig = new Config() + const { envConfig } = this const handleQuoteUpdateSpan = span.getChild('qs_quote_handleQuoteUpdate') try { // ensure no 'accept' header is present in the request headers. @@ -740,7 +741,7 @@ class QuotesModel { // we need to strip off the 'accept' header // for all PUT requests as per the API Specification Document // https://github.com/mojaloop/mojaloop-specification/blob/main/documents/v1.1-document-set/fspiop-v1.1-openapi2.yaml - const newHeaders = generateRequestHeaders(headers, this.db.config.protocolVersions, true) + const newHeaders = generateRequestHeaders(headers, this.envConfig.protocolVersions, true) this.writeLog(`Forwarding quote response to endpoint: ${fullCallbackUrl}`) this.writeLog(`Forwarding quote response headers: ${JSON.stringify(newHeaders)}`) @@ -824,7 +825,7 @@ class QuotesModel { ['success', 'queryName', 'duplicateResult'] ).startTimer() let txn = null - const envConfig = new Config() + const { envConfig } = this let newError const childSpan = span.getChild('qs_quote_handleQuoteError') try { @@ -940,7 +941,7 @@ class QuotesModel { } const fullCallbackUrl = `${endpoint}/quotes/${quoteId}` - const newHeaders = generateRequestHeaders(headers, this.db.config.protocolVersions) + const newHeaders = generateRequestHeaders(headers, this.envConfig.protocolVersions) this.writeLog(`Forwarding quote get request to endpoint: ${fullCallbackUrl}`) @@ -1008,7 +1009,7 @@ class QuotesModel { 'sendErrorCallback - Metrics for quote model', ['success', 'queryName', 'duplicateResult'] ).startTimer() - const envConfig = new Config() + const { envConfig } = this const fspiopDest = headers[ENUM.Http.Headers.FSPIOP.DESTINATION] try { // look up the callback base url @@ -1051,9 +1052,9 @@ class QuotesModel { // JWS Signer expects headers in lowercase if (envConfig.jws && envConfig.jws.jwsSign && fromSwitchHeaders['fspiop-source'] === envConfig.jws.fspiopSourceToSign) { - formattedHeaders = generateRequestHeadersForJWS(fromSwitchHeaders, this.db.config.protocolVersions, true) + formattedHeaders = generateRequestHeadersForJWS(fromSwitchHeaders, envConfig.protocolVersions, true) } else { - formattedHeaders = generateRequestHeaders(fromSwitchHeaders, this.db.config.protocolVersions, true) + formattedHeaders = generateRequestHeaders(fromSwitchHeaders, envConfig.protocolVersions, true) } let opts = { diff --git a/test/unit/mocks.js b/test/unit/mocks.js index ee47983a..86a7d4c5 100644 --- a/test/unit/mocks.js +++ b/test/unit/mocks.js @@ -36,7 +36,74 @@ const createMockHapiHandler = () => { return { handler, code } } +const fxQuoteMocks = { + fxQuoteRequest: ({ conversionRequestId = randomUUID() }) => ({ + conversionRequestId, + conversionTerms: { + conversionId: randomUUID(), + determiningTransferId: randomUUID(), + initiatingFsp: 'mockInitiator', + counterPartyFsp: 'mockCounterParty', + amountType: 'SEND', + sourceAmount: { + currency: 'ZMW', + amount: '100' + }, + targetAmount: { + currency: 'TZS', + amount: '10395' + }, + expiration: new Date(Date.now() + 10_000).toISOString(), + charges: [ + { + chargeType: 'TRANSACTION FEE', + sourceAmount: { + currency: 'ZMW', + amount: '1' + }, + targetAmount: { + currency: 'TZS', + amount: '103' + } + } + ], + extensionList: { + extension: [ + { + key: 'key1', + value: 'value1' + } + ] + } + } + }), + headers: () => ({ + Accept: 'application/vnd.interoperability.fxquotes+json;version=1.0', + 'Content-Type': 'application/vnd.interoperability.fxquotes+json;version=1.0', + 'Content-Length': '100', + Date: new Date().toISOString(), + 'fspiop-source': 'mockSource', + 'fspiop-destination': 'mockDestination' + }), + span: jest.fn(), + source: 'mockSource', + destination: 'mockcDestination', + initiatingFsp: 'mockInitiator', + counterPartyFsp: 'mockcCounterParty', + conversionRequestId: randomUUID(), + error: () => ({ + code: 2001, + message: 'Generic server error' + }), + httpRequestOptions: () => ({ + }), + db: () => ({ + getParticipant: jest.fn().mockResolvedValue({}) + }) +} + module.exports = { mockHttpRequest, - createMockHapiHandler + createMockHapiHandler, + fxQuoteMocks } diff --git a/test/unit/model/fxQuotes.test.js b/test/unit/model/fxQuotes.test.js index 94156a33..3f448800 100644 --- a/test/unit/model/fxQuotes.test.js +++ b/test/unit/model/fxQuotes.test.js @@ -2,6 +2,7 @@ const { randomUUID } = require('node:crypto') const ErrorHandler = require('@mojaloop/central-services-error-handling') const FxQuotesModel = require('../../../src/model/fxQuotes') const Config = require('../../../src/lib/config') +const { fxQuoteMocks } = require('../mocks') const config = new Config() @@ -21,6 +22,33 @@ describe('FxQuotesModel Tests -->', () => { jest.restoreAllMocks() }) + describe('validateFxQuoteRequest', () => { + test('should return true if the request is valid', async () => { + const request = fxQuoteMocks.fxQuoteRequest() + const destination = fxQuoteMocks.destination + const result = await fxQuotesModel.validateFxQuoteRequest(destination, request) + expect(result).toBe(true) + }) + + test('should throw an error if the request is invalid', async () => { + const request = { + quoteId: 'quoteId', + amount: { + currency: 'USD', + amount: '100' + }, + payee: { + partyIdInfo: { + partyIdType: 'MSISDN', + partyIdentifier: '1234567890', + fspId: 'fspId' + } + } + } + await expect(fxQuotesModel.validateFxQuoteRequest(request)).rejects.toThrow() + }) + }) + describe('sendErrorCallback method Tests', () => { test('should send errorCallback with flag modifyHeaders === true [CSI-414]', async () => { const destEndpoint = `https://some.endpoint/${Date.now()}` From eb25f719d2c0d6a54d510100b2eff3a8b12911dc Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 6 Aug 2024 16:15:36 +0100 Subject: [PATCH 04/28] test: disable in-progress test --- package.json | 6 +++--- scripts/env.sh | 15 +++++++++++++++ test/integration/scripts/start.sh | 2 +- test/unit/model/fxQuotes.test.js | 2 +- 4 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 scripts/env.sh diff --git a/package.json b/package.json index 82be5c5c..ba27289c 100644 --- a/package.json +++ b/package.json @@ -67,10 +67,10 @@ "run": "docker run -p 3002:3002 --rm --link db:mysql quoting-service:local", "docker:build": "docker build --build-arg NODE_VERSION=\"$(cat .nvmrc)-alpine\" -t mojaloop/quoting-service:local -f ./Dockerfile .", "docker:up": ". ./scripts/env.sh && docker-compose up -d", - "docker:stop": "docker-compose stop", - "docker:rm": "docker-compose rm -f -v", + "docker:stop": ". ./scripts/env.sh && docker-compose stop", + "docker:rm": ". ./scripts/env.sh && docker-compose rm -f -v", "docker:down": ". ./scripts/env.sh && docker-compose down -v", - "docker:clean": "docker-compose down --rmi local", + "docker:clean": ". ./scripts/env.sh && docker-compose down --rmi local", "generate-docs": "npx jsdoc -c jsdoc.json", "audit:fix": "npm audit fix", "audit:check": "npx audit-ci --config ./audit-ci.jsonc", diff --git a/scripts/env.sh b/scripts/env.sh new file mode 100644 index 00000000..d3e0da0e --- /dev/null +++ b/scripts/env.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# Retrieve the external IP address of the host machine (on macOS) +# or the IP address of the docker0 interface (on Linux) +get_external_ip() { + if [ "$(uname)" = "Linux" ]; then + echo "$(ip addr show docker0 | grep 'inet ' | awk '{print $2}' | cut -d/ -f1)" + else + # Need to find a way to support Windows here + echo "$(route get ifconfig.me | grep interface | sed -e 's/.*: //' | xargs ipconfig getifaddr)" + fi +} + +# set/override dynamic variables +export REDIS_CLUSTER_ANNOUNCE_IP=$(get_external_ip) diff --git a/test/integration/scripts/start.sh b/test/integration/scripts/start.sh index 97e93888..bb5932ba 100755 --- a/test/integration/scripts/start.sh +++ b/test/integration/scripts/start.sh @@ -7,7 +7,7 @@ then fi echo "Starting docker-compose..." -docker-compose up -d +npm run docker:up echo "Services started. Checking status..." docker-compose ps diff --git a/test/unit/model/fxQuotes.test.js b/test/unit/model/fxQuotes.test.js index 3f448800..eab8f698 100644 --- a/test/unit/model/fxQuotes.test.js +++ b/test/unit/model/fxQuotes.test.js @@ -23,7 +23,7 @@ describe('FxQuotesModel Tests -->', () => { }) describe('validateFxQuoteRequest', () => { - test('should return true if the request is valid', async () => { + test.skip('should return true if the request is valid', async () => { const request = fxQuoteMocks.fxQuoteRequest() const destination = fxQuoteMocks.destination const result = await fxQuotesModel.validateFxQuoteRequest(destination, request) From 0e64a873c0f499f3dd40750971b090ad4476ed2a Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 7 Aug 2024 09:42:02 +0100 Subject: [PATCH 05/28] ci: fix redis-cluster config --- package.json | 8 ++++---- test/integration/scripts/env.sh | 2 ++ test/integration/scripts/start.sh | 12 ++++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index ba27289c..6dd392af 100644 --- a/package.json +++ b/package.json @@ -67,10 +67,10 @@ "run": "docker run -p 3002:3002 --rm --link db:mysql quoting-service:local", "docker:build": "docker build --build-arg NODE_VERSION=\"$(cat .nvmrc)-alpine\" -t mojaloop/quoting-service:local -f ./Dockerfile .", "docker:up": ". ./scripts/env.sh && docker-compose up -d", - "docker:stop": ". ./scripts/env.sh && docker-compose stop", - "docker:rm": ". ./scripts/env.sh && docker-compose rm -f -v", - "docker:down": ". ./scripts/env.sh && docker-compose down -v", - "docker:clean": ". ./scripts/env.sh && docker-compose down --rmi local", + "docker:stop": "docker-compose stop", + "docker:rm": "docker-compose rm -f -v", + "docker:down": "docker-compose down -v", + "docker:clean": "docker-compose down --rmi local", "generate-docs": "npx jsdoc -c jsdoc.json", "audit:fix": "npm audit fix", "audit:check": "npx audit-ci --config ./audit-ci.jsonc", diff --git a/test/integration/scripts/env.sh b/test/integration/scripts/env.sh index e654df22..fc9a0123 100755 --- a/test/integration/scripts/env.sh +++ b/test/integration/scripts/env.sh @@ -16,3 +16,5 @@ export CENTRAL_LEDGER_ADMIN_PORT=3001 export CENTRAL_LEDGER_ADMIN_BASE=/ export MIGRATION_TIMEOUT=60 + +source $BASE_DIR/env.sh diff --git a/test/integration/scripts/start.sh b/test/integration/scripts/start.sh index bb5932ba..699880c3 100755 --- a/test/integration/scripts/start.sh +++ b/test/integration/scripts/start.sh @@ -6,18 +6,18 @@ then exit 1 fi -echo "Starting docker-compose..." -npm run docker:up - -echo "Services started. Checking status..." -docker-compose ps - pwd SCRIPTS_FOLDER=./test/integration/scripts echo "Loading env vars..." source $SCRIPTS_FOLDER/env.sh +echo "Starting docker-compose..." +docker-compose up -d + +echo "Services started. Checking status..." +docker-compose ps + echo "Waiting central-leger migrations for $MIGRATION_TIMEOUT sec..." sleep $MIGRATION_TIMEOUT From 0cb5c62e6c96fcd496afb8585da911f0d822a369 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 7 Aug 2024 09:58:22 +0100 Subject: [PATCH 06/28] ci: update ci script --- test/integration/scripts/env.sh | 6 ++++-- test/integration/scripts/start.sh | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/test/integration/scripts/env.sh b/test/integration/scripts/env.sh index fc9a0123..5ed56eed 100755 --- a/test/integration/scripts/env.sh +++ b/test/integration/scripts/env.sh @@ -1,7 +1,9 @@ #!/bin/bash +pwd BASE_DIR=$(dirname "$0") -DEFAULT_CONFIG_FILE="$BASE_DIR/../../../docker/central-ledger/default.json" +# DEFAULT_CONFIG_FILE="$BASE_DIR/../../../docker/central-ledger/default.json" +DEFAULT_CONFIG_FILE="./docker/central-ledger/default.json" export HUB_NAME=$(cat "$DEFAULT_CONFIG_FILE" | jq -r '.HUB_PARTICIPANT.NAME') @@ -17,4 +19,4 @@ export CENTRAL_LEDGER_ADMIN_BASE=/ export MIGRATION_TIMEOUT=60 -source $BASE_DIR/env.sh +source ./scripts/env.sh diff --git a/test/integration/scripts/start.sh b/test/integration/scripts/start.sh index 699880c3..bf69f3a7 100755 --- a/test/integration/scripts/start.sh +++ b/test/integration/scripts/start.sh @@ -10,6 +10,7 @@ pwd SCRIPTS_FOLDER=./test/integration/scripts echo "Loading env vars..." +chmod +x $SCRIPTS_FOLDER/env.sh source $SCRIPTS_FOLDER/env.sh echo "Starting docker-compose..." From f6e6eac735abbce2c61ea5aa5290a208eba3de44 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 7 Aug 2024 10:20:11 +0100 Subject: [PATCH 07/28] ci: update env scripts --- scripts/env.sh | 3 --- test/integration/scripts/env.sh | 3 --- 2 files changed, 6 deletions(-) diff --git a/scripts/env.sh b/scripts/env.sh index d3e0da0e..e9aed2bf 100644 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -1,7 +1,5 @@ #!/bin/sh -# Retrieve the external IP address of the host machine (on macOS) -# or the IP address of the docker0 interface (on Linux) get_external_ip() { if [ "$(uname)" = "Linux" ]; then echo "$(ip addr show docker0 | grep 'inet ' | awk '{print $2}' | cut -d/ -f1)" @@ -11,5 +9,4 @@ get_external_ip() { fi } -# set/override dynamic variables export REDIS_CLUSTER_ANNOUNCE_IP=$(get_external_ip) diff --git a/test/integration/scripts/env.sh b/test/integration/scripts/env.sh index 5ed56eed..435c7f3b 100755 --- a/test/integration/scripts/env.sh +++ b/test/integration/scripts/env.sh @@ -1,8 +1,5 @@ #!/bin/bash -pwd -BASE_DIR=$(dirname "$0") -# DEFAULT_CONFIG_FILE="$BASE_DIR/../../../docker/central-ledger/default.json" DEFAULT_CONFIG_FILE="./docker/central-ledger/default.json" export HUB_NAME=$(cat "$DEFAULT_CONFIG_FILE" | jq -r '.HUB_PARTICIPANT.NAME') From 9430eec8be4cd7e23f125a602fc3a6c28f2ce124 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 7 Aug 2024 14:05:47 +0100 Subject: [PATCH 08/28] test: add unit tests for fxQuotes model --- .circleci/config.yml | 20 ----- src/model/fxQuotes.js | 2 +- test/unit/mocks.js | 37 +++++++-- test/unit/model/fxQuotes.test.js | 134 ++++++++++++++++++++++++++----- 4 files changed, 146 insertions(+), 47 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 424e93e1..442728be 100755 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -281,19 +281,6 @@ jobs: - run: name: Create dir for test results command: mkdir -p ./test/results -# - run: -# name: Build and start the docker containers -# command: | -# ## This is not needed as we are only doing narrow-integration tests. -# # docker-compose build -# ## Lets pull only the Services needed for the Integration Test -# docker-compose pull mysql kafka init-kafka -# ## Lets startup only the Services needed for the Integration Test -# docker-compose up -d mysql kafka init-kafka -# ## Check straight away to see if any containers have exited -# docker-compose ps -# ## wait for services to be up and running -# npm run wait-4-docker - run: name: Prepare test environment command: | @@ -305,13 +292,6 @@ jobs: command: | npm rebuild npm run test:int -# environment: -# ENDPOINT_URL: http://localhost:4545/notification -# - store_artifacts: -# path: ./test/results -# destination: test -# - store_test_results: -# path: ./test/results vulnerability-check: executor: default-docker diff --git a/src/model/fxQuotes.js b/src/model/fxQuotes.js index 8086f21d..432bb8bd 100644 --- a/src/model/fxQuotes.js +++ b/src/model/fxQuotes.js @@ -132,7 +132,7 @@ class FxQuotesModel { } const fullCallbackUrl = `${endpoint}${ENUM.EndPoints.FspEndpointTemplates.FX_QUOTES_POST}` - const newHeaders = generateRequestHeaders(headers, this.envConfig.protocolVersions) // TODO ??? + const newHeaders = generateRequestHeaders(headers, this.envConfig.protocolVersions) this.writeLog(`Forwarding fx quote request to endpoint: ${fullCallbackUrl}`) this.writeLog(`Forwarding fx quote request headers: ${JSON.stringify(newHeaders)}`) diff --git a/test/unit/mocks.js b/test/unit/mocks.js index 86a7d4c5..b708b287 100644 --- a/test/unit/mocks.js +++ b/test/unit/mocks.js @@ -36,8 +36,16 @@ const createMockHapiHandler = () => { return { handler, code } } +const mockSpan = () => ({ + setTags: jest.fn(), + audit: jest.fn(), + finish: jest.fn(), + getChild: jest.fn(), + injectContextToHttpRequest: jest.fn().mockImplementation(param => param) +}) + const fxQuoteMocks = { - fxQuoteRequest: ({ conversionRequestId = randomUUID() }) => ({ + fxQuoteRequest: ({ conversionRequestId = randomUUID() } = {}) => ({ conversionRequestId, conversionTerms: { conversionId: randomUUID(), @@ -81,11 +89,13 @@ const fxQuoteMocks = { Accept: 'application/vnd.interoperability.fxquotes+json;version=1.0', 'Content-Type': 'application/vnd.interoperability.fxquotes+json;version=1.0', 'Content-Length': '100', - Date: new Date().toISOString(), + date: new Date().toISOString(), 'fspiop-source': 'mockSource', 'fspiop-destination': 'mockDestination' }), - span: jest.fn(), + span: () => ({ + getChild: jest.fn().mockReturnValue(mockSpan()) + }), source: 'mockSource', destination: 'mockcDestination', initiatingFsp: 'mockInitiator', @@ -97,13 +107,28 @@ const fxQuoteMocks = { }), httpRequestOptions: () => ({ }), - db: () => ({ - getParticipant: jest.fn().mockResolvedValue({}) + db: ({ getParticipant = jest.fn().mockResolvedValue({}) } = {}) => ({ + getParticipant + }), + proxyClient: ({ + isConnected = jest.fn().mockReturnValue(true), + connect = jest.fn().mockResolvedValue(true), + lookupProxyByDfspId = jest.fn().mockResolvedValue('mockProxy') + } = {}) => ({ + isConnected, + connect, + lookupProxyByDfspId + }), + logger: () => ({ + error: jest.fn(), + info: jest.fn(), + debug: jest.fn() }) } module.exports = { mockHttpRequest, createMockHapiHandler, - fxQuoteMocks + fxQuoteMocks, + mockSpan } diff --git a/test/unit/model/fxQuotes.test.js b/test/unit/model/fxQuotes.test.js index eab8f698..2325f3b3 100644 --- a/test/unit/model/fxQuotes.test.js +++ b/test/unit/model/fxQuotes.test.js @@ -1,7 +1,11 @@ const { randomUUID } = require('node:crypto') const ErrorHandler = require('@mojaloop/central-services-error-handling') +const ENUM = require('@mojaloop/central-services-shared').Enum +const LOCAL_ENUM = require('../../../src/lib/enum') const FxQuotesModel = require('../../../src/model/fxQuotes') const Config = require('../../../src/lib/config') +jest.mock('../../../src/lib/http') +const LibHttp = require('../../../src/lib/http') const { fxQuoteMocks } = require('../mocks') const config = new Config() @@ -11,11 +15,13 @@ describe('FxQuotesModel Tests -->', () => { let db let requestId let proxyClient + let log beforeEach(() => { - db = { config } // add needed functionality + db = fxQuoteMocks.db() + proxyClient = fxQuoteMocks.proxyClient() + log = fxQuoteMocks.logger() requestId = randomUUID() - fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient }) }) afterEach(() => { @@ -23,29 +29,117 @@ describe('FxQuotesModel Tests -->', () => { }) describe('validateFxQuoteRequest', () => { - test.skip('should return true if the request is valid', async () => { + test('should not function correctly with proxy cache disabled', async () => { + const destination = fxQuoteMocks.destination const request = fxQuoteMocks.fxQuoteRequest() + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient: undefined, log }) + + await expect(fxQuotesModel.validateFxQuoteRequest(destination, request)).resolves.toBeUndefined() + + expect(db.getParticipant).toBeCalledTimes(2) + expect(db.getParticipant).toHaveBeenNthCalledWith(1, destination, LOCAL_ENUM.COUNTERPARTY_FSP, 'ZMW', ENUM.Accounts.LedgerAccountType.POSITION) + expect(db.getParticipant).toHaveBeenNthCalledWith(2, destination, LOCAL_ENUM.COUNTERPARTY_FSP, 'TZS', ENUM.Accounts.LedgerAccountType.POSITION) + }) + + test('should not validate participant if proxy cache returns a proxy', async () => { const destination = fxQuoteMocks.destination - const result = await fxQuotesModel.validateFxQuoteRequest(destination, request) - expect(result).toBe(true) + const request = fxQuoteMocks.fxQuoteRequest() + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + + await expect(fxQuotesModel.validateFxQuoteRequest(destination, request)).resolves.toBeUndefined() + + expect(proxyClient.lookupProxyByDfspId).toBeCalledTimes(1) + expect(db.getParticipant).not.toHaveBeenCalled() + }) + + test('should validate participant if proxy cache returns no proxy', async () => { + const destination = fxQuoteMocks.destination + const request = fxQuoteMocks.fxQuoteRequest() + proxyClient.lookupProxyByDfspId = jest.fn().mockResolvedValue(undefined) + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + + await expect(fxQuotesModel.validateFxQuoteRequest(destination, request)).resolves.toBeUndefined() + + expect(proxyClient.lookupProxyByDfspId).toBeCalledTimes(1) + expect(db.getParticipant).toBeCalledTimes(2) + }) + + test('should throw error if participant validation fails', async () => { + const destination = fxQuoteMocks.destination + const request = fxQuoteMocks.fxQuoteRequest() + proxyClient.lookupProxyByDfspId = jest.fn().mockResolvedValue(undefined) + db.getParticipant = jest.fn().mockRejectedValue(new Error('DB Error')) + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + + await expect(fxQuotesModel.validateFxQuoteRequest(destination, request)).rejects.toThrow() + + expect(proxyClient.lookupProxyByDfspId).toBeCalledTimes(1) + expect(db.getParticipant).toBeCalledTimes(2) + }) + }) + + describe('handleFxQuoteRequest', () => { + test('should handle fx quote request', async () => { + const headers = fxQuoteMocks.headers() + const request = fxQuoteMocks.fxQuoteRequest() + const span = fxQuoteMocks.span() + + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + jest.spyOn(fxQuotesModel, 'forwardFxQuoteRequest').mockResolvedValue() + jest.spyOn(fxQuotesModel, 'validateFxQuoteRequest') + + await expect(fxQuotesModel.handleFxQuoteRequest(headers, request, span)).resolves.toBeUndefined() + + expect(fxQuotesModel.validateFxQuoteRequest).toBeCalledWith(headers['fspiop-destination'], request) + expect(fxQuotesModel.forwardFxQuoteRequest).toBeCalledWith(headers, request.conversionRequestId, request, span.getChild()) }) - test('should throw an error if the request is invalid', async () => { - const request = { - quoteId: 'quoteId', - amount: { - currency: 'USD', - amount: '100' - }, - payee: { - partyIdInfo: { - partyIdType: 'MSISDN', - partyIdentifier: '1234567890', - fspId: 'fspId' - } - } + test('should handle error thrown', async () => { + const headers = fxQuoteMocks.headers() + const request = fxQuoteMocks.fxQuoteRequest() + const span = fxQuoteMocks.span() + + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + jest.spyOn(fxQuotesModel, 'forwardFxQuoteRequest').mockRejectedValue(new Error('Forward Error')) + jest.spyOn(fxQuotesModel, 'validateFxQuoteRequest') + jest.spyOn(fxQuotesModel, 'handleException').mockResolvedValue() + + await expect(fxQuotesModel.handleFxQuoteRequest(headers, request, span)).resolves.toBeUndefined() + + expect(fxQuotesModel.validateFxQuoteRequest).toBeCalledWith(headers['fspiop-destination'], request) + expect(fxQuotesModel.forwardFxQuoteRequest).toBeCalledWith(headers, request.conversionRequestId, request, span.getChild()) + expect(fxQuotesModel.handleException).toBeCalledWith(headers['fspiop-source'], request.conversionRequestId, expect.any(Error), headers, span.getChild()) + expect(span.getChild().finish).toBeCalledTimes(1) + }) + }) + + describe('forwardFxQuoteRequest', () => { + test('should forward fx quote request, participant not proxy mapped', async () => { + const headers = fxQuoteMocks.headers() + const request = fxQuoteMocks.fxQuoteRequest() + const conversionRequestId = request.conversionRequestId + const span = fxQuoteMocks.span().getChild() + const mockEndpoint = 'https://some.endpoint' + + jest.spyOn(LibHttp, 'httpRequest').mockResolvedValue({ status: 200 }) + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + fxQuotesModel._getParticipantEndpoint = jest.fn().mockResolvedValue(mockEndpoint) + + await expect(fxQuotesModel.forwardFxQuoteRequest(headers, conversionRequestId, request, span)).resolves.toBeUndefined() + + const expectedHeaders = { + Accept: headers.Accept, + 'Content-Type': headers['Content-Type'], + 'FSPIOP-Source': headers['fspiop-source'], + 'FSPIOP-Destination': headers['fspiop-destination'], + Date: headers.date } - await expect(fxQuotesModel.validateFxQuoteRequest(request)).rejects.toThrow() + expect(LibHttp.httpRequest).toHaveBeenCalledWith({ + headers: expectedHeaders, + method: ENUM.Http.RestMethods.POST, + url: `${mockEndpoint}${ENUM.EndPoints.FspEndpointTemplates.FX_QUOTES_POST}`, + data: JSON.stringify(request) + }, headers['fspiop-source']) }) }) From 93db5d48266845cbbf3504377e7a4f4e1bf22ba6 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 8 Aug 2024 09:37:44 +0100 Subject: [PATCH 09/28] refactor: dedup tests --- package-lock.json | 16 ++-- package.json | 4 +- src/model/fxQuotes.js | 9 +- test/unit/mocks.js | 23 +++-- test/unit/model/fxQuotes.test.js | 139 +++++++++++++++++++++++++------ 5 files changed, 144 insertions(+), 47 deletions(-) diff --git a/package-lock.json b/package-lock.json index e9343523..42c5d5b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,10 +46,10 @@ "audit-ci": "^7.1.0", "eslint": "8.16.0", "eslint-config-standard": "17.1.0", - "eslint-plugin-jest": "28.7.0", + "eslint-plugin-jest": "28.8.0", "jest": "29.7.0", "jest-junit": "16.0.0", - "npm-check-updates": "17.0.3", + "npm-check-updates": "17.0.5", "nyc": "17.0.0", "pre-commit": "1.2.2", "proxyquire": "2.1.3", @@ -5426,9 +5426,9 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "28.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.7.0.tgz", - "integrity": "sha512-fzPGN7awL2ftVRQh/bsCi+16ArUZWujZnD1b8EGJqy8nr4//7tZ3BIdc/9edcJBtB3hpci3GtdMNFVDwHU0Eag==", + "version": "28.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.8.0.tgz", + "integrity": "sha512-Tubj1hooFxCl52G4qQu0edzV/+EZzPUeN8p2NnW5uu4fbDs+Yo7+qDVDc4/oG3FbCqEBmu/OC3LSsyiU22oghw==", "dev": true, "dependencies": { "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -10053,9 +10053,9 @@ } }, "node_modules/npm-check-updates": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-17.0.3.tgz", - "integrity": "sha512-3UWnsnijmx4u9GnICHVCChz6JnhVLmYWqazoedWjLSY6hZB/QhMCps07vBbDmjWnHMhpl6YseAtFlvGbUq9Yrw==", + "version": "17.0.5", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-17.0.5.tgz", + "integrity": "sha512-PfJ7fJDRelNwDWfUYq7b7EBh56xLvxcBOEvYWe6aQbjV0i+fg7LSIdkcVjb6lIxlxvQqlaAX6Lsln69XadL22Q==", "dev": true, "bin": { "ncu": "build/cli.js", diff --git a/package.json b/package.json index 6dd392af..afe64f2b 100644 --- a/package.json +++ b/package.json @@ -138,10 +138,10 @@ "audit-ci": "^7.1.0", "eslint": "8.16.0", "eslint-config-standard": "17.1.0", - "eslint-plugin-jest": "28.7.0", + "eslint-plugin-jest": "28.8.0", "jest": "29.7.0", "jest-junit": "16.0.0", - "npm-check-updates": "17.0.3", + "npm-check-updates": "17.0.5", "nyc": "17.0.0", "pre-commit": "1.2.2", "proxyquire": "2.1.3", diff --git a/src/model/fxQuotes.js b/src/model/fxQuotes.js index 432bb8bd..fd171095 100644 --- a/src/model/fxQuotes.js +++ b/src/model/fxQuotes.js @@ -172,14 +172,13 @@ class FxQuotesModel { ['success', 'queryName'] ).startTimer() - if ('accept' in headers) { - throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, - `Update for fx quote ${conversionRequestId} failed: "accept" header should not be sent in callbacks.`, null, headers['fspiop-source']) - } - const childSpan = span.getChild('qs_quote_forwardFxQuoteUpdate') try { await childSpan.audit({ headers, params: { conversionRequestId }, payload: fxQuoteUpdateRequest }, EventSdk.AuditEventAction.start) + if ('accept' in headers) { + throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, + `Update for fx quote ${conversionRequestId} failed: "accept" header should not be sent in callbacks.`, null, headers['fspiop-source']) + } await this.forwardFxQuoteUpdate(headers, conversionRequestId, fxQuoteUpdateRequest, childSpan) histTimer({ success: true, queryName: 'handleFxQuoteUpdate' }) } catch (err) { diff --git a/test/unit/mocks.js b/test/unit/mocks.js index b708b287..3f12350c 100644 --- a/test/unit/mocks.js +++ b/test/unit/mocks.js @@ -85,10 +85,17 @@ const fxQuoteMocks = { } } }), + fxQuoteUpdateRequest: ({ + condition = randomUUID(), + conversionTerms = fxQuoteMocks.fxQuoteRequest().conversionTerms + } = {}) => ({ + condition, + conversionTerms + }), headers: () => ({ - Accept: 'application/vnd.interoperability.fxquotes+json;version=1.0', - 'Content-Type': 'application/vnd.interoperability.fxquotes+json;version=1.0', - 'Content-Length': '100', + accept: 'application/vnd.interoperability.fxquotes+json;version=1.0', + 'content-type': 'application/vnd.interoperability.fxquotes+json;version=1.0', + 'content-length': '100', date: new Date().toISOString(), 'fspiop-source': 'mockSource', 'fspiop-destination': 'mockDestination' @@ -97,7 +104,7 @@ const fxQuoteMocks = { getChild: jest.fn().mockReturnValue(mockSpan()) }), source: 'mockSource', - destination: 'mockcDestination', + destination: 'mockDestination', initiatingFsp: 'mockInitiator', counterPartyFsp: 'mockcCounterParty', conversionRequestId: randomUUID(), @@ -107,8 +114,12 @@ const fxQuoteMocks = { }), httpRequestOptions: () => ({ }), - db: ({ getParticipant = jest.fn().mockResolvedValue({}) } = {}) => ({ - getParticipant + db: ({ + getParticipant = jest.fn().mockResolvedValue({}), + getParticipantEndpoint = jest.fn().mockResolvedValue(undefined) + } = {}) => ({ + getParticipant, + getParticipantEndpoint }), proxyClient: ({ isConnected = jest.fn().mockReturnValue(true), diff --git a/test/unit/model/fxQuotes.test.js b/test/unit/model/fxQuotes.test.js index 2325f3b3..e1e8292d 100644 --- a/test/unit/model/fxQuotes.test.js +++ b/test/unit/model/fxQuotes.test.js @@ -7,6 +7,7 @@ const Config = require('../../../src/lib/config') jest.mock('../../../src/lib/http') const LibHttp = require('../../../src/lib/http') const { fxQuoteMocks } = require('../mocks') +const { FSPIOPError } = require('@mojaloop/central-services-error-handling/src/factory') const config = new Config() @@ -17,11 +18,31 @@ describe('FxQuotesModel Tests -->', () => { let proxyClient let log + let headers + let conversionRequestId + let request + let updateRequest + let span + let childSpan + let mockEndpoint + let destination + + const endpointType = ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_FX_QUOTES + beforeEach(() => { db = fxQuoteMocks.db() proxyClient = fxQuoteMocks.proxyClient() log = fxQuoteMocks.logger() requestId = randomUUID() + + headers = fxQuoteMocks.headers() + request = fxQuoteMocks.fxQuoteRequest() + conversionRequestId = request.conversionRequestId + updateRequest = fxQuoteMocks.fxQuoteUpdateRequest() + span = fxQuoteMocks.span() + childSpan = span.getChild() + mockEndpoint = 'https://some.endpoint' + destination = fxQuoteMocks.destination }) afterEach(() => { @@ -30,8 +51,6 @@ describe('FxQuotesModel Tests -->', () => { describe('validateFxQuoteRequest', () => { test('should not function correctly with proxy cache disabled', async () => { - const destination = fxQuoteMocks.destination - const request = fxQuoteMocks.fxQuoteRequest() fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient: undefined, log }) await expect(fxQuotesModel.validateFxQuoteRequest(destination, request)).resolves.toBeUndefined() @@ -42,8 +61,6 @@ describe('FxQuotesModel Tests -->', () => { }) test('should not validate participant if proxy cache returns a proxy', async () => { - const destination = fxQuoteMocks.destination - const request = fxQuoteMocks.fxQuoteRequest() fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) await expect(fxQuotesModel.validateFxQuoteRequest(destination, request)).resolves.toBeUndefined() @@ -53,8 +70,6 @@ describe('FxQuotesModel Tests -->', () => { }) test('should validate participant if proxy cache returns no proxy', async () => { - const destination = fxQuoteMocks.destination - const request = fxQuoteMocks.fxQuoteRequest() proxyClient.lookupProxyByDfspId = jest.fn().mockResolvedValue(undefined) fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) @@ -65,8 +80,6 @@ describe('FxQuotesModel Tests -->', () => { }) test('should throw error if participant validation fails', async () => { - const destination = fxQuoteMocks.destination - const request = fxQuoteMocks.fxQuoteRequest() proxyClient.lookupProxyByDfspId = jest.fn().mockResolvedValue(undefined) db.getParticipant = jest.fn().mockRejectedValue(new Error('DB Error')) fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) @@ -80,10 +93,6 @@ describe('FxQuotesModel Tests -->', () => { describe('handleFxQuoteRequest', () => { test('should handle fx quote request', async () => { - const headers = fxQuoteMocks.headers() - const request = fxQuoteMocks.fxQuoteRequest() - const span = fxQuoteMocks.span() - fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) jest.spyOn(fxQuotesModel, 'forwardFxQuoteRequest').mockResolvedValue() jest.spyOn(fxQuotesModel, 'validateFxQuoteRequest') @@ -95,10 +104,6 @@ describe('FxQuotesModel Tests -->', () => { }) test('should handle error thrown', async () => { - const headers = fxQuoteMocks.headers() - const request = fxQuoteMocks.fxQuoteRequest() - const span = fxQuoteMocks.span() - fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) jest.spyOn(fxQuotesModel, 'forwardFxQuoteRequest').mockRejectedValue(new Error('Forward Error')) jest.spyOn(fxQuotesModel, 'validateFxQuoteRequest') @@ -114,22 +119,16 @@ describe('FxQuotesModel Tests -->', () => { }) describe('forwardFxQuoteRequest', () => { - test('should forward fx quote request, participant not proxy mapped', async () => { - const headers = fxQuoteMocks.headers() - const request = fxQuoteMocks.fxQuoteRequest() - const conversionRequestId = request.conversionRequestId - const span = fxQuoteMocks.span().getChild() - const mockEndpoint = 'https://some.endpoint' - + test('should forward fx quote request', async () => { jest.spyOn(LibHttp, 'httpRequest').mockResolvedValue({ status: 200 }) fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) fxQuotesModel._getParticipantEndpoint = jest.fn().mockResolvedValue(mockEndpoint) - await expect(fxQuotesModel.forwardFxQuoteRequest(headers, conversionRequestId, request, span)).resolves.toBeUndefined() + await expect(fxQuotesModel.forwardFxQuoteRequest(headers, conversionRequestId, request, childSpan)).resolves.toBeUndefined() const expectedHeaders = { - Accept: headers.Accept, - 'Content-Type': headers['Content-Type'], + Accept: headers.accept, + 'Content-Type': headers['content-type'], 'FSPIOP-Source': headers['fspiop-source'], 'FSPIOP-Destination': headers['fspiop-destination'], Date: headers.date @@ -141,6 +140,94 @@ describe('FxQuotesModel Tests -->', () => { data: JSON.stringify(request) }, headers['fspiop-source']) }) + + test('should forward quote request to proxy', async () => { + const mockProxyEndpoint = 'https://proxy.endpoint' + const mockProxy = 'mockProxy' + + proxyClient.lookupProxyByDfspId = jest.fn().mockResolvedValue(mockProxy) + db.getParticipantEndpoint = jest.fn().mockImplementation((fspId, _endpointType) => { + if (fspId === destination) return null + if (fspId === mockProxy) return mockProxyEndpoint + return 'https://some.other.endpoint' + }) + jest.spyOn(LibHttp, 'httpRequest').mockResolvedValue({ status: 200 }) + + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + await expect(fxQuotesModel.forwardFxQuoteRequest(headers, conversionRequestId, request, childSpan)).resolves.toBeUndefined() + + expect(LibHttp.httpRequest).toBeCalled() + expect(proxyClient.lookupProxyByDfspId).toBeCalledTimes(1) + expect(db.getParticipantEndpoint).toBeCalledTimes(2) + expect(db.getParticipantEndpoint).toHaveBeenNthCalledWith(1, destination, endpointType) + expect(db.getParticipantEndpoint).toHaveBeenNthCalledWith(2, mockProxy, endpointType) + }) + + test('should format error thrown and re-throw', async () => { + jest.spyOn(LibHttp, 'httpRequest').mockRejectedValue(new Error('HTTP Error')) + + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + fxQuotesModel._getParticipantEndpoint = jest.fn().mockResolvedValue(undefined) + + await expect(fxQuotesModel.forwardFxQuoteRequest(headers, conversionRequestId, request, childSpan)).rejects.toThrow(FSPIOPError) + }) + }) + + describe('handleFxQuoteUpdate', () => { + test('headers should not contain accept property', async () => { + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + jest.spyOn(fxQuotesModel, 'forwardFxQuoteUpdate').mockResolvedValue() + jest.spyOn(fxQuotesModel, 'handleException').mockResolvedValue() + + await expect(fxQuotesModel.handleFxQuoteUpdate(headers, conversionRequestId, updateRequest, span)).resolves.toBeUndefined() + + expect(fxQuotesModel.forwardFxQuoteUpdate).not.toBeCalled() + expect(fxQuotesModel.handleException).toBeCalledWith(headers['fspiop-source'], conversionRequestId, expect.any(Error), headers, span.getChild()) + }) + + test('should handle fx quote update', async () => { + delete headers.accept + + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + jest.spyOn(fxQuotesModel, 'forwardFxQuoteUpdate').mockResolvedValue() + jest.spyOn(fxQuotesModel, 'handleException').mockResolvedValue() + + await expect(fxQuotesModel.handleFxQuoteUpdate(headers, conversionRequestId, updateRequest, span)).resolves.toBeUndefined() + + expect(fxQuotesModel.forwardFxQuoteUpdate).toBeCalledWith(headers, conversionRequestId, updateRequest, span.getChild()) + }) + }) + + describe('forwardFxQuoteUpdate', () => { + test('should forward fx quote update', async () => { + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + + jest.spyOn(LibHttp, 'httpRequest').mockResolvedValue({ status: 200 }) + jest.spyOn(fxQuotesModel, '_getParticipantEndpoint').mockResolvedValue(mockEndpoint) + + await expect(fxQuotesModel.forwardFxQuoteUpdate(headers, conversionRequestId, updateRequest, childSpan)).resolves.toBeUndefined() + + expect(LibHttp.httpRequest).toHaveBeenCalledWith({ + headers: { + 'Content-Type': headers['content-type'], + 'FSPIOP-Source': headers['fspiop-source'], + 'FSPIOP-Destination': headers['fspiop-destination'], + Date: headers.date + }, + method: ENUM.Http.RestMethods.PUT, + url: `${mockEndpoint}/fxQuotes/${conversionRequestId}`, + data: JSON.stringify(updateRequest) + }, headers['fspiop-source']) + }) + + test('should send error callback if no endpoint found', async () => { + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + jest.spyOn(fxQuotesModel, 'sendErrorCallback').mockResolvedValue() + + await expect(fxQuotesModel.forwardFxQuoteUpdate(headers, conversionRequestId, updateRequest, childSpan)).resolves.toBeUndefined() + + expect(fxQuotesModel.sendErrorCallback).toBeCalledWith(headers['fspiop-source'], expect.any(Error), conversionRequestId, headers, childSpan, true) + }) }) describe('sendErrorCallback method Tests', () => { From 214954bb77eb76297dbf0469ac3af71e12891930 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 8 Aug 2024 14:48:09 +0100 Subject: [PATCH 10/28] test: update tests --- src/lib/constants.js | 7 + src/model/fxQuotes.js | 23 ++-- test/unit/mocks.js | 2 + test/unit/model/fxQuotes.test.js | 214 ++++++++++++++++++++++++++++--- 4 files changed, 217 insertions(+), 29 deletions(-) create mode 100644 src/lib/constants.js diff --git a/src/lib/constants.js b/src/lib/constants.js new file mode 100644 index 00000000..079bae46 --- /dev/null +++ b/src/lib/constants.js @@ -0,0 +1,7 @@ +const ERROR_MESSAGES = { + CALLBACK_UNSUCCESSFUL_HTTP_RESPONSE: 'Got non-success response sending error callback', + CALLBACK_NETWORK_ERROR: 'network error in sendErrorCallback', + NO_FX_CALLBACK_ENDPOINT: (fspiopSource, conversionRequestId) => `No FSPIOP_CALLBACK_URL_FX_QUOTES endpoint found for FSP '${fspiopSource}' while processing fxquote ${conversionRequestId}` +} + +module.exports = { ERROR_MESSAGES } diff --git a/src/model/fxQuotes.js b/src/model/fxQuotes.js index fd171095..d5ec0297 100644 --- a/src/model/fxQuotes.js +++ b/src/model/fxQuotes.js @@ -32,6 +32,7 @@ const { loggerFactory } = require('../lib') const { httpRequest } = require('../lib/http') const { getStackOrInspect, generateRequestHeadersForJWS, generateRequestHeaders, getParticipantEndpoint } = require('../lib/util') const LOCAL_ENUM = require('../lib/enum') +const { ERROR_MESSAGES } = require('../lib/constants') axios.defaults.headers.common = {} @@ -42,6 +43,7 @@ class FxQuotesModel { this.proxyClient = deps.proxyClient this.envConfig = deps.envConfig || new Config() this.log = deps.log || loggerFactory(this.constructor.name) + this.httpRequest = deps.httpRequest || httpRequest } /** @@ -128,7 +130,7 @@ class FxQuotesModel { this.writeLog(`Resolved FSPIOP_CALLBACK_URL_FX_QUOTES endpoint for fxQuote ${conversionRequestId} to: ${util.inspect(endpoint)}`) if (!endpoint) { - throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR, `No FSPIOP_CALLBACK_URL_FX_QUOTES endpoint found for FXP '${fspiopDest}' while processing fxquote ${conversionRequestId}`, null, fspiopSource) + throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR, ERROR_MESSAGES.NO_FX_CALLBACK_ENDPOINT(fspiopDest, conversionRequestId), null, fspiopSource) } const fullCallbackUrl = `${endpoint}${ENUM.EndPoints.FspEndpointTemplates.FX_QUOTES_POST}` @@ -151,7 +153,7 @@ class FxQuotesModel { } this.writeLog(`Forwarding request : ${util.inspect(opts)}`) - await httpRequest(opts, fspiopSource) + await this.httpRequest(opts, fspiopSource) histTimer({ success: true, queryName: 'forwardFxQuoteRequest' }) } catch (err) { this.writeLog(`Error forwarding fxQuote request to endpoint ${endpoint}: ${getStackOrInspect(err)}`) @@ -214,7 +216,7 @@ class FxQuotesModel { this.writeLog(`Resolved PAYER party FSPIOP_CALLBACK_URL_FX_QUOTES endpoint for fx quote ${conversionRequestId} to: ${util.inspect(endpoint)}`) if (!endpoint) { - const fspiopError = ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR, `No FSPIOP_CALLBACK_URL_FX_QUOTES endpoint found for PAYER party FSP '${fspiopDest}' while processing quote ${conversionRequestId}`, null, fspiopSource) + const fspiopError = ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR, '', null, fspiopSource) return this.sendErrorCallback(fspiopSource, fspiopError, conversionRequestId, headers, span, true) } @@ -240,7 +242,7 @@ class FxQuotesModel { span.audit(opts, EventSdk.AuditEventAction.egress) } - await httpRequest(opts, fspiopSource) + await this.httpRequest(opts, fspiopSource) histTimer({ success: true, queryName: 'forwardFxQuoteUpdate' }) } catch (err) { this.writeLog(`Error forwarding fx quote callback to endpoint ${endpoint}: ${getStackOrInspect(err)}`) @@ -297,7 +299,7 @@ class FxQuotesModel { this.writeLog(`Resolved ${fspiopDest} FSPIOP_CALLBACK_URL_FX_QUOTES endpoint for fx quote GET ${conversionRequestId} to: ${util.inspect(endpoint)}`) if (!endpoint) { - throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR, `No FSPIOP_CALLBACK_URL_FX_QUOTES endpoint found for FXP '${fspiopDest}' while processing fxquote GET ${conversionRequestId}`, null, fspiopSource) + throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR, ERROR_MESSAGES.NO_FX_CALLBACK_ENDPOINT(fspiopDest, conversionRequestId), null, fspiopSource) } const fullCallbackUrl = `${endpoint}/fxQuotes/${conversionRequestId}` @@ -316,7 +318,7 @@ class FxQuotesModel { span.audit(opts, EventSdk.AuditEventAction.egress) } - await httpRequest(opts, fspiopSource) + await this.httpRequest(opts, fspiopSource) histTimer({ success: true, queryName: 'forwardFxQuoteGet' }) } catch (err) { this.writeLog(`Error forwarding fx quote get request: ${getStackOrInspect(err)}`) @@ -339,8 +341,8 @@ class FxQuotesModel { const fspiopSource = headers[ENUM.Http.Headers.FSPIOP.SOURCE] const childSpan = span.getChild('qs_quote_forwardFxQuoteError') try { - const fspiopError = ErrorHandler.CreateFSPIOPErrorFromErrorInformation(error) await childSpan.audit({ headers, params: { conversionRequestId } }, EventSdk.AuditEventAction.start) + const fspiopError = ErrorHandler.CreateFSPIOPErrorFromErrorInformation(error) await this.sendErrorCallback(headers[ENUM.Http.Headers.FSPIOP.DESTINATION], fspiopError, conversionRequestId, headers, childSpan, false) histTimer({ success: true, queryName: 'handleFxQuoteError' }) } catch (err) { @@ -365,7 +367,6 @@ class FxQuotesModel { ['success', 'queryName'] ).startTimer() const fspiopError = ErrorHandler.ReformatFSPIOPError(error) - const childSpan = span.getChild('qs_fxQuote_sendErrorCallback') try { await childSpan.audit({ headers, params: { conversionRequestId } }, EventSdk.AuditEventAction.start) @@ -403,7 +404,7 @@ class FxQuotesModel { log.debug(`Resolved participant '${fspiopSource}' FSPIOP_CALLBACK_URL_FX_QUOTES to: '${endpoint}'`) if (!endpoint) { - throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.PARTY_NOT_FOUND, `No FSPIOP_CALLBACK_URL_FX_QUOTES endpoint found for FSP '${fspiopSource}', unable to make error callback`, null, fspiopSource) + throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.PARTY_NOT_FOUND, ERROR_MESSAGES.NO_FX_CALLBACK_ENDPOINT(fspiopSource, conversionRequestId), null, fspiopSource) } const fspiopUri = `/fxQuotes/${conversionRequestId}/error` @@ -466,7 +467,7 @@ class FxQuotesModel { res = await this.sendHttpRequest(opts) histTimer({ success: true, queryName: 'sendErrorCallback' }) } catch (err) { - throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_COMMUNICATION_ERROR, `network error in sendErrorCallback: ${err.message}`, { + throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_COMMUNICATION_ERROR, `${ERROR_MESSAGES.CALLBACK_NETWORK_ERROR}: ${err.message}`, { error: err, url: fullCallbackUrl, sourceFsp: fspiopSource, @@ -478,7 +479,7 @@ class FxQuotesModel { this.writeLog(`Error callback got response ${res.status} ${res.statusText}`) if (res.status !== ENUM.Http.ReturnCodes.OK.CODE) { - throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_COMMUNICATION_ERROR, 'Got non-success response sending error callback', { + throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_COMMUNICATION_ERROR, ERROR_MESSAGES.CALLBACK_UNSUCCESSFUL_HTTP_RESPONSE, { url: fullCallbackUrl, sourceFsp: fspiopSource, destinationFsp: fspiopDest, diff --git a/test/unit/mocks.js b/test/unit/mocks.js index 3f12350c..214b7654 100644 --- a/test/unit/mocks.js +++ b/test/unit/mocks.js @@ -41,6 +41,8 @@ const mockSpan = () => ({ audit: jest.fn(), finish: jest.fn(), getChild: jest.fn(), + error: jest.fn(), + isFinished: false, injectContextToHttpRequest: jest.fn().mockImplementation(param => param) }) diff --git a/test/unit/model/fxQuotes.test.js b/test/unit/model/fxQuotes.test.js index e1e8292d..64b245b1 100644 --- a/test/unit/model/fxQuotes.test.js +++ b/test/unit/model/fxQuotes.test.js @@ -1,13 +1,16 @@ +const fs = require('fs') +const path = require('path') +const axios = require('axios') +jest.mock('axios') const { randomUUID } = require('node:crypto') const ErrorHandler = require('@mojaloop/central-services-error-handling') const ENUM = require('@mojaloop/central-services-shared').Enum const LOCAL_ENUM = require('../../../src/lib/enum') const FxQuotesModel = require('../../../src/model/fxQuotes') const Config = require('../../../src/lib/config') -jest.mock('../../../src/lib/http') -const LibHttp = require('../../../src/lib/http') const { fxQuoteMocks } = require('../mocks') const { FSPIOPError } = require('@mojaloop/central-services-error-handling/src/factory') +const { ERROR_MESSAGES } = require('../../../src/lib/constants') const config = new Config() @@ -17,7 +20,6 @@ describe('FxQuotesModel Tests -->', () => { let requestId let proxyClient let log - let headers let conversionRequestId let request @@ -26,6 +28,7 @@ describe('FxQuotesModel Tests -->', () => { let childSpan let mockEndpoint let destination + let httpRequest const endpointType = ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_FX_QUOTES @@ -34,7 +37,6 @@ describe('FxQuotesModel Tests -->', () => { proxyClient = fxQuoteMocks.proxyClient() log = fxQuoteMocks.logger() requestId = randomUUID() - headers = fxQuoteMocks.headers() request = fxQuoteMocks.fxQuoteRequest() conversionRequestId = request.conversionRequestId @@ -43,6 +45,7 @@ describe('FxQuotesModel Tests -->', () => { childSpan = span.getChild() mockEndpoint = 'https://some.endpoint' destination = fxQuoteMocks.destination + httpRequest = jest.fn().mockResolvedValue({ status: 200 }) }) afterEach(() => { @@ -61,6 +64,7 @@ describe('FxQuotesModel Tests -->', () => { }) test('should not validate participant if proxy cache returns a proxy', async () => { + proxyClient.isConnected = false fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) await expect(fxQuotesModel.validateFxQuoteRequest(destination, request)).resolves.toBeUndefined() @@ -120,8 +124,7 @@ describe('FxQuotesModel Tests -->', () => { describe('forwardFxQuoteRequest', () => { test('should forward fx quote request', async () => { - jest.spyOn(LibHttp, 'httpRequest').mockResolvedValue({ status: 200 }) - fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log, httpRequest }) fxQuotesModel._getParticipantEndpoint = jest.fn().mockResolvedValue(mockEndpoint) await expect(fxQuotesModel.forwardFxQuoteRequest(headers, conversionRequestId, request, childSpan)).resolves.toBeUndefined() @@ -133,7 +136,7 @@ describe('FxQuotesModel Tests -->', () => { 'FSPIOP-Destination': headers['fspiop-destination'], Date: headers.date } - expect(LibHttp.httpRequest).toHaveBeenCalledWith({ + expect(httpRequest).toHaveBeenCalledWith({ headers: expectedHeaders, method: ENUM.Http.RestMethods.POST, url: `${mockEndpoint}${ENUM.EndPoints.FspEndpointTemplates.FX_QUOTES_POST}`, @@ -151,12 +154,11 @@ describe('FxQuotesModel Tests -->', () => { if (fspId === mockProxy) return mockProxyEndpoint return 'https://some.other.endpoint' }) - jest.spyOn(LibHttp, 'httpRequest').mockResolvedValue({ status: 200 }) - fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log, httpRequest }) await expect(fxQuotesModel.forwardFxQuoteRequest(headers, conversionRequestId, request, childSpan)).resolves.toBeUndefined() - expect(LibHttp.httpRequest).toBeCalled() + expect(httpRequest).toBeCalled() expect(proxyClient.lookupProxyByDfspId).toBeCalledTimes(1) expect(db.getParticipantEndpoint).toBeCalledTimes(2) expect(db.getParticipantEndpoint).toHaveBeenNthCalledWith(1, destination, endpointType) @@ -164,9 +166,9 @@ describe('FxQuotesModel Tests -->', () => { }) test('should format error thrown and re-throw', async () => { - jest.spyOn(LibHttp, 'httpRequest').mockRejectedValue(new Error('HTTP Error')) + httpRequest.mockRejectedValue(new Error('HTTP Error')) - fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log, httpRequest }) fxQuotesModel._getParticipantEndpoint = jest.fn().mockResolvedValue(undefined) await expect(fxQuotesModel.forwardFxQuoteRequest(headers, conversionRequestId, request, childSpan)).rejects.toThrow(FSPIOPError) @@ -200,14 +202,12 @@ describe('FxQuotesModel Tests -->', () => { describe('forwardFxQuoteUpdate', () => { test('should forward fx quote update', async () => { - fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) - - jest.spyOn(LibHttp, 'httpRequest').mockResolvedValue({ status: 200 }) + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log, httpRequest }) jest.spyOn(fxQuotesModel, '_getParticipantEndpoint').mockResolvedValue(mockEndpoint) await expect(fxQuotesModel.forwardFxQuoteUpdate(headers, conversionRequestId, updateRequest, childSpan)).resolves.toBeUndefined() - expect(LibHttp.httpRequest).toHaveBeenCalledWith({ + expect(httpRequest).toHaveBeenCalledWith({ headers: { 'Content-Type': headers['content-type'], 'FSPIOP-Source': headers['fspiop-source'], @@ -221,17 +221,195 @@ describe('FxQuotesModel Tests -->', () => { }) test('should send error callback if no endpoint found', async () => { - fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log, httpRequest }) + jest.spyOn(fxQuotesModel, '_getParticipantEndpoint').mockResolvedValue(undefined) jest.spyOn(fxQuotesModel, 'sendErrorCallback').mockResolvedValue() await expect(fxQuotesModel.forwardFxQuoteUpdate(headers, conversionRequestId, updateRequest, childSpan)).resolves.toBeUndefined() - expect(fxQuotesModel.sendErrorCallback).toBeCalledWith(headers['fspiop-source'], expect.any(Error), conversionRequestId, headers, childSpan, true) + expect(httpRequest).not.toBeCalled() + }) + + test('should format error thrown and re-throw', async () => { + const httpRequest = jest.fn().mockRejectedValue(new Error('HTTP Error')) + + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log, httpRequest }) + fxQuotesModel._getParticipantEndpoint = jest.fn().mockResolvedValue(mockEndpoint) + + await expect(fxQuotesModel.forwardFxQuoteUpdate(headers, conversionRequestId, updateRequest, childSpan)).rejects.toThrow(FSPIOPError) + }) + }) + + describe('handleFxQuoteGet', () => { + test('should handle fx quote get', async () => { + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + jest.spyOn(fxQuotesModel, 'forwardFxQuoteGet').mockResolvedValue() + jest.spyOn(fxQuotesModel, 'handleException').mockResolvedValue() + + await expect(fxQuotesModel.handleFxQuoteGet(headers, conversionRequestId, span)).resolves.toBeUndefined() + + expect(fxQuotesModel.forwardFxQuoteGet).toBeCalledWith(headers, conversionRequestId, span.getChild()) + expect(fxQuotesModel.handleException).not.toBeCalled() + }) + + test('should handle error thrown', async () => { + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + jest.spyOn(fxQuotesModel, 'forwardFxQuoteGet').mockRejectedValue(new Error('Forward Error')) + jest.spyOn(fxQuotesModel, 'handleException').mockResolvedValue() + + await expect(fxQuotesModel.handleFxQuoteGet(headers, conversionRequestId, span)).resolves.toBeUndefined() + + expect(fxQuotesModel.forwardFxQuoteGet).toBeCalledWith(headers, conversionRequestId, span.getChild()) + expect(fxQuotesModel.handleException).toBeCalledWith(headers['fspiop-source'], conversionRequestId, expect.any(Error), headers, span.getChild()) + }) + }) + + describe('forwardFxQuoteGet', () => { + test('should forward fx quote get', async () => { + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log, httpRequest }) + jest.spyOn(fxQuotesModel, '_getParticipantEndpoint').mockResolvedValue(mockEndpoint) + + await expect(fxQuotesModel.forwardFxQuoteGet(headers, conversionRequestId, childSpan)).resolves.toBeUndefined() + + expect(httpRequest).toHaveBeenCalledWith({ + headers: { + Accept: headers.accept, + 'Content-Type': headers['content-type'], + 'FSPIOP-Source': headers['fspiop-source'], + 'FSPIOP-Destination': headers['fspiop-destination'], + Date: headers.date + }, + method: ENUM.Http.RestMethods.GET, + url: `${mockEndpoint}/fxQuotes/${conversionRequestId}` + }, headers['fspiop-source']) + }) + + test('should format error thrown and re-throw', async () => { + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log, httpRequest }) + fxQuotesModel._getParticipantEndpoint = jest.fn().mockResolvedValue(undefined) + + await expect(fxQuotesModel.forwardFxQuoteGet(headers, conversionRequestId, updateRequest, childSpan)).rejects.toThrow(FSPIOPError) + }) + }) + + describe('handleFxQuoteError', () => { + test('should handle fx quote error', async () => { + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + jest.spyOn(fxQuotesModel, 'sendErrorCallback').mockResolvedValue() + + const error = { errorCode: '3201', errorDescription: 'Destination FSP error' } + await expect(fxQuotesModel.handleFxQuoteError(headers, conversionRequestId, error, span)).resolves.toBeUndefined() + + const fspiopError = ErrorHandler.CreateFSPIOPErrorFromErrorInformation(error) + expect(fxQuotesModel.sendErrorCallback).toBeCalledWith(headers['fspiop-destination'], fspiopError, conversionRequestId, headers, childSpan, false) + }) + + test('should handle error thrown', async () => { + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + jest.spyOn(fxQuotesModel, 'sendErrorCallback').mockRejectedValue(new Error('Send Error Callback Error')) + jest.spyOn(fxQuotesModel, 'handleException').mockResolvedValue() + + const error = { errorCode: '3201', errorDescription: 'Destination FSP error' } + await expect(fxQuotesModel.handleFxQuoteError(headers, conversionRequestId, error, span)).resolves.toBeUndefined() + + const fspiopError = ErrorHandler.CreateFSPIOPErrorFromErrorInformation(error) + expect(fxQuotesModel.sendErrorCallback).toBeCalledWith(headers['fspiop-destination'], fspiopError, conversionRequestId, headers, childSpan, false) + expect(fxQuotesModel.handleException).toBeCalledWith(headers['fspiop-source'], conversionRequestId, expect.any(Error), headers, childSpan) + }) + }) + + describe('handleException', () => { + test('should handle exception', async () => { + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + jest.spyOn(fxQuotesModel, 'sendErrorCallback').mockResolvedValue() + + const error = new Error('Test Error') + await expect(fxQuotesModel.handleException(headers['fspiop-source'], conversionRequestId, error, headers, span)).resolves.toBeUndefined() + + const fspiopError = ErrorHandler.ReformatFSPIOPError(error) + expect(fxQuotesModel.sendErrorCallback).toBeCalledWith(headers['fspiop-source'], fspiopError, conversionRequestId, headers, childSpan, true) + }) + + test('should handle error thrown', async () => { + const error = new Error('Send Error Callback Error') + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + jest.spyOn(fxQuotesModel, 'sendErrorCallback').mockRejectedValue(error) + + await expect(fxQuotesModel.handleException(headers['fspiop-source'], conversionRequestId, error, headers, span)).resolves.toBeUndefined() + + const fspiopError = ErrorHandler.ReformatFSPIOPError(error) + expect(fxQuotesModel.sendErrorCallback).toBeCalledWith(headers['fspiop-source'], fspiopError, conversionRequestId, headers, childSpan, true) + expect(log.error).toBeCalledWith(expect.any(String), error) }) }) describe('sendErrorCallback method Tests', () => { + test('should throw fspiop error if no destination found', async () => { + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + fxQuotesModel._getParticipantEndpoint = jest.fn().mockResolvedValue(undefined) + const fspiopError = ErrorHandler.CreateFSPIOPError({ code: 2001, message: 'Generic server error' }, '', new Error('Test error')) + await expect(fxQuotesModel.sendErrorCallback(headers['fspiop-source'], fspiopError, conversionRequestId, headers, childSpan)).rejects.toThrow(ERROR_MESSAGES.NO_FX_CALLBACK_ENDPOINT(headers['fspiop-source'], conversionRequestId)) + }) + + test('should send error callback with flag modifyHeaders === false', async () => { + headers['fspiop-signature'] = 'signature' + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient }) + fxQuotesModel._getParticipantEndpoint = jest.fn().mockResolvedValue(mockEndpoint) + jest.spyOn(fxQuotesModel, 'sendHttpRequest') + jest.spyOn(axios, 'request').mockResolvedValue({ status: 200 }) + const fspiopError = ErrorHandler.CreateFSPIOPError({ code: 2001, message: 'Generic server error' }, '', new Error('Test error')) + + await expect(fxQuotesModel.sendErrorCallback(headers['fspiop-source'], fspiopError, conversionRequestId, headers, childSpan, false)).resolves.toBeUndefined() + + expect(fxQuotesModel.sendHttpRequest).toBeCalledTimes(1) + const [args] = fxQuotesModel.sendHttpRequest.mock.calls[0] + expect(args.headers['FSPIOP-Source']).toBe(headers['fspiop-source']) + expect(args.headers['FSPIOP-Destination']).toBe(headers['fspiop-destination']) + expect(args.headers['FSPIOP-Signature']).toBe(headers['fspiop-signature']) + expect(args.headers.Date).toBe(headers.date) + expect(args.headers['Content-Type']).toBe(headers['content-type']) + expect(args.headers.Accept).toBeUndefined() + expect(args.method).toBe(ENUM.Http.RestMethods.PUT) + expect(args.url).toBe(`${mockEndpoint}/fxQuotes/${conversionRequestId}/error`) + expect(args.data).toBe(JSON.stringify(fspiopError.toApiErrorObject())) + }) + + test('should reformat and re-throw http request error to fspiop error', async () => { + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + fxQuotesModel._getParticipantEndpoint = jest.fn().mockResolvedValue(mockEndpoint) + fxQuotesModel.sendHttpRequest = jest.fn(async () => { throw new Error('Test error') }) + const fspiopError = ErrorHandler.CreateFSPIOPError({ code: 2001, message: 'Generic server error' }, '', new Error('Test error')) + + await expect(fxQuotesModel.sendErrorCallback(headers['fspiop-source'], fspiopError, conversionRequestId, headers, childSpan, false)).rejects.toThrow(FSPIOPError) + }) + + test('should re-throw error response from callback if not OK', async () => { + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) + fxQuotesModel._getParticipantEndpoint = jest.fn().mockResolvedValue(mockEndpoint) + fxQuotesModel.sendHttpRequest = jest.fn(async () => ({ status: 500 })) + const fspiopError = ErrorHandler.CreateFSPIOPError({ code: 2001, message: 'Generic server error' }, '', new Error('Test error')) + + await expect(fxQuotesModel.sendErrorCallback(headers['fspiop-source'], fspiopError, conversionRequestId, headers, childSpan, false)).rejects.toThrow(ERROR_MESSAGES.CALLBACK_UNSUCCESSFUL_HTTP_RESPONSE) + }) + + test('should jws sign the request if jwsSign is true', async () => { + const envConfig = new Config() + envConfig.jws.jwsSign = true + envConfig.jws.jwsSigningKey = fs.readFileSync(path.join(__dirname, '../../../secrets/jwsSigningKey.key'), 'utf-8') + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log, envConfig }) + fxQuotesModel._getParticipantEndpoint = jest.fn().mockResolvedValue(mockEndpoint) + fxQuotesModel.sendHttpRequest = jest.fn(async () => ({ status: 200 })) + const fspiopError = ErrorHandler.CreateFSPIOPError({ code: 2001, message: 'Generic server error' }, '', new Error('Test error')) + + await expect(fxQuotesModel.sendErrorCallback(headers['fspiop-source'], fspiopError, conversionRequestId, headers, childSpan, true)).resolves.toBeUndefined() + + expect(fxQuotesModel.sendHttpRequest).toBeCalledTimes(1) + const [args] = fxQuotesModel.sendHttpRequest.mock.calls[0] + expect(args.headers['fspiop-signature']).toContain('signature') + }) + test('should send errorCallback with flag modifyHeaders === true [CSI-414]', async () => { + fxQuotesModel = new FxQuotesModel({ db, requestId, proxyClient, log }) const destEndpoint = `https://some.endpoint/${Date.now()}` fxQuotesModel._getParticipantEndpoint = jest.fn(async () => destEndpoint) fxQuotesModel.sendHttpRequest = jest.fn(async () => ({ status: 200 })) From bd1c0eb7745322784885965e33c5830195eab08c Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 8 Aug 2024 15:13:51 +0100 Subject: [PATCH 11/28] test: update quotes unit test --- test/unit/model/quotes.test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/unit/model/quotes.test.js b/test/unit/model/quotes.test.js index bb6a1755..1514f148 100644 --- a/test/unit/model/quotes.test.js +++ b/test/unit/model/quotes.test.js @@ -629,6 +629,21 @@ describe('QuotesModel', () => { expect(quotesModel.db.getParticipant.mock.calls[0][0]).toBe(mockData.quoteRequest.payee.partyIdInfo.fspId) }) + + it('should validate payer supported currencies if supplied', async () => { + const fspiopSource = 'dfsp1' + const fspiopDestination = 'dfsp2' + const request = mockData.quoteRequest + request.payer.supportedCurrencies = ['ZMW', 'TZS'] + quotesModel.db.getParticipant.mockResolvedValueOnce({ accounts: [{ currency: 'ZMW' }] }) + quotesModel.db.getParticipant.mockResolvedValueOnce({ accounts: [{ currency: 'TZS' }] }) + + await expect(quotesModel.validateQuoteRequest(fspiopSource, fspiopDestination, request)).resolves.toBeUndefined() + + expect(quotesModel.db.getParticipant).toHaveBeenCalledTimes(2) + expect(quotesModel.db.getParticipant).toHaveBeenCalledWith(fspiopSource, 'PAYER_DFSP', 'ZMW', Enum.Accounts.LedgerAccountType.POSITION) + expect(quotesModel.db.getParticipant).toHaveBeenCalledWith(fspiopSource, 'PAYER_DFSP', 'TZS', Enum.Accounts.LedgerAccountType.POSITION) + }) }) describe('validateQuoteUpdate', () => { beforeEach(() => { From 30ef75395ef61402fea34b208b72172974439370 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 8 Aug 2024 17:03:25 +0100 Subject: [PATCH 12/28] test: update unit tests for quotes model, init and quoting handler --- package-lock.json | 1184 +++++++++------------ package.json | 2 +- src/handlers/init.js | 4 +- test/unit/handlers/QuotingHandler.test.js | 108 +- test/unit/handlers/init.test.js | 52 +- test/unit/model/quotes.test.js | 15 + 6 files changed, 656 insertions(+), 709 deletions(-) diff --git a/package-lock.json b/package-lock.json index 42c5d5b7..5bbe6fdf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "eslint-plugin-jest": "28.8.0", "jest": "29.7.0", "jest-junit": "16.0.0", - "npm-check-updates": "17.0.5", + "npm-check-updates": "17.0.6", "nyc": "17.0.0", "pre-commit": "1.2.2", "proxyquire": "2.1.3", @@ -77,9 +77,9 @@ } }, "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "11.6.4", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.6.4.tgz", - "integrity": "sha512-9K6xOqeevacvweLGik6LnZCb1fBtCOSIWQs8d096XGeqoLKC33UVMGz9+77Gw44KvbH4pKcQPWo4ZpxkXYj05w==", + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.0.tgz", + "integrity": "sha512-pRrmXMCwnmrkS3MLgAIW5dXRzeTv6GLjkjb4HmxNnvAKXN1Nfzp4KmGADBQvlVUcqi+a5D+hfGDLLnd5NnYxog==", "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", @@ -106,30 +106,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", - "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", - "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helpers": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -154,12 +154,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", - "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", "dev": true, "dependencies": { - "@babel/types": "^7.24.7", + "@babel/types": "^7.25.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -169,14 +169,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", - "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -202,49 +202,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", - "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", - "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", @@ -259,16 +216,15 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", - "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-module-imports": "^7.24.7", "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -278,9 +234,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", - "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "dev": true, "engines": { "node": ">=6.9.0" @@ -299,22 +255,10 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -330,22 +274,22 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", - "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", - "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", "dev": true, "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -438,10 +382,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", + "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", "dev": true, + "dependencies": { + "@babel/types": "^7.25.2" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -627,33 +574,30 @@ } }, "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", - "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", + "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -671,12 +615,12 @@ } }, "node_modules/@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-string-parser": "^7.24.8", "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, @@ -690,6 +634,14 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@dabh/diagnostics": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", @@ -716,9 +668,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", - "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -784,9 +736,9 @@ "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==" }, "node_modules/@grpc/grpc-js": { - "version": "1.10.9", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.9.tgz", - "integrity": "sha512-5tcgUctCG0qoNyfChZifz2tJqbRbXVO9J7X6duFcOjY3HUNCxg5D0ZCK7EP9vIcZ0zRpLU9bWkyCqVCLZ46IbQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.11.1.tgz", + "integrity": "sha512-gyt/WayZrVPH2w/UTLansS7F9Nwld472JxxaETamrM8HNlsa+jSLNyKAZmhxI2Me4c3mQHFiS1wWHDY1g1Kthw==", "dependencies": { "@grpc/proto-loader": "^0.7.13", "@js-sdsl/ordered-map": "^4.4.2" @@ -1438,12 +1390,6 @@ "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -1764,9 +1710,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { @@ -1883,48 +1829,6 @@ "winston": "3.13.1" } }, - "node_modules/@mojaloop/central-services-logger/node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@mojaloop/central-services-logger/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@mojaloop/central-services-logger/node_modules/winston": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.1.tgz", - "integrity": "sha512-SvZit7VFNvXRzbqGHsv5KSmgbEYR5EiQfDAL9gxYkRqa934Hnk++zze0wANKtMHcy/gI4W/3xmSDwlhf865WGw==", - "dependencies": { - "@colors/colors": "^1.6.0", - "@dabh/diagnostics": "^2.0.2", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.6.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.7.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, "node_modules/@mojaloop/central-services-metrics": { "version": "12.0.8", "resolved": "https://registry.npmjs.org/@mojaloop/central-services-metrics/-/central-services-metrics-12.0.8.tgz", @@ -2068,11 +1972,45 @@ } } }, + "node_modules/@mojaloop/event-sdk/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@mojaloop/event-sdk/node_modules/tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, + "node_modules/@mojaloop/event-sdk/node_modules/winston": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz", + "integrity": "sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/@mojaloop/inter-scheme-proxy-cache-lib": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@mojaloop/inter-scheme-proxy-cache-lib/-/inter-scheme-proxy-cache-lib-2.2.0.tgz", @@ -2357,11 +2295,11 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.14.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", - "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", + "integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.13.0" } }, "node_modules/@types/normalize-package-data": { @@ -2382,9 +2320,9 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" }, "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -2397,16 +2335,16 @@ "dev": true }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.13.0.tgz", - "integrity": "sha512-ZrMCe1R6a01T94ilV13egvcnvVJ1pxShkE0+NDjDzH4nvG1wXpwsVI5bZCvE7AEDH1mXEx5tJSVR68bLgG7Dng==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.1.tgz", + "integrity": "sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.13.0", - "@typescript-eslint/visitor-keys": "7.13.0" + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -2414,12 +2352,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.13.0.tgz", - "integrity": "sha512-QWuwm9wcGMAuTsxP+qz6LBBd3Uq8I5Nv8xb0mk54jmNoCyDspnMvVsOxI6IsMmway5d1S9Su2+sCKv1st2l6eA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.1.tgz", + "integrity": "sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==", "dev": true, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -2427,13 +2365,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.13.0.tgz", - "integrity": "sha512-cAvBvUoobaoIcoqox1YatXOnSl3gx92rCZoMRPzMNisDiM12siGilSM4+dJAekuuHTibI2hVC2fYK79iSFvWjw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.1.tgz", + "integrity": "sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.13.0", - "@typescript-eslint/visitor-keys": "7.13.0", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2442,7 +2380,7 @@ "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -2464,9 +2402,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -2479,16 +2417,16 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.13.0.tgz", - "integrity": "sha512-nxn+dozQx+MK61nn/JP+M4eCkHDSxSLDpgE3WcQo0+fkjEolnaB5jswvIKC4K56By8MMgIho7f1PVxERHEo8rw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.1.tgz", + "integrity": "sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.13.0", + "@typescript-eslint/types": "8.0.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -2514,9 +2452,9 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -2835,18 +2773,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.toreversed": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", - "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - } - }, "node_modules/array.prototype.tosorted": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", @@ -3168,17 +3094,6 @@ "ms": "2.0.0" } }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -3229,9 +3144,9 @@ "integrity": "sha512-UcQusNAX7nnuXf9tvvLRC6DtZ8/YkDJRtTIbiA5ayb8MehwtSwtkvd5ZTXNLUTTtU6J/yJsi+1LJXqgRz1obwg==" }, "node_modules/browserslist": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", - "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, "funding": [ { @@ -3248,10 +3163,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001629", - "electron-to-chromium": "^1.4.796", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.16" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -3420,9 +3335,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001632", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001632.tgz", - "integrity": "sha512-udx3o7yHJfUxMLkGohMlVHCvFvWmirKh9JAH/d7WOLPetlH+LTL5cocMZ0t7oZx/mdlOWXti97xLZWc8uURRHg==", + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", "dev": true, "funding": [ { @@ -3467,9 +3382,9 @@ } }, "node_modules/chance": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/chance/-/chance-1.1.11.tgz", - "integrity": "sha512-kqTg3WWywappJPqtgrdvbA380VoXO2eu9VCV895JgbyHsaErXdyHK9LOZ911OvAk6L0obK7kDk9CGs8+oBawVA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/chance/-/chance-1.1.12.tgz", + "integrity": "sha512-vVBIGQVnwtUG+SYe0ge+3MvF78cvSpuCOEUJr7sVEk2vSBuMW6OXNJjSzdtzrlxNUEaoqH2GBd5Y/+18BEB01Q==", "dev": true }, "node_modules/char-regex": { @@ -3594,22 +3509,6 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -3943,45 +3842,6 @@ "node": ">=10" } }, - "node_modules/conventional-changelog-core/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-core/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-core/node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/conventional-changelog-ember": { "version": "2.0.9", "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz", @@ -4186,14 +4046,6 @@ "node": ">=6" } }, - "node_modules/convict/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "engines": { - "node": ">=10" - } - }, "node_modules/cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", @@ -4364,9 +4216,9 @@ } }, "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dependencies": { "ms": "2.1.2" }, @@ -4603,6 +4455,15 @@ "node": ">=8" } }, + "node_modules/dir-glob/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -4851,9 +4712,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.798", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.798.tgz", - "integrity": "sha512-by9J2CiM9KPGj9qfp5U4FcPSbXJG7FNzqnYaY4WLzX+v2PHieVGmnsA4dxfpGE3QEC7JofpPZmn7Vn1B9NR2+Q==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.5.tgz", + "integrity": "sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA==", "dev": true }, "node_modules/emittery": { @@ -4886,16 +4747,6 @@ "node": ">= 0.8" } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "optional": true, - "peer": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -5319,9 +5170,9 @@ } }, "node_modules/eslint-plugin-es-x": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.7.0.tgz", - "integrity": "sha512-aP3qj8BwiEDPttxQkZdI221DLKq9sI/qHolE2YSQL1/9+xk7dTV+tB1Fz8/IaCA+lnLA1bDEnvaS2LKs0k2Uig==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", "dev": true, "funding": [ "https://github.com/sponsors/ota-meshi", @@ -5330,7 +5181,7 @@ "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.6.0", + "@eslint-community/regexpp": "^4.11.0", "eslint-compat-utils": "^0.5.1" }, "engines": { @@ -5451,25 +5302,25 @@ } }, "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.13.0.tgz", - "integrity": "sha512-jceD8RgdKORVnB4Y6BqasfIkFhl4pajB1wVxrF4akxD2QPM8GNYjgGwEzYS+437ewlqqrg7Dw+6dhdpjMpeBFQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.1.tgz", + "integrity": "sha512-CBFR0G0sCt0+fzfnKaciu9IBsKvEKYwN9UZ+eeogK1fYHg4Qxk1yf/wLQkLXlq8wbU2dFlgAesxt8Gi76E8RTA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.13.0", - "@typescript-eslint/types": "7.13.0", - "@typescript-eslint/typescript-estree": "7.13.0" + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/typescript-estree": "8.0.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0" } }, "node_modules/eslint-plugin-n": { @@ -5502,9 +5353,9 @@ } }, "node_modules/eslint-plugin-promise": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.2.0.tgz", - "integrity": "sha512-QmAqwizauvnKOlifxyDj2ObfULpHQawlg/zQdgEixur9vl0CvZGv/LCJV2rtj3210QCoeGBzVMfMXqGAOr/4fA==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", + "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -5517,35 +5368,35 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.34.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.2.tgz", - "integrity": "sha512-2HCmrU+/JNigDN6tg55cRDKCQWicYAPB38JGSFDQt95jDm8rrvSUo7YPkOIm5l6ts1j1zCvysNcasvfTMQzUOw==", + "version": "7.35.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", + "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", "dev": true, "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.2", - "array.prototype.toreversed": "^1.1.2", - "array.prototype.tosorted": "^1.1.3", + "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.0.19", "estraverse": "^5.3.0", + "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.8", "object.fromentries": "^2.0.8", - "object.hasown": "^1.1.4", "object.values": "^1.2.0", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.11" + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "node_modules/eslint-plugin-react/node_modules/doctrine": { @@ -5702,9 +5553,9 @@ } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -6500,57 +6351,16 @@ "wrap-ansi": "^7.0.0" } }, - "node_modules/get-pkg-repo/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/get-pkg-repo/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/get-pkg-repo/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "node_modules/get-pkg-repo/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "dependencies": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" } }, - "node_modules/get-pkg-repo/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/get-pkg-repo/node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -6569,6 +6379,15 @@ "node": ">=10" } }, + "node_modules/get-pkg-repo/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/get-stdin": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", @@ -6611,9 +6430,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", - "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz", + "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==", "dev": true, "peer": true, "dependencies": { @@ -6703,12 +6522,6 @@ "ini": "^1.3.2" } }, - "node_modules/gitconfiglocal/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -6887,9 +6700,9 @@ } }, "node_modules/handlebars/node_modules/uglify-js": { - "version": "3.18.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.18.0.tgz", - "integrity": "sha512-SyVVbcNBCk0dzr9XL/R/ySrmYf0s372K6/hFklzgcp2lBFyXtw4I7BOdDjlLhE1aVqaI/SHWXWmYdlZxuyF38A==", + "version": "3.19.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.1.tgz", + "integrity": "sha512-y/2wiW+ceTYR2TSSptAhfnEtpLaQ4Ups5zrjB2d3kuVxHj16j/QJwPl5PvuGy9uARb39J0+iKxcRPvtpsx4A4A==", "dev": true, "optional": true, "bin": { @@ -7097,6 +6910,36 @@ "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==", "deprecated": "This module has moved and is now available at @hapi/hoek. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues." }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -7301,13 +7144,11 @@ } }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "optional": true, - "peer": true, + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { "node": ">=0.10.0" @@ -7371,9 +7212,9 @@ } }, "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, "dependencies": { "pkg-dir": "^4.2.0", @@ -7423,6 +7264,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -7594,11 +7440,14 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8013,9 +7862,9 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", - "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, "dependencies": { "@babel/core": "^7.23.9", @@ -8045,18 +7894,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-processinfo/node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -8112,9 +7949,9 @@ } }, "node_modules/jake": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", - "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", @@ -8864,12 +8701,6 @@ "integrity": "sha512-pi/dX/DqBA9O8FFTgdR2uuYBQoW40QIB0UW7vH1QcRpoTsYA/ANcWspzD7pFxyrs+P09/K5fKAVdr9k42twy3A==", "dev": true }, - "node_modules/json-schema-ref-parser/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -9309,9 +9140,9 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/logform": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", - "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.1.tgz", + "integrity": "sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==", "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", @@ -9324,14 +9155,6 @@ "node": ">= 12.0.0" } }, - "node_modules/logform/node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", @@ -9518,43 +9341,10 @@ } }, "node_modules/meow/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true }, "node_modules/meow/node_modules/read-pkg": { "version": "5.2.0", @@ -9597,12 +9387,6 @@ "node": ">=8" } }, - "node_modules/meow/node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, "node_modules/meow/node_modules/read-pkg/node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -9615,15 +9399,6 @@ "validate-npm-package-license": "^3.0.1" } }, - "node_modules/meow/node_modules/read-pkg/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/meow/node_modules/read-pkg/node_modules/type-fest": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", @@ -9633,6 +9408,15 @@ "node": ">=8" } }, + "node_modules/meow/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/meow/node_modules/type-fest": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", @@ -9645,15 +9429,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/meow/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -9711,9 +9486,9 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", "engines": { "node": ">= 0.6" } @@ -9729,6 +9504,14 @@ "node": ">= 0.6" } }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -9874,9 +9657,9 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/nan": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", - "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==" + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==" }, "node_modules/nanoid": { "version": "3.3.7", @@ -10039,11 +9822,26 @@ "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==" }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -10053,9 +9851,9 @@ } }, "node_modules/npm-check-updates": { - "version": "17.0.5", - "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-17.0.5.tgz", - "integrity": "sha512-PfJ7fJDRelNwDWfUYq7b7EBh56xLvxcBOEvYWe6aQbjV0i+fg7LSIdkcVjb6lIxlxvQqlaAX6Lsln69XadL22Q==", + "version": "17.0.6", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-17.0.6.tgz", + "integrity": "sha512-KCiaJH1cfnh/RyzKiDNjNfXgcKFyQs550Uf1OF/Yzb8xO56w+RLpP/OKRUx23/GyP/mLYwEpOO65qjmVdh6j0A==", "dev": true, "bin": { "ncu": "build/cli.js", @@ -10170,18 +9968,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nyc/node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/nyc/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -10242,6 +10028,15 @@ "node": ">=8" } }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/oas-kit-common": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", @@ -10341,9 +10136,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10421,23 +10219,6 @@ "node": ">= 0.4" } }, - "node_modules/object.hasown": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", - "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object.values": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", @@ -10791,6 +10572,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -10923,12 +10716,24 @@ "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, "engines": { - "node": ">=8" + "node": ">=4" + } + }, + "node_modules/path-type/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" } }, "node_modules/pause-stream": { @@ -11093,9 +10898,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -11111,9 +10916,9 @@ } ], "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "source-map-js": "^1.0.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -11388,6 +11193,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", "dev": true, "engines": { "node": ">=0.6.0", @@ -11395,9 +11201,9 @@ } }, "node_modules/qs": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", - "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { "side-channel": "^1.0.6" }, @@ -11477,17 +11283,6 @@ "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -11502,11 +11297,6 @@ "rc": "cli.js" } }, - "node_modules/rc/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -11661,18 +11451,6 @@ "node": ">=4" } }, - "node_modules/read-pkg/node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/read-pkg/node_modules/pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -12048,6 +11826,15 @@ "node": ">=8" } }, + "node_modules/replace/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -12291,9 +12078,9 @@ } }, "node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { "semver": "bin/semver.js" }, @@ -12736,6 +12523,12 @@ "node": ">= 6" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "node_modules/sqlstring": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", @@ -13000,56 +12793,6 @@ "node": ">=4" } }, - "node_modules/standard-version/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/standard-version/node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/standard-version/node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/standard-version/node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/standard-version/node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -13068,6 +12811,15 @@ "node": ">=10" } }, + "node_modules/standard-version/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/standard/node_modules/@eslint/eslintrc": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", @@ -13348,6 +13100,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", @@ -13962,9 +13724,9 @@ } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "peer": true, "bin": { @@ -14065,9 +13827,9 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", + "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==" }, "node_modules/unpipe": { "version": "1.0.0", @@ -14078,9 +13840,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, "funding": [ { @@ -14154,9 +13916,9 @@ "dev": true }, "node_modules/v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", @@ -14275,13 +14037,13 @@ } }, "node_modules/which-builtin-type": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", - "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", + "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", "dev": true, "dependencies": { - "function.prototype.name": "^1.1.5", - "has-tostringtag": "^1.0.0", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.0.5", "is-finalizationregistry": "^1.0.2", @@ -14290,8 +14052,8 @@ "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -14596,6 +14358,14 @@ "yargs-parser": "^11.1.1" } }, + "node_modules/widdershins/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, "node_modules/window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", @@ -14605,15 +14375,15 @@ } }, "node_modules/winston": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz", - "integrity": "sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.1.tgz", + "integrity": "sha512-SvZit7VFNvXRzbqGHsv5KSmgbEYR5EiQfDAL9gxYkRqa934Hnk++zze0wANKtMHcy/gI4W/3xmSDwlhf865WGw==", "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", - "logform": "^2.4.0", + "logform": "^2.6.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", @@ -14626,12 +14396,12 @@ } }, "node_modules/winston-transport": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz", - "integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.1.tgz", + "integrity": "sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA==", "dependencies": { - "logform": "^2.3.2", - "readable-stream": "^3.6.0", + "logform": "^2.6.1", + "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" }, "engines": { @@ -14651,14 +14421,6 @@ "node": ">= 6" } }, - "node_modules/winston/node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/winston/node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -14687,6 +14449,22 @@ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -14738,9 +14516,9 @@ } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, "node_modules/yaml": { @@ -14772,6 +14550,14 @@ } }, "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", diff --git a/package.json b/package.json index afe64f2b..2e31e80a 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "eslint-plugin-jest": "28.8.0", "jest": "29.7.0", "jest-junit": "16.0.0", - "npm-check-updates": "17.0.5", + "npm-check-updates": "17.0.6", "nyc": "17.0.0", "pre-commit": "1.2.2", "proxyquire": "2.1.3", diff --git a/src/handlers/init.js b/src/handlers/init.js index 9961f685..f8c5584e 100644 --- a/src/handlers/init.js +++ b/src/handlers/init.js @@ -15,8 +15,8 @@ let proxyClient let consumersMap let monitoringServer -const startFn = async (handlerList) => { - const config = new Config() +const startFn = async (handlerList, appConfig = undefined) => { + const config = appConfig || new Config() db = new Database(config) await db.connect() diff --git a/test/unit/handlers/QuotingHandler.test.js b/test/unit/handlers/QuotingHandler.test.js index 534c5c0d..745c57a3 100644 --- a/test/unit/handlers/QuotingHandler.test.js +++ b/test/unit/handlers/QuotingHandler.test.js @@ -4,10 +4,12 @@ const { Tracer } = require('@mojaloop/event-sdk') const Logger = require('@mojaloop/central-services-logger') jest.mock('../../../src/model/quotes') +jest.mock('../../../src/model/fxQuotes') jest.mock('../../../src/model/bulkQuotes') const QuotingHandler = require('../../../src/handlers/QuotingHandler') const QuotesModel = require('../../../src/model/quotes') +const FxQuotesModel = require('../../../src/model/fxQuotes') const BulkQuotesModel = require('../../../src/model/bulkQuotes') const Config = require('../../../src/lib/config') @@ -36,20 +38,24 @@ const createKafkaMessage = (topic) => ({ describe('QuotingHandler Tests -->', () => { let handler let quotesModel + let fxQuotesModel let bulkQuotesModel const quotesModelFactory = () => quotesModel + const fxQuotesModelFactory = () => fxQuotesModel const bulkQuotesModelFactory = () => bulkQuotesModel beforeEach(() => { const config = new Config() quotesModel = new QuotesModel({}) + fxQuotesModel = new FxQuotesModel({}) bulkQuotesModel = new BulkQuotesModel({}) handler = new QuotingHandler({ quotesModelFactory, bulkQuotesModelFactory, + fxQuotesModelFactory, config, logger: Logger, cache: new Cache(), @@ -218,8 +224,84 @@ describe('QuotingHandler Tests -->', () => { }) }) + describe('handlePostFxQuotes method Tests', () => { + it('should process POST /fxQuotes payload', async () => { + const requestData = createRequestData() + + const result = await handler.handlePostFxQuotes(requestData) + expect(result).toBe(true) + + expect(fxQuotesModel.handleFxQuoteRequest).toHaveBeenCalledTimes(1) + expect(fxQuotesModel.handleException).toHaveBeenCalledTimes(0) + }) + + it('should call handleException in case of error in handleFxQuoteRequest', async () => { + fxQuotesModel.handleFxQuoteRequest = jest.fn(async () => { throw new Error('Test Error') }) + const requestData = createRequestData() + + const result = await handler.handlePostFxQuotes(requestData) + expect(result).toBe(true) + expect(fxQuotesModel.handleException).toHaveBeenCalledTimes(1) + }) + }) + + describe('handlePutFxQuotes method Tests', () => { + it('should process success PUT /fxQuotes payload', async () => { + const requestData = createRequestData() + + const result = await handler.handlePutFxQuotes(requestData) + expect(result).toBe(true) + + expect(fxQuotesModel.handleFxQuoteUpdate).toHaveBeenCalledTimes(1) + expect(fxQuotesModel.handleException).toHaveBeenCalledTimes(0) + }) + + it('should process error PUT /fxQuotes payload', async () => { + const requestData = createRequestData({ + payload: { errorInformation: {} } + }) + + const result = await handler.handlePutFxQuotes(requestData) + expect(result).toBe(true) + + expect(fxQuotesModel.handleFxQuoteError).toHaveBeenCalledTimes(1) + expect(fxQuotesModel.handleFxQuoteUpdate).toHaveBeenCalledTimes(0) + expect(fxQuotesModel.handleException).toHaveBeenCalledTimes(0) + }) + + it('should call handleException in case of error in handleFxQuoteUpdate', async () => { + fxQuotesModel.handleFxQuoteUpdate = jest.fn(async () => { throw new Error('Test Error') }) + const requestData = createRequestData() + + const result = await handler.handlePutFxQuotes(requestData) + expect(result).toBe(true) + expect(fxQuotesModel.handleException).toHaveBeenCalledTimes(1) + }) + }) + + describe('handleGetFxQuotes method Tests', () => { + it('should process GET /fxQuotes payload', async () => { + const requestData = createRequestData() + + const result = await handler.handleGetFxQuotes(requestData) + expect(result).toBe(true) + + expect(fxQuotesModel.handleFxQuoteGet).toHaveBeenCalledTimes(1) + expect(fxQuotesModel.handleException).toHaveBeenCalledTimes(0) + }) + + it('should call handleException in case of error in handleFxQuoteGet', async () => { + fxQuotesModel.handleFxQuoteGet = jest.fn(async () => { throw new Error('Test Error') }) + const requestData = createRequestData() + + const result = await handler.handleGetFxQuotes(requestData) + expect(result).toBe(true) + expect(fxQuotesModel.handleException).toHaveBeenCalledTimes(1) + }) + }) + describe('defineHandlerByTopic method Tests', () => { - const { QUOTE, BULK_QUOTE } = (new Config()).kafkaConfig.CONSUMER + const { QUOTE, BULK_QUOTE, FX_QUOTE } = (new Config()).kafkaConfig.CONSUMER it('should skip message processing and log warn on incorrect topic name', async () => { const message = createKafkaMessage('wrong-topic') @@ -277,6 +359,30 @@ describe('QuotingHandler Tests -->', () => { await handler.defineHandlerByTopic(message) expect(handler.handleGetBulkQuotes).toHaveBeenCalledTimes(1) }) + + it('should define a handler for FX_QUOTE.POST.topic', async () => { + const message = createKafkaMessage(FX_QUOTE.POST.topic) + handler.handlePostFxQuotes = jest.fn() + + await handler.defineHandlerByTopic(message) + expect(handler.handlePostFxQuotes).toHaveBeenCalledTimes(1) + }) + + it('should define a handler for FX_QUOTE.PUT.topic', async () => { + const message = createKafkaMessage(FX_QUOTE.PUT.topic) + handler.handlePutFxQuotes = jest.fn() + + await handler.defineHandlerByTopic(message) + expect(handler.handlePutFxQuotes).toHaveBeenCalledTimes(1) + }) + + it('should define a handler for FX_QUOTE.GET.topic', async () => { + const message = createKafkaMessage(FX_QUOTE.GET.topic) + handler.handleGetFxQuotes = jest.fn() + + await handler.defineHandlerByTopic(message) + expect(handler.handleGetFxQuotes).toHaveBeenCalledTimes(1) + }) }) describe('handleMessages method Tests', () => { diff --git a/test/unit/handlers/init.test.js b/test/unit/handlers/init.test.js index 1e02e0fe..eda09603 100644 --- a/test/unit/handlers/init.test.js +++ b/test/unit/handlers/init.test.js @@ -1,15 +1,18 @@ jest.mock('../../../src/handlers/createConsumers') jest.mock('../../../src/handlers/monitoringServer') -jest.mock('../../../src/lib/proxy', () => ({ - createProxyClient: () => ({ - connect: jest.fn(), - isConnected: false - }) -})) +// jest.mock('../../../src/lib/proxy', () => ({ +// createProxyClient: () => ({ +// connect: jest.fn().mockResolvedValue(true), +// isConnected: false +// }) +// })) +jest.mock('../../../src/lib/proxy') const init = require('../../../src/handlers/init') const Database = require('../../../src/data/cachedDatabase') const { Functionalities } = require('../../../src/lib/enum') +const Config = require('../../../src/lib/config') +const { createProxyClient } = require('../../../src/lib/proxy') const handlerList = [Functionalities.QUOTE] @@ -26,6 +29,22 @@ describe('init Tests -->', () => { await expect(init.stopFn()).resolves.toBeUndefined() }) + test('should disconnect proxyCache if enabled', async () => { + isDbOk = true + const config = new Config() + config.proxyCache.enabled = true + const mockProxyCache = { + isConnected: true, + connect: jest.fn().mockResolvedValue(true), + disconnect: jest.fn().mockResolvedValue(true) + } + createProxyClient.mockReturnValue(mockProxyCache) + await init.startFn(handlerList, config) + + await expect(init.stopFn()).resolves.toBeUndefined() + expect(mockProxyCache.disconnect).toHaveBeenCalled() + }) + test('should execute startFn without error if DB is connected', async () => { isDbOk = true await expect(init.startFn(handlerList)) @@ -38,4 +57,25 @@ describe('init Tests -->', () => { await expect(init.startFn(handlerList)) .rejects.toThrowError('DB is not connected') }) + + test('should connect proxyCache if enabled', async () => { + isDbOk = true + const config = new Config() + config.proxyCache.enabled = true + const mockProxyCache = { connect: jest.fn().mockResolvedValue(true) } + createProxyClient.mockReturnValue(mockProxyCache) + + await expect(init.startFn(handlerList, config)).resolves.toBeTruthy() + expect(mockProxyCache.connect).toHaveBeenCalled() + }) + + test('should throw error if proxyCache is not connected', async () => { + isDbOk = true + const config = new Config() + config.proxyCache.enabled = true + const mockProxyCache = { connect: jest.fn().mockResolvedValue(false) } + createProxyClient.mockReturnValue(mockProxyCache) + + await expect(init.startFn(handlerList, config)).rejects.toThrowError('Proxy is not connected') + }) }) diff --git a/test/unit/model/quotes.test.js b/test/unit/model/quotes.test.js index 1514f148..4839550c 100644 --- a/test/unit/model/quotes.test.js +++ b/test/unit/model/quotes.test.js @@ -1068,6 +1068,21 @@ describe('QuotesModel', () => { expect(quotesModel.handleException.mock.calls.length).toBe(1) expect(result).toEqual(expectedResult) }) + + it('calls handleQuoteRequestResend if request is duplicate and should resend', async () => { + expect.assertions(1) + + quotesModel.checkDuplicateQuoteRequest = jest.fn(() => { + return { + isDuplicateId: true, + isResend: true + } + }) + + await quotesModel.handleQuoteRequest(mockData.headers, mockData.quoteRequest, mockSpan) + + expect(quotesModel.handleQuoteRequestResend).toHaveBeenCalledTimes(1) + }) }) }) }) From 1a3f7899d579b868faab1fcdfee29b0f065b44cf Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 8 Aug 2024 17:04:50 +0100 Subject: [PATCH 13/28] chore: remove commented block --- test/unit/handlers/init.test.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/unit/handlers/init.test.js b/test/unit/handlers/init.test.js index eda09603..264dabf2 100644 --- a/test/unit/handlers/init.test.js +++ b/test/unit/handlers/init.test.js @@ -1,11 +1,5 @@ jest.mock('../../../src/handlers/createConsumers') jest.mock('../../../src/handlers/monitoringServer') -// jest.mock('../../../src/lib/proxy', () => ({ -// createProxyClient: () => ({ -// connect: jest.fn().mockResolvedValue(true), -// isConnected: false -// }) -// })) jest.mock('../../../src/lib/proxy') const init = require('../../../src/handlers/init') From c816698b3aae17cef6ea87a09b32d8954073c112 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Fri, 9 Aug 2024 10:47:52 +0100 Subject: [PATCH 14/28] test: update tests --- src/api/routes.js | 5 +- test/integration/fxQuotes.test.js | 138 +++++++++++++++++++++++++++ test/integration/postRequest.test.js | 66 +------------ test/unit/api/routes.test.js | 42 ++++++++ 4 files changed, 187 insertions(+), 64 deletions(-) create mode 100644 test/integration/fxQuotes.test.js create mode 100644 test/unit/api/routes.test.js diff --git a/src/api/routes.js b/src/api/routes.js index ace8ee5d..9ccb2ed6 100644 --- a/src/api/routes.js +++ b/src/api/routes.js @@ -176,4 +176,7 @@ const APIRoutes = (api) => [ } ] -module.exports = { APIRoutes } +module.exports = { + APIRoutes, + handleRequest // Exposed for testing +} diff --git a/test/integration/fxQuotes.test.js b/test/integration/fxQuotes.test.js new file mode 100644 index 00000000..5c2beed2 --- /dev/null +++ b/test/integration/fxQuotes.test.js @@ -0,0 +1,138 @@ +/***** + License + -------------- + Copyright © 2020 Mojaloop Foundation + + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 + (the "License") and you may not use these files except in compliance with the [License](http://www.apache.org/licenses/LICENSE-2.0). + + You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the [License](http://www.apache.org/licenses/LICENSE-2.0). + + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Steven Oderayi + -------------- + ******/ + +const { Producer } = require('@mojaloop/central-services-stream').Util +const { createProxyClient } = require('../../src/lib/proxy') +const Config = require('../../src/lib/config') +const dto = require('../../src/lib/dto') +const mocks = require('../mocks') +const MockServerClient = require('./mockHttpServer/MockServerClient') +const uuid = require('crypto').randomUUID + +const TEST_TIMEOUT = 20_000 +const WAIT_TIMEOUT = 3_000 + +const hubClient = new MockServerClient() + +const base64Encode = (data) => Buffer.from(data).toString('base64') +const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)) + +describe('POST request tests --> ', () => { + jest.setTimeout(TEST_TIMEOUT) + + const { kafkaConfig, proxyCache } = new Config() + + beforeEach(async () => { + await hubClient.clearHistory() + }) + + afterAll(async () => { + await Producer.disconnect() + }) + + test('should forward POST /fxQuotes request to proxy if the payee dfsp is not registered in the hub', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.POST + const topicConfig = dto.topicConfigDto({ topicName: topic }) + const from = 'pinkbank' + const to = 'redbank' // redbank not in the hub db + const proxyId = 'redbankproxy' + let proxyClient + + try { + proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + + // register proxy representative for redbank + const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) + + // assert that the proxy representative is mapped in the cache + const key = `dfsp:${to}` + const representative = await proxyClient.redisClient.get(key) + expect(isAdded).toBe(true) + expect(representative).toBe(proxyId) + + const payload = { + conversionRequestId: uuid(), + conversionTerms: { + conversionId: uuid(), + initiatingFsp: from, + counterPartyFsp: to, + amountType: 'SEND', + sourceAmount: { + currency: 'USD', + amount: 300 + }, + targetAmount: { + currency: 'TZS' + } + } + } + const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + // assert that the request was received by the proxy + const request = response.data.history[0] + expect(request.url).toBe(`/${proxyId}/fxQuotes`) + expect(request.body).toEqual(payload) + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + } finally { + await proxyClient.disconnect() + } + }) + + /** + * Test cases to cover: + * - POST fx quote (no proxy) --> PUT fx callback (no proxy) + * - POST fx quote (no proxy) to invalid participant --> Expect put callback error at the sender's endpoint + * - POST quotes (no proxy) --> PUT quotes (no proxy) --> Expect callback received at the sender's endpoint + + * - POST fx quote (proxy) --> PUT fx callback (proxy) + * - POST quotes (proxy) --> PUT quotes (proxy) --> Expect end to end success of fx quote and final quote + * + * - POST fx quote to invalid participant (no proxy) --> Expect put callback error at the sender's endpoint + * - POST fx quote to invalid participant (proxy) --> Expect put callback error at the proxy endpoint + * + * - GET fx quote (no proxy) --> PUT fx callback (no proxy) --> Expect callback received at the sender's endpoint + * - GET fx quote (proxy) --> PUT fx callback (proxy) --> Expect callback received at the proxy endpoint + * + * - GET fx quote - invalid conversionRequestId --> Expect error callback at the sender's endpoint + * + * - PUT fx quote error (no proxy) --> Expect error callback at the receiver's endpoint + * - PUT fx quote error (proxy) --> Expect error callback at the proxy endpoint + */ +}) diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index 34bd68d4..3bcfa326 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -29,18 +29,18 @@ const { Producer } = require('@mojaloop/central-services-stream').Util const { createProxyClient } = require('../../src/lib/proxy') - const Config = require('../../src/lib/config') const dto = require('../../src/lib/dto') const mocks = require('../mocks') const MockServerClient = require('./mockHttpServer/MockServerClient') const uuid = require('crypto').randomUUID -const hubClient = new MockServerClient() -const base64Encode = (data) => Buffer.from(data).toString('base64') const TEST_TIMEOUT = 20_000 const WAIT_TIMEOUT = 3_000 +const hubClient = new MockServerClient() + +const base64Encode = (data) => Buffer.from(data).toString('base64') const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)) describe('POST request tests --> ', () => { @@ -233,66 +233,6 @@ describe('POST request tests --> ', () => { } }) - test('should forward POST /fxQuotes request to proxy if the payee dfsp is not registered in the hub', async () => { - let response = await hubClient.getHistory() - expect(response.data.history.length).toBe(0) - - const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.POST - const topicConfig = dto.topicConfigDto({ topicName: topic }) - const from = 'pinkbank' - // redbank not in the hub db - const to = 'redbank' - - // register proxy representative for redbank - const proxyId = 'redbankproxy' - let proxyClient - - try { - proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) - const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) - - // assert that the proxy representative is mapped in the cache - const key = `dfsp:${to}` - const representative = await proxyClient.redisClient.get(key) - - expect(isAdded).toBe(true) - expect(representative).toBe(proxyId) - - const payload = { - conversionRequestId: uuid(), - conversionTerms: { - conversionId: uuid(), - initiatingFsp: from, - counterPartyFsp: to, - amountType: 'SEND', - sourceAmount: { - currency: 'USD', - amount: 300 - }, - targetAmount: { - currency: 'TZS' - } - } - } - const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) - const isOk = await Producer.produceMessage(message, topicConfig, config) - expect(isOk).toBe(true) - - await wait(WAIT_TIMEOUT) - - response = await hubClient.getHistory() - expect(response.data.history.length).toBe(1) - - const request = response.data.history[0] - expect(request.url).toBe(`/${proxyId}/fxQuotes`) - expect(request.body).toEqual(payload) - expect(request.headers['fspiop-source']).toBe(from) - expect(request.headers['fspiop-destination']).toBe(to) - } finally { - await proxyClient.disconnect() - } - }) - test('should forward POST /bulkQuotes request to proxy if the payee dfsp is not registered in the hub', async () => { let response = await hubClient.getHistory() expect(response.data.history.length).toBe(0) diff --git a/test/unit/api/routes.test.js b/test/unit/api/routes.test.js new file mode 100644 index 00000000..20bc78af --- /dev/null +++ b/test/unit/api/routes.test.js @@ -0,0 +1,42 @@ +const { handleRequest } = require('../../../src/api/routes') + +describe('routes', () => { + let api + let h + + beforeEach(() => { + jest.resetModules() + api = { + handleRequest: jest.fn().mockReturnValue(200) + } + h = jest.fn() + }) + + const testCase = (method, path) => { + it(`should return 200 for ${method} ${path}`, async () => { + const req = { method, path, payload: {}, query: {}, headers: {} } + const result = handleRequest(api, req, h) + + expect(api.handleRequest).toHaveBeenCalled() + expect(result).toEqual(200) + const [args] = api.handleRequest.mock.calls[0] + expect(args.path).toEqual(req.path) + expect(args.method).toEqual(req.method) + }) + } + + testCase('PUT', '/quotes/{id}/error') + testCase('GET', '/quotes/{id}') + testCase('PUT', '/quotes/{id}') + testCase('POST', '/quotes') + testCase('PUT', '/bulkQuotes/{id}/error') + testCase('GET', '/bulkQuotes/{id}') + testCase('PUT', '/bulkQuotes/{id}') + testCase('POST', '/bulkQuotes') + testCase('GET', '/fxQuotes/{id}/error') + testCase('PUT', '/fxQuotes/{id}') + testCase('GET', '/fxQuotes/{id}') + testCase('POST', '/fxQuotes') + testCase('GET', '/health') + testCase('GET', '/metrics') +}) From e20eb0c1323730b6139e85f5d53a0b5f0ec7a2f9 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Mon, 19 Aug 2024 12:19:18 +0100 Subject: [PATCH 15/28] test: add dto for fxquotes payload --- Dockerfile | 2 +- test/integration/fxQuotes.test.js | 34 +++++++++---------------------- test/integration/scripts/start.sh | 7 ------- test/mocks.js | 29 +++++++++++++++++++++++++- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/Dockerfile b/Dockerfile index 42647daf..fdbd6889 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ ARG NODE_VERSION=lts-alpine # # Build Image -FROM node:${NODE_VERSION} as builder +FROM node:${NODE_VERSION} AS builder USER root WORKDIR /opt/app diff --git a/test/integration/fxQuotes.test.js b/test/integration/fxQuotes.test.js index 5c2beed2..719ed614 100644 --- a/test/integration/fxQuotes.test.js +++ b/test/integration/fxQuotes.test.js @@ -30,20 +30,18 @@ const { Producer } = require('@mojaloop/central-services-stream').Util const { createProxyClient } = require('../../src/lib/proxy') const Config = require('../../src/lib/config') +const MockServerClient = require('./mockHttpServer/MockServerClient') const dto = require('../../src/lib/dto') const mocks = require('../mocks') -const MockServerClient = require('./mockHttpServer/MockServerClient') -const uuid = require('crypto').randomUUID const TEST_TIMEOUT = 20_000 const WAIT_TIMEOUT = 3_000 const hubClient = new MockServerClient() - const base64Encode = (data) => Buffer.from(data).toString('base64') const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)) -describe('POST request tests --> ', () => { +describe('POST /fxQuotes request tests --> ', () => { jest.setTimeout(TEST_TIMEOUT) const { kafkaConfig, proxyCache } = new Config() @@ -68,33 +66,21 @@ describe('POST request tests --> ', () => { let proxyClient try { - proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + proxyClient = createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) // register proxy representative for redbank const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) // assert that the proxy representative is mapped in the cache const key = `dfsp:${to}` - const representative = await proxyClient.redisClient.get(key) + const proxy = await proxyClient.redisClient.get(key) expect(isAdded).toBe(true) - expect(representative).toBe(proxyId) - - const payload = { - conversionRequestId: uuid(), - conversionTerms: { - conversionId: uuid(), - initiatingFsp: from, - counterPartyFsp: to, - amountType: 'SEND', - sourceAmount: { - currency: 'USD', - amount: 300 - }, - targetAmount: { - currency: 'TZS' - } - } - } + expect(proxy).toBe(proxyId) + + const payload = mocks.fxQuotesPostPayloadDto({ + initiatingFsp: from, + counterPartyFsp: to + }) const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) diff --git a/test/integration/scripts/start.sh b/test/integration/scripts/start.sh index b295ec44..51ebdbd6 100755 --- a/test/integration/scripts/start.sh +++ b/test/integration/scripts/start.sh @@ -19,12 +19,6 @@ docker-compose up -d echo "Services started. Checking status..." docker-compose ps -# pwd -# SCRIPTS_FOLDER=./test/integration/scripts - -# echo "Loading env vars..." -# source $SCRIPTS_FOLDER/env.sh - echo "Waiting central-ledger migrations for $MIGRATION_TIMEOUT sec..." sleep $MIGRATION_TIMEOUT @@ -32,4 +26,3 @@ echo "Populating test data..." source $SCRIPTS_FOLDER/populateTestData.sh echo "Test environment is ready!" - diff --git a/test/mocks.js b/test/mocks.js index 3be1ab08..8654494c 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -1,3 +1,4 @@ +const uuid = require('crypto').randomUUID const Config = new (require('../src/lib/config'))() const CONTENT_TYPE = 'application/vnd.interoperability.quotes+json;version={{API_VERSION}}' @@ -91,8 +92,34 @@ const proxyCacheConfigDto = ({ timeout: 5000 // is it used anywhere? }) +const fxQuotesPostPayloadDto = ({ + conversionRequestId = uuid(), + conversionId = uuid(), + initiatingFsp = 'pinkbank', + counterPartyFsp = 'redbank', + amountType = 'SEND', + sourceAmount = { + currency: 'USD', + amount: 300 + }, + targetAmount = { + currency: 'TZS' + } +} = {}) => ({ + conversionRequestId, + conversionTerms: { + conversionId, + initiatingFsp, + counterPartyFsp, + amountType, + sourceAmount, + targetAmount + } +}) + module.exports = { kafkaMessagePayloadDto, kafkaMessagePayloadPostDto, - proxyCacheConfigDto + proxyCacheConfigDto, + fxQuotesPostPayloadDto } From 2f044a52673b0d9d5b401bf7d54322ec34ca3aab Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Mon, 19 Aug 2024 14:29:00 +0100 Subject: [PATCH 16/28] test: update integration tests for fxquotes --- docker-compose.yml | 2 +- test/integration/fxQuotes.test.js | 308 ++++++++++++++++++++++++++++-- test/mocks.js | 38 +++- 3 files changed, 333 insertions(+), 15 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index c1ec2c92..5593a1df 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -61,7 +61,7 @@ services: quoting-service-handler: <<: *quotingServiceBase - command: npm run start:handlers + command: npm run start:handlers:debug ports: - "3003:3003" - "29229:9229" diff --git a/test/integration/fxQuotes.test.js b/test/integration/fxQuotes.test.js index 719ed614..c0b889d1 100644 --- a/test/integration/fxQuotes.test.js +++ b/test/integration/fxQuotes.test.js @@ -27,6 +27,7 @@ -------------- ******/ +const uuid = require('crypto').randomUUID const { Producer } = require('@mojaloop/central-services-stream').Util const { createProxyClient } = require('../../src/lib/proxy') const Config = require('../../src/lib/config') @@ -44,7 +45,7 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)) describe('POST /fxQuotes request tests --> ', () => { jest.setTimeout(TEST_TIMEOUT) - const { kafkaConfig, proxyCache } = new Config() + const { kafkaConfig, proxyCache, hubName } = new Config() beforeEach(async () => { await hubClient.clearHistory() @@ -53,13 +54,13 @@ describe('POST /fxQuotes request tests --> ', () => { afterAll(async () => { await Producer.disconnect() }) - - test('should forward POST /fxQuotes request to proxy if the payee dfsp is not registered in the hub', async () => { + /** + * Produces a POST /fxQuotes message for a dfsp that is not registered in the hub + */ + test('should POST /fxQuotes (proxied)', async () => { let response = await hubClient.getHistory() expect(response.data.history.length).toBe(0) - const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.POST - const topicConfig = dto.topicConfigDto({ topicName: topic }) const from = 'pinkbank' const to = 'redbank' // redbank not in the hub db const proxyId = 'redbankproxy' @@ -82,6 +83,8 @@ describe('POST /fxQuotes request tests --> ', () => { counterPartyFsp: to }) const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.POST + const topicConfig = dto.topicConfigDto({ topicName: topic }) const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) @@ -101,24 +104,305 @@ describe('POST /fxQuotes request tests --> ', () => { } }) + /** + * Produces a PUT /fxQuotes/{ID} callback from a proxied payee + * Expects a PUT /fxQuotes/{ID} callback at the payer's endpoint + */ + test('should PUT /fxQuotes/{ID} callback (proxied)', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const from = 'redbank' + const to = 'pinkbank' + const proxyId = 'redbankproxy' + let proxyClient + + try { + proxyClient = createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + + // register proxy representative for redbank + const isAdded = await proxyClient.addDfspIdToProxyMapping(from, proxyId) + + // assert that the proxy representative is mapped in the cache + const key = `dfsp:${from}` + const proxy = await proxyClient.redisClient.get(key) + expect(isAdded).toBe(true) + expect(proxy).toBe(proxyId) + + const payload = mocks.fxQuotesPutPayloadDto({ + fxQuotesPostPayload: mocks.fxQuotesPostPayloadDto({ initiatingFsp: to, counterPartyFsp: from }) + }) + const message = mocks.kafkaMessageFxPayloadPutDto({ from, to, id: payload.conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.PUT + const topicConfig = dto.topicConfigDto({ topicName: topic }) + + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + // assert that the callback was received by the payer dfsp + const request = response.data.history[0] + expect(request.url).toBe(`/${to}/fxQuotes/${payload.conversionRequestId}`) + expect(request.body).toEqual(payload) + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + } finally { + await proxyClient.disconnect() + } + }) + + /** + * Produces a POST /fxQuotes message for a dfsp that is registered in the hub + * Expects a POST /fxQuotes request at the payee dfsp's endpoint + */ + test('should POST fx quote (no proxy)', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const from = 'pinkbank' + const to = 'greenbank' + const payload = mocks.fxQuotesPostPayloadDto({ + initiatingFsp: from, + counterPartyFsp: to + }) + const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.POST + const topicConfig = dto.topicConfigDto({ topicName: topic }) + + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + // assert that the request was received by the payee dfsp + const request = response.data.history[0] + expect(request.url).toBe(`/${to}/fxQuotes`) + expect(request.body).toEqual(payload) + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + }) + + /** + * Produces a PUT /fxQuotes/{ID} callback for a dfsp that is registered in the hub + * Expects a PUT /fxQuotes/{ID} callback at the payer dfsp's endpoint + */ + test('should PUT fx quote callback (no proxy)', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const from = 'greenbank' + const to = 'pinkbank' + const payload = mocks.fxQuotesPutPayloadDto({ + fxQuotesPostPayload: mocks.fxQuotesPostPayloadDto({ initiatingFsp: to, counterPartyFsp: from }) + }) + const message = mocks.kafkaMessageFxPayloadPutDto({ from, to, id: payload.conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.PUT + const topicConfig = dto.topicConfigDto({ topicName: topic }) + + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + // assert that the callback was received by the payee dfsp + const request = response.data.history[0] + expect(request.url).toBe(`/${to}/fxQuotes/${payload.conversionRequestId}`) + expect(request.body).toEqual(payload) + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + }) + + /** + * Produces a POST /fxQuotes request for an invalid dfsp + * Expects a PUT /fxQuotes/{ID} callback with an error at the sender's endpoint + */ + test('should POST fx quote to invalid participant', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const from = 'pinkbank' + const to = 'invalidbank' + const payload = mocks.fxQuotesPostPayloadDto({ + initiatingFsp: from, + counterPartyFsp: to + }) + const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.POST + const topicConfig = dto.topicConfigDto({ topicName: topic }) + + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + // assert that error callback was received by the payer dfsp + const request = response.data.history[0] + expect(request.url).toBe(`/${from}/fxQuotes/${payload.conversionRequestId}/error`) + expect(request.body.errorInformation.errorCode).toBe('3100') + expect(request.body.errorInformation.errorDescription).toBe(`Generic validation error - Unsupported participant '${to}'`) + expect(request.headers['fspiop-source']).toBe(hubName) + expect(request.headers['fspiop-destination']).toBe(from) + }) + + /** + * Produces a PUT /fxQuotes/{ID} callback with an error for a dfsp that is registered in the hub + * Expects a PUT /fxQuotes/{ID} callback with an error at the receiver's endpoint + */ + test('should PUT /fxQuotes/{ID}/error (no proxy)', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const from = 'greenbank' + const to = 'pinkbank' + const conversionRequestId = uuid() + const payload = { + errorInformation: { + errorCode: '3100', + errorDescription: 'Generic validation error' + } + } + const message = mocks.kafkaMessageFxPayloadPutDto({ from, to, id: conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.PUT + const topicConfig = dto.topicConfigDto({ topicName: topic }) + + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + // assert that the error callback was received by the payer dfsp + const request = response.data.history[0] + expect(request.url).toBe(`/${to}/fxQuotes/${conversionRequestId}/error`) + expect(request.body.errorInformation.errorCode).toBe('3100') + expect(request.body.errorInformation.errorDescription).toBe('Generic validation error') + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + }) + + /** + * Produces a PUT /fxQuotes/{ID}/error for a proxied dfsp + * Expects a PUT /fxQuotes/{ID}/error callback with an error at the proxy's endpoint + */ + test('should PUT /fxQuotes/{ID}/error (proxied)', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const from = 'pinkbank' + const to = 'redbank' + const proxyId = 'redbankproxy' + let proxyClient + + try { + proxyClient = createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + + // register proxy representative for redbank + const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) + + // assert that the proxy representative is mapped in the cache + const key = `dfsp:${to}` + const proxy = await proxyClient.redisClient.get(key) + expect(isAdded).toBe(true) + expect(proxy).toBe(proxyId) + + const conversionRequestId = uuid() + const payload = { + errorInformation: { + errorCode: '3100', + errorDescription: 'Generic validation error' + } + } + const message = mocks.kafkaMessageFxPayloadPutDto({ from, to, id: conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.PUT + const topicConfig = dto.topicConfigDto({ topicName: topic }) + + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + // assert that the error callback was received by the proxy + const request = response.data.history[0] + expect(request.url).toBe(`/${proxyId}/fxQuotes/${conversionRequestId}/error`) + expect(request.body.errorInformation.errorCode).toBe('3100') + expect(request.body.errorInformation.errorDescription).toBe('Generic validation error') + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + } finally { + await proxyClient.disconnect() + } + }) + + /** + * Produces a GET /fxQuotes request for a dfsp that is registered in the hub + * Expects a PUT /fxQuotes/{ID} callback at the requester's endpoint + */ + test.only('should GET fx quote (no proxy)', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const from = 'pinkbank' + const to = 'greenbank' + const conversionRequestId = uuid() + const payload = mocks.fxQuotesPutPayloadDto({ + fxQuotesPostPayload: mocks.fxQuotesPostPayloadDto({ initiatingFsp: from, counterPartyFsp: to }) + }) + const message = mocks.kafkaMessageFxPayloadPutDto({ from, to, id: conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.PUT + const topicConfig = dto.topicConfigDto({ topicName: topic }) + + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + // assert that the callback was received by the requester dfsp + const request = response.data.history[0] + expect(request.url).toBe(`/${from}/fxQuotes/${conversionRequestId}`) + expect(request.body).toEqual(payload) + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + }) + /** * Test cases to cover: - * - POST fx quote (no proxy) --> PUT fx callback (no proxy) - * - POST fx quote (no proxy) to invalid participant --> Expect put callback error at the sender's endpoint + * + POST fx quote (no proxy) --> PUT fx callback (no proxy) + * + POST fx quote (no proxy) to invalid participant --> Expect put callback error at the sender's endpoint * - POST quotes (no proxy) --> PUT quotes (no proxy) --> Expect callback received at the sender's endpoint - * - POST fx quote (proxy) --> PUT fx callback (proxy) + * + POST fx quote (proxy) --> PUT fx callback (proxy) * - POST quotes (proxy) --> PUT quotes (proxy) --> Expect end to end success of fx quote and final quote * - * - POST fx quote to invalid participant (no proxy) --> Expect put callback error at the sender's endpoint - * - POST fx quote to invalid participant (proxy) --> Expect put callback error at the proxy endpoint + * ! POST fx quote to invalid participant (proxy) --> Expect put callback error at the proxy endpoint + + * + PUT fx quote error (no proxy) --> Expect error callback at the receiver's endpoint + * + PUT fx quote error (proxy) --> Expect error callback at the proxy endpoint * * - GET fx quote (no proxy) --> PUT fx callback (no proxy) --> Expect callback received at the sender's endpoint * - GET fx quote (proxy) --> PUT fx callback (proxy) --> Expect callback received at the proxy endpoint * * - GET fx quote - invalid conversionRequestId --> Expect error callback at the sender's endpoint * - * - PUT fx quote error (no proxy) --> Expect error callback at the receiver's endpoint - * - PUT fx quote error (proxy) --> Expect error callback at the proxy endpoint */ }) diff --git a/test/mocks.js b/test/mocks.js index 8654494c..7a89986f 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -77,6 +77,24 @@ const kafkaMessagePayloadPostDto = (params = {}) => kafkaMessagePayloadDto({ operationId: 'Quotes' }) +const kafkaMessageFxPayloadPostDto = (params = {}) => kafkaMessagePayloadDto({ + ...params, + action: 'post', + type: 'fxquote', + operationId: 'FxQuotesPost' +}) + +const kafkaMessageFxPayloadPutDto = (params = {}) => { + const dto = kafkaMessagePayloadDto({ + ...params, + action: 'put', + type: 'fxquote', + operationId: 'FxQuotesPut' + }) + delete dto.content.headers.accept + return dto +} + const proxyCacheConfigDto = ({ type = 'redis' } = {}) => Object.freeze({ @@ -103,7 +121,7 @@ const fxQuotesPostPayloadDto = ({ amount: 300 }, targetAmount = { - currency: 'TZS' + currency: 'ZMW' } } = {}) => ({ conversionRequestId, @@ -117,9 +135,25 @@ const fxQuotesPostPayloadDto = ({ } }) +const fxQuotesPutPayloadDto = ({ + fxQuotesPostPayload = fxQuotesPostPayloadDto(), + condition = 'mock-condition', + charges = [{ chargeType: 'Tax', sourceAmount: { amount: 1, currency: 'USD' }, targetAmount: { amount: 100, currency: 'ZMW' } }] +} = {}) => { + const dto = { + ...fxQuotesPostPayload, + condition, + charges + } + return dto +} + module.exports = { kafkaMessagePayloadDto, kafkaMessagePayloadPostDto, + kafkaMessageFxPayloadPostDto, + kafkaMessageFxPayloadPutDto, proxyCacheConfigDto, - fxQuotesPostPayloadDto + fxQuotesPostPayloadDto, + fxQuotesPutPayloadDto } From dd5299d14b3f71cb751294d24aa0b2c0b129de41 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Mon, 19 Aug 2024 16:09:04 +0100 Subject: [PATCH 17/28] test: update integration tests --- test/integration/fxQuotes.test.js | 94 +++++++++++++++++++--------- test/integration/postRequest.test.js | 5 ++ test/mocks.js | 25 ++++++-- 3 files changed, 88 insertions(+), 36 deletions(-) diff --git a/test/integration/fxQuotes.test.js b/test/integration/fxQuotes.test.js index c0b889d1..3f583517 100644 --- a/test/integration/fxQuotes.test.js +++ b/test/integration/fxQuotes.test.js @@ -95,6 +95,7 @@ describe('POST /fxQuotes request tests --> ', () => { // assert that the request was received by the proxy const request = response.data.history[0] + expect(request.method).toBe('POST') expect(request.url).toBe(`/${proxyId}/fxQuotes`) expect(request.body).toEqual(payload) expect(request.headers['fspiop-source']).toBe(from) @@ -146,6 +147,7 @@ describe('POST /fxQuotes request tests --> ', () => { // assert that the callback was received by the payer dfsp const request = response.data.history[0] + expect(request.method).toBe('PUT') expect(request.url).toBe(`/${to}/fxQuotes/${payload.conversionRequestId}`) expect(request.body).toEqual(payload) expect(request.headers['fspiop-source']).toBe(from) @@ -183,6 +185,7 @@ describe('POST /fxQuotes request tests --> ', () => { // assert that the request was received by the payee dfsp const request = response.data.history[0] + expect(request.method).toBe('POST') expect(request.url).toBe(`/${to}/fxQuotes`) expect(request.body).toEqual(payload) expect(request.headers['fspiop-source']).toBe(from) @@ -216,6 +219,7 @@ describe('POST /fxQuotes request tests --> ', () => { // assert that the callback was received by the payee dfsp const request = response.data.history[0] + expect(request.method).toBe('PUT') expect(request.url).toBe(`/${to}/fxQuotes/${payload.conversionRequestId}`) expect(request.body).toEqual(payload) expect(request.headers['fspiop-source']).toBe(from) @@ -250,6 +254,7 @@ describe('POST /fxQuotes request tests --> ', () => { // assert that error callback was received by the payer dfsp const request = response.data.history[0] + expect(request.method).toBe('PUT') expect(request.url).toBe(`/${from}/fxQuotes/${payload.conversionRequestId}/error`) expect(request.body.errorInformation.errorCode).toBe('3100') expect(request.body.errorInformation.errorDescription).toBe(`Generic validation error - Unsupported participant '${to}'`) @@ -288,6 +293,7 @@ describe('POST /fxQuotes request tests --> ', () => { // assert that the error callback was received by the payer dfsp const request = response.data.history[0] + expect(request.method).toBe('PUT') expect(request.url).toBe(`/${to}/fxQuotes/${conversionRequestId}/error`) expect(request.body.errorInformation.errorCode).toBe('3100') expect(request.body.errorInformation.errorDescription).toBe('Generic validation error') @@ -341,6 +347,7 @@ describe('POST /fxQuotes request tests --> ', () => { // assert that the error callback was received by the proxy const request = response.data.history[0] + expect(request.method).toBe('PUT') expect(request.url).toBe(`/${proxyId}/fxQuotes/${conversionRequestId}/error`) expect(request.body.errorInformation.errorCode).toBe('3100') expect(request.body.errorInformation.errorDescription).toBe('Generic validation error') @@ -352,21 +359,18 @@ describe('POST /fxQuotes request tests --> ', () => { }) /** - * Produces a GET /fxQuotes request for a dfsp that is registered in the hub - * Expects a PUT /fxQuotes/{ID} callback at the requester's endpoint + * Produces a GET /fxQuotes/{ID} request for a dfsp that is registered in the hub + * Expects a GET /fxQuotes/{ID} request at the destination's endpoint */ - test.only('should GET fx quote (no proxy)', async () => { + test('should GET fx quote (no proxy)', async () => { let response = await hubClient.getHistory() expect(response.data.history.length).toBe(0) const from = 'pinkbank' const to = 'greenbank' const conversionRequestId = uuid() - const payload = mocks.fxQuotesPutPayloadDto({ - fxQuotesPostPayload: mocks.fxQuotesPostPayloadDto({ initiatingFsp: from, counterPartyFsp: to }) - }) - const message = mocks.kafkaMessageFxPayloadPutDto({ from, to, id: conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) - const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.PUT + const message = mocks.kafkaMessageFxPayloadGetDto({ from, to, id: conversionRequestId }) + const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.GET const topicConfig = dto.topicConfigDto({ topicName: topic }) const isOk = await Producer.produceMessage(message, topicConfig, config) @@ -377,32 +381,62 @@ describe('POST /fxQuotes request tests --> ', () => { response = await hubClient.getHistory() expect(response.data.history.length).toBe(1) - // assert that the callback was received by the requester dfsp + // assert that the callback was received by the destination dfsp's endpoint const request = response.data.history[0] - expect(request.url).toBe(`/${from}/fxQuotes/${conversionRequestId}`) - expect(request.body).toEqual(payload) + expect(request.method).toBe('GET') + expect(request.url).toBe(`/${to}/fxQuotes/${conversionRequestId}`) + expect(request.body).toBeUndefined() expect(request.headers['fspiop-source']).toBe(from) expect(request.headers['fspiop-destination']).toBe(to) }) /** - * Test cases to cover: - * + POST fx quote (no proxy) --> PUT fx callback (no proxy) - * + POST fx quote (no proxy) to invalid participant --> Expect put callback error at the sender's endpoint - * - POST quotes (no proxy) --> PUT quotes (no proxy) --> Expect callback received at the sender's endpoint - - * + POST fx quote (proxy) --> PUT fx callback (proxy) - * - POST quotes (proxy) --> PUT quotes (proxy) --> Expect end to end success of fx quote and final quote - * - * ! POST fx quote to invalid participant (proxy) --> Expect put callback error at the proxy endpoint - - * + PUT fx quote error (no proxy) --> Expect error callback at the receiver's endpoint - * + PUT fx quote error (proxy) --> Expect error callback at the proxy endpoint - * - * - GET fx quote (no proxy) --> PUT fx callback (no proxy) --> Expect callback received at the sender's endpoint - * - GET fx quote (proxy) --> PUT fx callback (proxy) --> Expect callback received at the proxy endpoint - * - * - GET fx quote - invalid conversionRequestId --> Expect error callback at the sender's endpoint - * - */ + * Produces a GET /fxQuotes/{ID} for a proxied dfsp + * Expects a GET /fxQuotes/{ID} request at the proxy's endpoint + */ + test('should GET fx quote (proxied)', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const from = 'pinkbank' + const to = 'redbank' + const proxyId = 'redbankproxy' + let proxyClient + + try { + proxyClient = createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + + // register proxy representative for redbank + const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) + + // assert that the proxy representative is mapped in the cache + const key = `dfsp:${to}` + const proxy = await proxyClient.redisClient.get(key) + expect(isAdded).toBe(true) + expect(proxy).toBe(proxyId) + + const conversionRequestId = uuid() + const message = mocks.kafkaMessageFxPayloadGetDto({ from, to, id: conversionRequestId }) + const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.GET + const topicConfig = dto.topicConfigDto({ topicName: topic }) + + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + // assert that the callback was received by the proxy + const request = response.data.history[0] + expect(request.method).toBe('GET') + expect(request.url).toBe(`/${proxyId}/fxQuotes/${conversionRequestId}`) + expect(request.body).toBeUndefined() + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + } finally { + await proxyClient.disconnect() + } + }) }) diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index 048aa8d8..fbfb10ab 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -361,3 +361,8 @@ describe('POST request tests --> ', () => { } }) }) + +/** + - POST quotes (no proxy) --> PUT quotes (no proxy) --> Expect callback received at the sender's endpoint + - POST quotes (proxy) --> PUT quotes (proxy) --> Expect end to end success of fx quote and final quote + */ diff --git a/test/mocks.js b/test/mocks.js index 7a89986f..635e70dc 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -79,22 +79,34 @@ const kafkaMessagePayloadPostDto = (params = {}) => kafkaMessagePayloadDto({ const kafkaMessageFxPayloadPostDto = (params = {}) => kafkaMessagePayloadDto({ ...params, + fspiopVersion: '2.0', action: 'post', type: 'fxquote', operationId: 'FxQuotesPost' }) const kafkaMessageFxPayloadPutDto = (params = {}) => { - const dto = kafkaMessagePayloadDto({ - ...params, - action: 'put', - type: 'fxquote', - operationId: 'FxQuotesPut' - }) + const dto = { + ...kafkaMessagePayloadDto({ + ...params, + fspiopVersion: '2.0', + action: 'put', + type: 'fxquote', + operationId: 'FxQuotesPut' + }) + } delete dto.content.headers.accept return dto } +const kafkaMessageFxPayloadGetDto = (params = {}) => kafkaMessagePayloadDto({ + ...params, + fspiopVersion: '2.0', + action: 'get', + type: 'fxquote', + operationId: 'FxQuotesGet' +}) + const proxyCacheConfigDto = ({ type = 'redis' } = {}) => Object.freeze({ @@ -153,6 +165,7 @@ module.exports = { kafkaMessagePayloadPostDto, kafkaMessageFxPayloadPostDto, kafkaMessageFxPayloadPutDto, + kafkaMessageFxPayloadGetDto, proxyCacheConfigDto, fxQuotesPostPayloadDto, fxQuotesPutPayloadDto From 0e710ed60eb46485e051c25c80f5b3fb36a8653d Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Mon, 19 Aug 2024 16:42:35 +0100 Subject: [PATCH 18/28] test: add retry logic to tests --- test/integration/fxQuotes.test.js | 91 +++++++++++++++++++--------- test/integration/postRequest.test.js | 6 +- test/integration/putCallback.test.js | 8 +-- test/unit/lib/proxy.test.js | 2 +- 4 files changed, 70 insertions(+), 37 deletions(-) diff --git a/test/integration/fxQuotes.test.js b/test/integration/fxQuotes.test.js index 3f583517..aa976c7e 100644 --- a/test/integration/fxQuotes.test.js +++ b/test/integration/fxQuotes.test.js @@ -34,13 +34,24 @@ const Config = require('../../src/lib/config') const MockServerClient = require('./mockHttpServer/MockServerClient') const dto = require('../../src/lib/dto') const mocks = require('../mocks') +const { wrapWithRetries } = require('../util/helper') const TEST_TIMEOUT = 20_000 -const WAIT_TIMEOUT = 3_000 const hubClient = new MockServerClient() const base64Encode = (data) => Buffer.from(data).toString('base64') -const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)) + +const retryDelay = process?.env?.TEST_INT_RETRY_DELAY || 1 +const retryCount = process?.env?.TEST_INT_RETRY_COUNT || 20 +const retryOpts = { + retries: retryCount, + minTimeout: retryDelay, + maxTimeout: retryDelay +} +const wrapWithRetriesConf = { + remainingRetries: retryOpts?.retries || 10, // default 10 + timeout: retryOpts?.maxTimeout || 2 // default 2 +} describe('POST /fxQuotes request tests --> ', () => { jest.setTimeout(TEST_TIMEOUT) @@ -88,9 +99,11 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - await wait(WAIT_TIMEOUT) - - response = await hubClient.getHistory() + response = await wrapWithRetries(() => hubClient.getHistory(), + wrapWithRetriesConf.remainingRetries, + wrapWithRetriesConf.timeout, + (result) => result.data.history.length > 0 + ) expect(response.data.history.length).toBe(1) // assert that the request was received by the proxy @@ -101,6 +114,7 @@ describe('POST /fxQuotes request tests --> ', () => { expect(request.headers['fspiop-source']).toBe(from) expect(request.headers['fspiop-destination']).toBe(to) } finally { + await proxyClient.removeDfspIdFromProxyMapping(to) await proxyClient.disconnect() } }) @@ -140,9 +154,11 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - await wait(WAIT_TIMEOUT) - - response = await hubClient.getHistory() + response = await wrapWithRetries(() => hubClient.getHistory(), + wrapWithRetriesConf.remainingRetries, + wrapWithRetriesConf.timeout, + (result) => result.data.history.length > 0 + ) expect(response.data.history.length).toBe(1) // assert that the callback was received by the payer dfsp @@ -153,6 +169,7 @@ describe('POST /fxQuotes request tests --> ', () => { expect(request.headers['fspiop-source']).toBe(from) expect(request.headers['fspiop-destination']).toBe(to) } finally { + await proxyClient.removeDfspIdFromProxyMapping(from) await proxyClient.disconnect() } }) @@ -178,9 +195,11 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - await wait(WAIT_TIMEOUT) - - response = await hubClient.getHistory() + response = await wrapWithRetries(() => hubClient.getHistory(), + wrapWithRetriesConf.remainingRetries, + wrapWithRetriesConf.timeout, + (result) => result.data.history.length > 0 + ) expect(response.data.history.length).toBe(1) // assert that the request was received by the payee dfsp @@ -212,9 +231,11 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - await wait(WAIT_TIMEOUT) - - response = await hubClient.getHistory() + response = await wrapWithRetries(() => hubClient.getHistory(), + wrapWithRetriesConf.remainingRetries, + wrapWithRetriesConf.timeout, + (result) => result.data.history.length > 0 + ) expect(response.data.history.length).toBe(1) // assert that the callback was received by the payee dfsp @@ -247,9 +268,11 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - await wait(WAIT_TIMEOUT) - - response = await hubClient.getHistory() + response = await wrapWithRetries(() => hubClient.getHistory(), + wrapWithRetriesConf.remainingRetries, + wrapWithRetriesConf.timeout, + (result) => result.data.history.length > 0 + ) expect(response.data.history.length).toBe(1) // assert that error callback was received by the payer dfsp @@ -286,9 +309,11 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - await wait(WAIT_TIMEOUT) - - response = await hubClient.getHistory() + response = await wrapWithRetries(() => hubClient.getHistory(), + wrapWithRetriesConf.remainingRetries, + wrapWithRetriesConf.timeout, + (result) => result.data.history.length > 0 + ) expect(response.data.history.length).toBe(1) // assert that the error callback was received by the payer dfsp @@ -340,9 +365,11 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - await wait(WAIT_TIMEOUT) - - response = await hubClient.getHistory() + response = await wrapWithRetries(() => hubClient.getHistory(), + wrapWithRetriesConf.remainingRetries, + wrapWithRetriesConf.timeout, + (result) => result.data.history.length > 0 + ) expect(response.data.history.length).toBe(1) // assert that the error callback was received by the proxy @@ -354,6 +381,7 @@ describe('POST /fxQuotes request tests --> ', () => { expect(request.headers['fspiop-source']).toBe(from) expect(request.headers['fspiop-destination']).toBe(to) } finally { + await proxyClient.removeDfspIdFromProxyMapping(to) await proxyClient.disconnect() } }) @@ -376,9 +404,11 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - await wait(WAIT_TIMEOUT) - - response = await hubClient.getHistory() + response = await wrapWithRetries(() => hubClient.getHistory(), + wrapWithRetriesConf.remainingRetries, + wrapWithRetriesConf.timeout, + (result) => result.data.history.length > 0 + ) expect(response.data.history.length).toBe(1) // assert that the callback was received by the destination dfsp's endpoint @@ -423,9 +453,11 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - await wait(WAIT_TIMEOUT) - - response = await hubClient.getHistory() + response = await wrapWithRetries(() => hubClient.getHistory(), + wrapWithRetriesConf.remainingRetries, + wrapWithRetriesConf.timeout, + (result) => result.data.history.length > 0 + ) expect(response.data.history.length).toBe(1) // assert that the callback was received by the proxy @@ -436,6 +468,7 @@ describe('POST /fxQuotes request tests --> ', () => { expect(request.headers['fspiop-source']).toBe(from) expect(request.headers['fspiop-destination']).toBe(to) } finally { + await proxyClient.removeDfspIdFromProxyMapping(to) await proxyClient.disconnect() } }) diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index fbfb10ab..766c8a35 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -105,7 +105,7 @@ describe('POST request tests --> ', () => { const to = 'greenbank' let proxyClient try { - proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + proxyClient = createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) const { topic, config } = kafkaConfig.PRODUCER.QUOTE.POST const topicConfig = dto.topicConfigDto({ topicName: topic }) @@ -257,7 +257,7 @@ describe('POST request tests --> ', () => { let proxyClient try { - proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + proxyClient = createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) // assert that the proxy representative is mapped in the cache @@ -314,7 +314,7 @@ describe('POST request tests --> ', () => { let proxyClient try { - proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + proxyClient = createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) // assert that the proxy representative is mapped in the cache diff --git a/test/integration/putCallback.test.js b/test/integration/putCallback.test.js index 1b1124b5..124d35fa 100644 --- a/test/integration/putCallback.test.js +++ b/test/integration/putCallback.test.js @@ -178,7 +178,7 @@ describe('PUT callback Tests --> ', () => { const proxyId = 'greenbankproxy' let proxyClient try { - proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + proxyClient = createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) const isAdded = await proxyClient.addDfspIdToProxyMapping(from, proxyId) expect(isAdded).toBe(true) const payload = { @@ -300,7 +300,7 @@ describe('PUT callback Tests --> ', () => { let proxyClient try { - proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + proxyClient = createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) // assert that the proxy representative is mapped in the cache @@ -381,7 +381,7 @@ describe('PUT callback Tests --> ', () => { let proxyClient try { - proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + proxyClient = createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) // assert that the proxy representative is mapped in the cache @@ -471,7 +471,7 @@ describe('PUT callback Tests --> ', () => { let proxyClient try { - proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + proxyClient = createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) // assert that the proxy representative is mapped in the cache diff --git a/test/unit/lib/proxy.test.js b/test/unit/lib/proxy.test.js index b7ecae9b..42ecf66f 100644 --- a/test/unit/lib/proxy.test.js +++ b/test/unit/lib/proxy.test.js @@ -53,7 +53,7 @@ describe('createProxyClient', () => { }) it('should create a proxy client and return it', async () => { - const proxyClient = await createProxyClient({ proxyCacheConfig }) + const proxyClient = createProxyClient({ proxyCacheConfig }) expect(proxyClient).toBeDefined() expect(proxyClient.isConnected).toBe(true) From e22b0c1f557374daa5f8fb0ec6fa65dd65f1d261 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Mon, 19 Aug 2024 16:56:28 +0100 Subject: [PATCH 19/28] test: simplify retry conf --- test/integration/fxQuotes.test.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/test/integration/fxQuotes.test.js b/test/integration/fxQuotes.test.js index aa976c7e..b667edfe 100644 --- a/test/integration/fxQuotes.test.js +++ b/test/integration/fxQuotes.test.js @@ -41,16 +41,9 @@ const TEST_TIMEOUT = 20_000 const hubClient = new MockServerClient() const base64Encode = (data) => Buffer.from(data).toString('base64') -const retryDelay = process?.env?.TEST_INT_RETRY_DELAY || 1 -const retryCount = process?.env?.TEST_INT_RETRY_COUNT || 20 -const retryOpts = { - retries: retryCount, - minTimeout: retryDelay, - maxTimeout: retryDelay -} const wrapWithRetriesConf = { - remainingRetries: retryOpts?.retries || 10, // default 10 - timeout: retryOpts?.maxTimeout || 2 // default 2 + remainingRetries: process?.env?.TEST_INT_RETRY_COUNT || 20, + timeout: process?.env?.TEST_INT_RETRY_DELAY || 1 } describe('POST /fxQuotes request tests --> ', () => { From ecbe367825f779cd6e8707886629fb16f46b6929 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Mon, 19 Aug 2024 17:40:54 +0100 Subject: [PATCH 20/28] test: refactor tests --- test/integration/fxQuotes.test.js | 56 ++++----- test/integration/postRequest.test.js | 177 ++++++++++++++------------- test/integration/putCallback.test.js | 65 +++++----- test/mocks.js | 96 ++++++++++----- 4 files changed, 217 insertions(+), 177 deletions(-) diff --git a/test/integration/fxQuotes.test.js b/test/integration/fxQuotes.test.js index b667edfe..02164bf0 100644 --- a/test/integration/fxQuotes.test.js +++ b/test/integration/fxQuotes.test.js @@ -36,16 +36,16 @@ const dto = require('../../src/lib/dto') const mocks = require('../mocks') const { wrapWithRetries } = require('../util/helper') -const TEST_TIMEOUT = 20_000 - const hubClient = new MockServerClient() const base64Encode = (data) => Buffer.from(data).toString('base64') -const wrapWithRetriesConf = { +const retryConf = { remainingRetries: process?.env?.TEST_INT_RETRY_COUNT || 20, timeout: process?.env?.TEST_INT_RETRY_DELAY || 1 } +const TEST_TIMEOUT = 20_000 + describe('POST /fxQuotes request tests --> ', () => { jest.setTimeout(TEST_TIMEOUT) @@ -82,7 +82,7 @@ describe('POST /fxQuotes request tests --> ', () => { expect(isAdded).toBe(true) expect(proxy).toBe(proxyId) - const payload = mocks.fxQuotesPostPayloadDto({ + const payload = mocks.postFxQuotesPayloadDto({ initiatingFsp: from, counterPartyFsp: to }) @@ -93,8 +93,8 @@ describe('POST /fxQuotes request tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -137,8 +137,8 @@ describe('POST /fxQuotes request tests --> ', () => { expect(isAdded).toBe(true) expect(proxy).toBe(proxyId) - const payload = mocks.fxQuotesPutPayloadDto({ - fxQuotesPostPayload: mocks.fxQuotesPostPayloadDto({ initiatingFsp: to, counterPartyFsp: from }) + const payload = mocks.putFxQuotesPayloadDto({ + fxQuotesPostPayload: mocks.postFxQuotesPayloadDto({ initiatingFsp: to, counterPartyFsp: from }) }) const message = mocks.kafkaMessageFxPayloadPutDto({ from, to, id: payload.conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.PUT @@ -148,8 +148,8 @@ describe('POST /fxQuotes request tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -177,7 +177,7 @@ describe('POST /fxQuotes request tests --> ', () => { const from = 'pinkbank' const to = 'greenbank' - const payload = mocks.fxQuotesPostPayloadDto({ + const payload = mocks.postFxQuotesPayloadDto({ initiatingFsp: from, counterPartyFsp: to }) @@ -189,8 +189,8 @@ describe('POST /fxQuotes request tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -214,8 +214,8 @@ describe('POST /fxQuotes request tests --> ', () => { const from = 'greenbank' const to = 'pinkbank' - const payload = mocks.fxQuotesPutPayloadDto({ - fxQuotesPostPayload: mocks.fxQuotesPostPayloadDto({ initiatingFsp: to, counterPartyFsp: from }) + const payload = mocks.putFxQuotesPayloadDto({ + fxQuotesPostPayload: mocks.postFxQuotesPayloadDto({ initiatingFsp: to, counterPartyFsp: from }) }) const message = mocks.kafkaMessageFxPayloadPutDto({ from, to, id: payload.conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.PUT @@ -225,8 +225,8 @@ describe('POST /fxQuotes request tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -250,7 +250,7 @@ describe('POST /fxQuotes request tests --> ', () => { const from = 'pinkbank' const to = 'invalidbank' - const payload = mocks.fxQuotesPostPayloadDto({ + const payload = mocks.postFxQuotesPayloadDto({ initiatingFsp: from, counterPartyFsp: to }) @@ -262,8 +262,8 @@ describe('POST /fxQuotes request tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -303,8 +303,8 @@ describe('POST /fxQuotes request tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -359,8 +359,8 @@ describe('POST /fxQuotes request tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -398,8 +398,8 @@ describe('POST /fxQuotes request tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -447,8 +447,8 @@ describe('POST /fxQuotes request tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index 766c8a35..eeb1a63c 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -36,23 +36,16 @@ const MockServerClient = require('./mockHttpServer/MockServerClient') const uuid = require('crypto').randomUUID const { wrapWithRetries } = require('../util/helper') -const TEST_TIMEOUT = 20_000 - const hubClient = new MockServerClient() - const base64Encode = (data) => Buffer.from(data).toString('base64') -const retryDelay = process?.env?.TEST_INT_RETRY_DELAY || 1 -const retryCount = process?.env?.TEST_INT_RETRY_COUNT || 20 -const retryOpts = { - retries: retryCount, - minTimeout: retryDelay, - maxTimeout: retryDelay -} -const wrapWithRetriesConf = { - remainingRetries: retryOpts?.retries || 10, // default 10 - timeout: retryOpts?.maxTimeout || 2 // default 2 + +const retryConf = { + remainingRetries: process?.env?.TEST_INT_RETRY_COUNT || 20, + timeout: process?.env?.TEST_INT_RETRY_DELAY || 1 } +const TEST_TIMEOUT = 20_000 + describe('POST request tests --> ', () => { jest.setTimeout(TEST_TIMEOUT) @@ -74,22 +67,14 @@ describe('POST request tests --> ', () => { const topicConfig = dto.topicConfigDto({ topicName: topic }) const from = 'pinkbank' const to = 'greenbank' - const payload = { - quoteId: uuid(), - transactionId: uuid(), - amountType: 'SEND', - amount: { amount: '100', currency: 'USD' }, - transactionType: { scenario: 'DEPOSIT', initiator: 'PAYER', initiatorType: 'CONSUMER' }, - payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from } }, - payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } } - } + const payload = mocks.postQuotesPayloadDto({ from, to }) const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) @@ -113,22 +98,14 @@ describe('POST request tests --> ', () => { const proxyId2 = 'proxyRB' await proxyClient.addDfspIdToProxyMapping(to, proxyId1) await proxyClient.addDfspIdToProxyMapping(from, proxyId2) - const payload = { - quoteId: uuid(), - transactionId: uuid(), - amountType: 'SEND', - amount: { amount: '100', currency: 'USD' }, - transactionType: { scenario: 'DEPOSIT', initiator: 'PAYER', initiatorType: 'CONSUMER' }, - payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from } }, - payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } } - } + const payload = mocks.postQuotesPayloadDto({ from, to }) const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -150,22 +127,14 @@ describe('POST request tests --> ', () => { const topicConfig = dto.topicConfigDto({ topicName: topic }) const from = 'pinkbank' const to = 'greenbank' - const payload = { - quoteId: uuid(), - transactionId: uuid(), - amountType: 'SEND', - amount: { amount: '100', currency: 'GBP' }, - transactionType: { scenario: 'DEPOSIT', initiator: 'PAYER', initiatorType: 'CONSUMER' }, - payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from } }, - payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } } - } + const payload = mocks.postQuotesPayloadDto({ from, to, amount: { amount: '100', currency: 'GBP' } }) const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -184,22 +153,18 @@ describe('POST request tests --> ', () => { const topicConfig = dto.topicConfigDto({ topicName: topic }) const from = 'pinkbank' const to = 'greenbank' - const payload = { - quoteId: uuid(), - transactionId: uuid(), - amountType: 'SEND', - amount: { amount: '100', currency: 'USD' }, - transactionType: { scenario: 'DEPOSIT', initiator: 'PAYER', initiatorType: 'CONSUMER' }, - payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from }, supportedCurrencies: ['USD', 'ZMW'] }, - payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } } - } + const payload = mocks.postQuotesPayloadDto({ + from, + to, + payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from }, supportedCurrencies: ['USD', 'ZMW'] } + }) const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -216,22 +181,18 @@ describe('POST request tests --> ', () => { const topicConfig = dto.topicConfigDto({ topicName: topic }) const from = 'pinkbank' const to = 'greenbank' - const payload = { - quoteId: uuid(), - transactionId: uuid(), - amountType: 'SEND', - amount: { amount: '100', currency: 'USD' }, - transactionType: { scenario: 'DEPOSIT', initiator: 'PAYER', initiatorType: 'CONSUMER' }, - payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from }, supportedCurrencies: ['USD', 'ZMW', 'GBP'] }, - payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } } - } + const payload = mocks.postQuotesPayloadDto({ + from, + to, + payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from }, supportedCurrencies: ['USD', 'ZMW', 'GBP'] } + }) const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -242,6 +203,35 @@ describe('POST request tests --> ', () => { expect(body.errorInformation.errorDescription).toBe(`Payer FSP ID not found - Unsupported participant '${message.from}'`) }) + test('should forward POST /quotes request to payee dfsp registered in the hub', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const { topic, config } = kafkaConfig.PRODUCER.QUOTE.POST + const topicConfig = dto.topicConfigDto({ topicName: topic }) + const from = 'pinkbank' + const to = 'greenbank' + + const payload = mocks.postQuotesPayloadDto({ from, to }) + const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + response = await wrapWithRetries(() => hubClient.getHistory(), + retryConf.remainingRetries, + retryConf.timeout, + (result) => result.data.history.length > 0 + ) + expect([1, 2]).toContain(response.data.history.length) + + const request = response.data.history[0] + expect(request.method).toBe('POST') + expect(request.url).toBe(`/${to}/quotes`) + expect(request.body).toEqual(payload) + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + }) + test('should forward POST /quotes request to proxy if the payee dfsp is not registered in the hub', async () => { let response = await hubClient.getHistory() expect(response.data.history.length).toBe(0) @@ -267,22 +257,14 @@ describe('POST request tests --> ', () => { expect(isAdded).toBe(true) expect(representative).toBe(proxyId) - const payload = { - quoteId: uuid(), - transactionId: uuid(), - amountType: 'SEND', - amount: { amount: '100', currency: 'USD' }, - transactionType: { scenario: 'DEPOSIT', initiator: 'PAYER', initiatorType: 'CONSUMER' }, - payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from } }, - payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } } - } + const payload = mocks.postQuotesPayloadDto({ from, to }) const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect([1, 2]).toContain(response.data.history.length) @@ -299,6 +281,34 @@ describe('POST request tests --> ', () => { } }) + test('should forward POST /bulkQuotes request the payee dfsp registered in the hub', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const { topic, config } = kafkaConfig.PRODUCER.BULK_QUOTE.POST + const topicConfig = dto.topicConfigDto({ topicName: topic }) + const from = 'pinkbank' + const to = 'greenbank' + + const payload = mocks.postBulkQuotesPayloadDto({ from, to }) + const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + response = await wrapWithRetries(() => hubClient.getHistory(), + retryConf.remainingRetries, + retryConf.timeout, + (result) => result.data.history.length > 0 + ) + expect(response.data.history.length).toBe(1) + + const request = response.data.history[0] + expect(request.url).toBe(`/${to}/bulkQuotes`) + expect(request.body).toEqual(payload) + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + }) + test('should forward POST /bulkQuotes request to proxy if the payee dfsp is not registered in the hub', async () => { let response = await hubClient.getHistory() expect(response.data.history.length).toBe(0) @@ -343,8 +353,8 @@ describe('POST request tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -361,8 +371,3 @@ describe('POST request tests --> ', () => { } }) }) - -/** - - POST quotes (no proxy) --> PUT quotes (no proxy) --> Expect callback received at the sender's endpoint - - POST quotes (proxy) --> PUT quotes (proxy) --> Expect end to end success of fx quote and final quote - */ diff --git a/test/integration/putCallback.test.js b/test/integration/putCallback.test.js index 124d35fa..517c8668 100644 --- a/test/integration/putCallback.test.js +++ b/test/integration/putCallback.test.js @@ -39,23 +39,18 @@ const uuid = require('crypto').randomUUID const { wrapWithRetries } = require('../util/helper') const hubClient = new MockServerClient() -const base64Encode = (data) => Buffer.from(data).toString('base64') -const TEST_TIMEOUT = 20_000 -const WAIT_TIMEOUT = 3_000 +const base64Encode = (data) => Buffer.from(data).toString('base64') const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)) -const retryDelay = process?.env?.TEST_INT_RETRY_DELAY || 1 -const retryCount = process?.env?.TEST_INT_RETRY_COUNT || 20 -const retryOpts = { - retries: retryCount, - minTimeout: retryDelay, - maxTimeout: retryDelay -} -const wrapWithRetriesConf = { - remainingRetries: retryOpts?.retries || 10, // default 10 - timeout: retryOpts?.maxTimeout || 2 // default 2 + +const retryConf = { + remainingRetries: process?.env?.TEST_INT_RETRY_COUNT || 20, + timeout: process?.env?.TEST_INT_RETRY_DELAY || 1 } +const TEST_TIMEOUT = 20_000 +const WAIT_TIMEOUT = 3_000 + /** * Publishes a test 'POST quote' message to the Kafka topic */ @@ -111,8 +106,8 @@ describe('PUT callback Tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) @@ -153,8 +148,8 @@ describe('PUT callback Tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -198,8 +193,8 @@ describe('PUT callback Tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -240,8 +235,8 @@ describe('PUT callback Tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -270,8 +265,8 @@ describe('PUT callback Tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -330,8 +325,8 @@ describe('PUT callback Tests --> ', () => { expect(postIsOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect([1, 2]).toContain(response.data.history.length) @@ -348,8 +343,8 @@ describe('PUT callback Tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect([1, 2]).toContain(response.data.history.length) @@ -413,8 +408,8 @@ describe('PUT callback Tests --> ', () => { expect(postIsOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -438,8 +433,8 @@ describe('PUT callback Tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -502,8 +497,8 @@ describe('PUT callback Tests --> ', () => { expect(postIsOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) @@ -530,8 +525,8 @@ describe('PUT callback Tests --> ', () => { expect(isOk).toBe(true) response = await wrapWithRetries(() => hubClient.getHistory(), - wrapWithRetriesConf.remainingRetries, - wrapWithRetriesConf.timeout, + retryConf.remainingRetries, + retryConf.timeout, (result) => result.data.history.length > 0 ) expect(response.data.history.length).toBe(1) diff --git a/test/mocks.js b/test/mocks.js index 635e70dc..9dcddf17 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -4,6 +4,21 @@ const Config = new (require('../src/lib/config'))() const CONTENT_TYPE = 'application/vnd.interoperability.quotes+json;version={{API_VERSION}}' const contentTypeFn = ({ fspiopVersion = 1.0 }) => CONTENT_TYPE.replace('{{API_VERSION}}', fspiopVersion) +const proxyCacheConfigDto = ({ + type = 'redis' +} = {}) => Object.freeze({ + type, + proxyConfig: { + ...(type === 'redis' && { + host: 'localhost', port: 6379 + }), + ...(type === 'redis-cluster' && { + cluster: [{ host: 'localhost', port: 6379 }] + }) + }, + timeout: 5000 // is it used anywhere? +}) + const kafkaMessagePayloadDto = ({ action = 'put', from = Config.hubName, @@ -107,22 +122,7 @@ const kafkaMessageFxPayloadGetDto = (params = {}) => kafkaMessagePayloadDto({ operationId: 'FxQuotesGet' }) -const proxyCacheConfigDto = ({ - type = 'redis' -} = {}) => Object.freeze({ - type, - proxyConfig: { - ...(type === 'redis' && { - host: 'localhost', port: 6379 - }), - ...(type === 'redis-cluster' && { - cluster: [{ host: 'localhost', port: 6379 }] - }) - }, - timeout: 5000 // is it used anywhere? -}) - -const fxQuotesPostPayloadDto = ({ +const postFxQuotesPayloadDto = ({ conversionRequestId = uuid(), conversionId = uuid(), initiatingFsp = 'pinkbank', @@ -147,18 +147,56 @@ const fxQuotesPostPayloadDto = ({ } }) -const fxQuotesPutPayloadDto = ({ - fxQuotesPostPayload = fxQuotesPostPayloadDto(), +const putFxQuotesPayloadDto = ({ + fxQuotesPostPayload = postFxQuotesPayloadDto(), condition = 'mock-condition', charges = [{ chargeType: 'Tax', sourceAmount: { amount: 1, currency: 'USD' }, targetAmount: { amount: 100, currency: 'ZMW' } }] -} = {}) => { - const dto = { - ...fxQuotesPostPayload, - condition, - charges - } - return dto -} +} = {}) => ({ + ...fxQuotesPostPayload, + condition, + charges +}) + +const postQuotesPayloadDto = ({ + from = 'payer', + to = 'payee', + quoteId = uuid(), + transactionId = uuid(), + amountType = 'SEND', + amount = { amount: '100', currency: 'USD' }, + transactionType = { scenario: 'DEPOSIT', initiator: 'PAYER', initiatorType: 'CONSUMER' }, + payer = { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from } }, + payee = { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } } +} = {}) => ({ + quoteId, + transactionId, + amountType, + amount, + transactionType, + payer, + payee +}) + +const postBulkQuotesPayloadDto = ({ + from = 'payer', + to = 'payee', + bulkQuoteId = uuid(), + payer = { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from } }, + individualQuotes = [ + { + quoteId: uuid(), + transactionId: uuid(), + payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } }, + amountType: 'SEND', + amount: { amount: '100', currency: 'USD' }, + transactionType: { scenario: 'DEPOSIT', initiator: 'PAYER', initiatorType: 'CONSUMER' } + } + ] +} = {}) => ({ + bulkQuoteId, + payer, + individualQuotes +}) module.exports = { kafkaMessagePayloadDto, @@ -167,6 +205,8 @@ module.exports = { kafkaMessageFxPayloadPutDto, kafkaMessageFxPayloadGetDto, proxyCacheConfigDto, - fxQuotesPostPayloadDto, - fxQuotesPutPayloadDto + postFxQuotesPayloadDto, + putFxQuotesPayloadDto, + postQuotesPayloadDto, + postBulkQuotesPayloadDto } From 17a0203f9589d1d6989513f17c8f530a90ffc689 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Mon, 19 Aug 2024 18:46:21 +0100 Subject: [PATCH 21/28] refactor: minor refactor --- src/model/fxQuotes.js | 6 +++--- test/unit/model/quotes.test.js | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/model/fxQuotes.js b/src/model/fxQuotes.js index 112326e7..3a17ef2f 100644 --- a/src/model/fxQuotes.js +++ b/src/model/fxQuotes.js @@ -254,9 +254,9 @@ class FxQuotesModel { await this.forwardFxQuoteGet(headers, conversionRequestId, childSpan) histTimer({ success: true, queryName: 'handleFxQuoteGet' }) } catch (err) { + histTimer({ success: false, queryName: 'handleFxQuoteGet' }) this.log.error('error in handleFxQuoteGet', err) await this.handleException(fspiopSource, conversionRequestId, err, headers, childSpan) - histTimer({ success: false, queryName: 'handleFxQuoteGet' }) } finally { if (childSpan && !childSpan.isFinished) { await childSpan.finish() @@ -325,9 +325,9 @@ class FxQuotesModel { await this.sendErrorCallback(headers[ENUM.Http.Headers.FSPIOP.DESTINATION], fspiopError, conversionRequestId, headers, childSpan, false) histTimer({ success: true, queryName: 'handleFxQuoteError' }) } catch (err) { + histTimer({ success: false, queryName: 'handleFxQuoteError' }) this.log.error('error in handleFxQuoteError', err) await this.handleException(fspiopSource, conversionRequestId, err, headers, childSpan) - histTimer({ success: false, queryName: 'handleFxQuoteError' }) } finally { if (childSpan && !childSpan.isFinished) { await childSpan.finish() @@ -353,8 +353,8 @@ class FxQuotesModel { await this.sendErrorCallback(fspiopSource, fspiopError, conversionRequestId, headers, childSpan, true) histTimer({ success: true, queryName: 'handleException' }) } catch (err) { - this.log.error('error in handleException, stop request processing!', err) histTimer({ success: false, queryName: 'handleException' }) + this.log.error('error in handleException, stop request processing!', err) } finally { if (!childSpan.isFinished) { await childSpan.finish() diff --git a/test/unit/model/quotes.test.js b/test/unit/model/quotes.test.js index 2eee2e4a..86583a58 100644 --- a/test/unit/model/quotes.test.js +++ b/test/unit/model/quotes.test.js @@ -1091,16 +1091,13 @@ describe('QuotesModel', () => { it('calls handleQuoteRequestResend if request is duplicate and should resend', async () => { expect.assertions(1) - quotesModel.checkDuplicateQuoteRequest = jest.fn(() => { return { isDuplicateId: true, isResend: true } }) - await quotesModel.handleQuoteRequest(mockData.headers, mockData.quoteRequest, mockSpan) - expect(quotesModel.handleQuoteRequestResend).toHaveBeenCalledTimes(1) }) }) From bc21082f127e35308111f36eba38b4e2ed4af04a Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Mon, 19 Aug 2024 18:55:54 +0100 Subject: [PATCH 22/28] ci: remove debug run --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 5593a1df..c1ec2c92 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -61,7 +61,7 @@ services: quoting-service-handler: <<: *quotingServiceBase - command: npm run start:handlers:debug + command: npm run start:handlers ports: - "3003:3003" - "29229:9229" From 68a3a80455d80827c6d141cfa802609e4a9b9a06 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 20 Aug 2024 17:14:32 +0100 Subject: [PATCH 23/28] test: update fxquotes test - persistent mode --- config/default.json | 2 +- docker-compose.yml | 4 +- src/data/cachedDatabase.js | 2 +- test/integration/fxQuotes.test.js | 128 ++++++++++++++------------- test/integration/putCallback.test.js | 8 +- test/mocks.js | 14 +-- 6 files changed, 84 insertions(+), 74 deletions(-) diff --git a/config/default.json b/config/default.json index b6395f58..7f739be3 100644 --- a/config/default.json +++ b/config/default.json @@ -53,7 +53,7 @@ "includeCauseExtension": false, "truncateExtensions": true }, - "SIMPLE_ROUTING_MODE": true, + "SIMPLE_ROUTING_MODE": false, "ENDPOINT_SECURITY":{ "JWS": { "JWS_SIGN": false, diff --git a/docker-compose.yml b/docker-compose.yml index 44b39e5f..c40f1391 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3.7" - x-depends-on: &dependsOnMysqlAndKafka mysql: condition: service_healthy @@ -62,7 +60,7 @@ services: quoting-service-handler: <<: *quotingServiceBase - command: npm run start:handlers + command: npm run start:handlers:debug ports: - "3003:3003" - "29229:9229" diff --git a/src/data/cachedDatabase.js b/src/data/cachedDatabase.js index e155264d..dcf7b30b 100644 --- a/src/data/cachedDatabase.js +++ b/src/data/cachedDatabase.js @@ -127,7 +127,7 @@ class CachedDatabase extends Database { } histTimer({ success: true, queryName: type, hit: false }) } else { - this.log.error('Cache hit for : ', { type, params, value }) + this.log.debug('Cache hit for : ', { type, params, value }) histTimer({ success: true, queryName: type, hit: true }) } diff --git a/test/integration/fxQuotes.test.js b/test/integration/fxQuotes.test.js index 60f57693..d6f1453b 100644 --- a/test/integration/fxQuotes.test.js +++ b/test/integration/fxQuotes.test.js @@ -27,7 +27,6 @@ -------------- ******/ -const uuid = require('crypto').randomUUID const { Producer } = require('@mojaloop/central-services-stream').Util const { createProxyClient } = require('../../src/lib/proxy') const Config = require('../../src/lib/config') @@ -68,6 +67,27 @@ describe('POST /fxQuotes request tests --> ', () => { await db?.disconnect() await Producer.disconnect() }) + + const createFxQuote = async (from, to, payload) => { + const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.POST + const topicConfig = dto.topicConfigDto({ topicName: topic }) + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + const response = await getResponseWithRetry() + expect(response.data.history.length).toBe(1) + await hubClient.clearHistory() + } + + const getResponseWithRetry = async () => { + return wrapWithRetries(() => hubClient.getHistory(), + retryConf.remainingRetries, + retryConf.timeout, + (result) => result.data.history.length > 0 + ) + } + /** * Produces a POST /fxQuotes message for a dfsp that is not registered in the hub */ @@ -102,11 +122,7 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) // assert that the request was received by the proxy @@ -166,21 +182,19 @@ describe('POST /fxQuotes request tests --> ', () => { expect(isAdded).toBe(true) expect(proxy).toBe(proxyId) - const payload = mocks.putFxQuotesPayloadDto({ - fxQuotesPostPayload: mocks.postFxQuotesPayloadDto({ initiatingFsp: to, counterPartyFsp: from }) - }) + // create subject fxquote + const fxQuotesPostPayload = mocks.postFxQuotesPayloadDto({ initiatingFsp: to, counterPartyFsp: from }) + await createFxQuote(to, from, fxQuotesPostPayload) + + // send put callback + const payload = mocks.putFxQuotesPayloadDto({ fxQuotesPostPayload }) const message = mocks.kafkaMessageFxPayloadPutDto({ from, to, id: payload.conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.PUT const topicConfig = dto.topicConfigDto({ topicName: topic }) - const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) // assert that the callback was received by the payer dfsp @@ -200,7 +214,7 @@ describe('POST /fxQuotes request tests --> ', () => { * Produces a POST /fxQuotes message for a dfsp that is registered in the hub * Expects a POST /fxQuotes request at the payee dfsp's endpoint */ - test('should POST fx quote (no proxy)', async () => { + test('should POST fxquote (no proxy)', async () => { let response = await hubClient.getHistory() expect(response.data.history.length).toBe(0) @@ -217,11 +231,7 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) // assert that the request was received by the payee dfsp @@ -237,27 +247,26 @@ describe('POST /fxQuotes request tests --> ', () => { * Produces a PUT /fxQuotes/{ID} callback for a dfsp that is registered in the hub * Expects a PUT /fxQuotes/{ID} callback at the payer dfsp's endpoint */ - test('should PUT fx quote callback (no proxy)', async () => { + test('should PUT fxquote callback (no proxy)', async () => { let response = await hubClient.getHistory() expect(response.data.history.length).toBe(0) const from = 'greenbank' const to = 'pinkbank' - const payload = mocks.putFxQuotesPayloadDto({ - fxQuotesPostPayload: mocks.postFxQuotesPayloadDto({ initiatingFsp: to, counterPartyFsp: from }) - }) + + // create subject fxquote + const fxQuotesPostPayload = mocks.postFxQuotesPayloadDto({ initiatingFsp: to, counterPartyFsp: from }) + await createFxQuote(to, from, fxQuotesPostPayload) + + // send put callback + const payload = mocks.putFxQuotesPayloadDto({ fxQuotesPostPayload }) const message = mocks.kafkaMessageFxPayloadPutDto({ from, to, id: payload.conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.PUT const topicConfig = dto.topicConfigDto({ topicName: topic }) - const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) // assert that the callback was received by the payee dfsp @@ -290,11 +299,7 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) // assert that error callback was received by the payer dfsp @@ -317,7 +322,12 @@ describe('POST /fxQuotes request tests --> ', () => { const from = 'greenbank' const to = 'pinkbank' - const conversionRequestId = uuid() + + // create subject fxquote + const fxQuotesPostPayload = mocks.postFxQuotesPayloadDto({ initiatingFsp: to, counterPartyFsp: from }) + await createFxQuote(to, from, fxQuotesPostPayload) + + const conversionRequestId = fxQuotesPostPayload.conversionRequestId const payload = { errorInformation: { errorCode: '3100', @@ -331,11 +341,7 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) // assert that the error callback was received by the payer dfsp @@ -373,7 +379,11 @@ describe('POST /fxQuotes request tests --> ', () => { expect(isAdded).toBe(true) expect(proxy).toBe(proxyId) - const conversionRequestId = uuid() + // create subject fxquote + const fxQuotesPostPayload = mocks.postFxQuotesPayloadDto({ initiatingFsp: to, counterPartyFsp: from }) + await createFxQuote(to, from, fxQuotesPostPayload) + + const conversionRequestId = fxQuotesPostPayload.conversionRequestId const payload = { errorInformation: { errorCode: '3100', @@ -387,11 +397,7 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) // assert that the error callback was received by the proxy @@ -418,7 +424,13 @@ describe('POST /fxQuotes request tests --> ', () => { const from = 'pinkbank' const to = 'greenbank' - const conversionRequestId = uuid() + + // create subject fxquote + const fxQuotesPostPayload = mocks.postFxQuotesPayloadDto({ initiatingFsp: to, counterPartyFsp: from }) + await createFxQuote(to, from, fxQuotesPostPayload) + + // get the fxquote + const conversionRequestId = fxQuotesPostPayload.conversionRequestId const message = mocks.kafkaMessageFxPayloadGetDto({ from, to, id: conversionRequestId }) const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.GET const topicConfig = dto.topicConfigDto({ topicName: topic }) @@ -426,11 +438,7 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) // assert that the callback was received by the destination dfsp's endpoint @@ -467,7 +475,11 @@ describe('POST /fxQuotes request tests --> ', () => { expect(isAdded).toBe(true) expect(proxy).toBe(proxyId) - const conversionRequestId = uuid() + // create subject fxquote + const fxQuotesPostPayload = mocks.postFxQuotesPayloadDto({ initiatingFsp: to, counterPartyFsp: from }) + await createFxQuote(to, from, fxQuotesPostPayload) + + const conversionRequestId = fxQuotesPostPayload.conversionRequestId const message = mocks.kafkaMessageFxPayloadGetDto({ from, to, id: conversionRequestId }) const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.GET const topicConfig = dto.topicConfigDto({ topicName: topic }) @@ -475,11 +487,7 @@ describe('POST /fxQuotes request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) // assert that the callback was received by the proxy diff --git a/test/integration/putCallback.test.js b/test/integration/putCallback.test.js index f34fd248..44604a17 100644 --- a/test/integration/putCallback.test.js +++ b/test/integration/putCallback.test.js @@ -409,7 +409,7 @@ describe('PUT callback Tests --> ', () => { amount: 300 }, targetAmount: { - currency: 'TZS' + currency: 'ZMW' }, expiration: new Date(Date.now() + 5 * 60 * 1000).toISOString(), extensionList: { @@ -462,7 +462,7 @@ describe('PUT callback Tests --> ', () => { counterPartyFsp: to, amountType: 'SEND', sourceAmount: { amount: 100, currency: 'USD' }, - targetAmount: { amount: 100, currency: 'TZS' }, + targetAmount: { amount: 100, currency: 'ZMW' }, expiration: new Date(Date.now() + 5 * 60 * 1000).toISOString(), charges: [ { @@ -472,7 +472,7 @@ describe('PUT callback Tests --> ', () => { amount: 1 }, targetAmount: { - currency: 'TZS', + currency: 'ZMW', amount: 1 } } @@ -582,7 +582,7 @@ describe('PUT callback Tests --> ', () => { amount: 300 }, targetAmount: { - currency: 'TZS' + currency: 'ZMW' }, expiration: new Date(Date.now() + 5 * 60 * 1000).toISOString(), extensionList: { diff --git a/test/mocks.js b/test/mocks.js index d59671be..ac4e3dc9 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -162,11 +162,15 @@ const putFxQuotesPayloadDto = ({ fxQuotesPostPayload = postFxQuotesPayloadDto(), condition = 'mock-condition', charges = [{ chargeType: 'Tax', sourceAmount: { amount: 1, currency: 'USD' }, targetAmount: { amount: 100, currency: 'ZMW' } }] -} = {}) => ({ - ...fxQuotesPostPayload, - condition, - charges -}) +} = {}) => { + const dto = { + ...fxQuotesPostPayload, + condition + } + dto.conversionTerms.targetAmount.amount = 600 + dto.conversionTerms.charges = charges + return dto +} const postQuotesPayloadDto = ({ from = 'payer', From 3a4d43a62309032dcc8960f2988e8226211f7dd5 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 20 Aug 2024 18:02:04 +0100 Subject: [PATCH 24/28] test: update put quotes test - persistent mode --- test/integration/fxQuotes.test.js | 65 +++- test/integration/putCallback.test.js | 486 ++++----------------------- test/mocks.js | 13 + 3 files changed, 131 insertions(+), 433 deletions(-) diff --git a/test/integration/fxQuotes.test.js b/test/integration/fxQuotes.test.js index d6f1453b..7b332f1a 100644 --- a/test/integration/fxQuotes.test.js +++ b/test/integration/fxQuotes.test.js @@ -36,21 +36,19 @@ const mocks = require('../mocks') const { wrapWithRetries } = require('../util/helper') const Database = require('../../src/data/cachedDatabase') -const hubClient = new MockServerClient() -const base64Encode = (data) => Buffer.from(data).toString('base64') - -const retryConf = { - remainingRetries: process?.env?.TEST_INT_RETRY_COUNT || 20, - timeout: process?.env?.TEST_INT_RETRY_DELAY || 1 -} - const TEST_TIMEOUT = 20_000 describe('POST /fxQuotes request tests --> ', () => { jest.setTimeout(TEST_TIMEOUT) + + let db const config = new Config() const { kafkaConfig, proxyCache, hubName } = config - let db + const hubClient = new MockServerClient() + const retryConf = { + remainingRetries: process?.env?.TEST_INT_RETRY_COUNT || 20, + timeout: process?.env?.TEST_INT_RETRY_DELAY || 1 + } beforeAll(async () => { db = new Database(config) @@ -68,13 +66,14 @@ describe('POST /fxQuotes request tests --> ', () => { await Producer.disconnect() }) + const base64Encode = (data) => Buffer.from(data).toString('base64') + const createFxQuote = async (from, to, payload) => { const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.POST const topicConfig = dto.topicConfigDto({ topicName: topic }) const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - const response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) await hubClient.clearHistory() @@ -204,6 +203,41 @@ describe('POST /fxQuotes request tests --> ', () => { expect(request.body).toEqual(payload) expect(request.headers['fspiop-source']).toBe(from) expect(request.headers['fspiop-destination']).toBe(to) + + // check fx quote response details were saved to db + const fxQuoteResponseDetails = await db._getFxQuoteResponseDetails(payload.conversionRequestId) + expect(fxQuoteResponseDetails).toEqual({ + conversionRequestId: payload.conversionRequestId, + fxQuoteResponseId: expect.anything(), + ilpCondition: payload.condition, + conversionId: payload.conversionTerms.conversionId, + amountTypeId: 1, + determiningTransferId: null, + counterPartyFsp: payload.conversionTerms.counterPartyFsp, + initiatingFsp: payload.conversionTerms.initiatingFsp, + sourceAmount: payload.conversionTerms.sourceAmount.amount, + sourceCurrency: payload.conversionTerms.sourceAmount.currency, + targetAmount: payload.conversionTerms.targetAmount.amount, + targetCurrency: payload.conversionTerms.targetAmount.currency, + expirationDate: expect.anything(), + createdDate: expect.anything(), + charges: expect.anything(), + extensions: expect.anything() + }) + expect(JSON.parse(fxQuoteResponseDetails.extensions)).toEqual(payload.conversionTerms.extensionList.extension) + const charges = JSON.parse(fxQuoteResponseDetails.charges) + const expectedCharges = charges.map(charge => ({ + chargeType: charge.chargeType, + sourceAmount: { + currency: charge.sourceCurrency, + amount: charge.sourceAmount + }, + targetAmount: { + currency: charge.targetCurrency, + amount: charge.targetAmount + } + })) + expect(expectedCharges).toEqual(payload.conversionTerms.charges) } finally { await proxyClient.removeDfspIdFromProxyMapping(from) await proxyClient.disconnect() @@ -408,6 +442,17 @@ describe('POST /fxQuotes request tests --> ', () => { expect(request.body.errorInformation.errorDescription).toBe('Generic validation error') expect(request.headers['fspiop-source']).toBe(from) expect(request.headers['fspiop-destination']).toBe(to) + + // check fxquote error details were saved to db + const fxQuoteErrorDetails = await db._getFxQuoteErrorDetails(conversionRequestId) + expect(fxQuoteErrorDetails).toEqual({ + conversionRequestId, + fxQuoteResponseId: null, + fxQuoteErrorId: expect.anything(), + errorCode: Number(payload.errorInformation.errorCode), + errorDescription: payload.errorInformation.errorDescription, + createdDate: expect.anything() + }) } finally { await proxyClient.removeDfspIdFromProxyMapping(to) await proxyClient.disconnect() diff --git a/test/integration/putCallback.test.js b/test/integration/putCallback.test.js index 44604a17..0f16b7fb 100644 --- a/test/integration/putCallback.test.js +++ b/test/integration/putCallback.test.js @@ -28,61 +28,30 @@ -------------- ******/ +const uuid = require('crypto').randomUUID const { Producer } = require('@mojaloop/central-services-stream').Util const { createProxyClient } = require('../../src/lib/proxy') - const Config = require('../../src/lib/config') const dto = require('../../src/lib/dto') const mocks = require('../mocks') const MockServerClient = require('./mockHttpServer/MockServerClient') -const uuid = require('crypto').randomUUID const { wrapWithRetries } = require('../util/helper') const Database = require('../../src/data/cachedDatabase') -const hubClient = new MockServerClient() - -const base64Encode = (data) => Buffer.from(data).toString('base64') -const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)) - -const retryConf = { - remainingRetries: process?.env?.TEST_INT_RETRY_COUNT || 20, - timeout: process?.env?.TEST_INT_RETRY_DELAY || 1 -} - const TEST_TIMEOUT = 20_000 const WAIT_TIMEOUT = 3_000 -/** - * Publishes a test 'POST quote' message to the Kafka topic - */ -const createQuote = async ({ - from = 'pinkbank', - to = 'greenbank', - amount = { amount: '100', currency: 'USD' }, - amountType = 'SEND' -} = {}) => { - const { kafkaConfig } = new Config() - const { topic, config } = kafkaConfig.PRODUCER.QUOTE.POST - const topicConfig = dto.topicConfigDto({ topicName: topic }) - const payload = { - quoteId: uuid(), - transactionId: uuid(), - amountType, - amount, - transactionType: { scenario: 'DEPOSIT', initiator: 'PAYER', initiatorType: 'CONSUMER' }, - payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } }, - payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from } } - } - const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) - const isOk = await Producer.produceMessage(message, topicConfig, config) - expect(isOk).toBe(true) - return payload -} - describe('PUT callback Tests --> ', () => { + jest.setTimeout(TEST_TIMEOUT) + + let db const config = new Config() const { kafkaConfig, proxyCache } = config - let db + const hubClient = new MockServerClient() + const retryConf = { + remainingRetries: process?.env?.TEST_INT_RETRY_COUNT || 20, + timeout: process?.env?.TEST_INT_RETRY_DELAY || 1 + } beforeEach(async () => { await hubClient.clearHistory() @@ -100,6 +69,36 @@ describe('PUT callback Tests --> ', () => { await Producer.disconnect() }) + const base64Encode = (data) => Buffer.from(data).toString('base64') + const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)) + + /** + * Publishes a test 'POST quote' message to the Kafka topic + */ + const createQuote = async ({ + from = 'pinkbank', + to = 'greenbank', + amount = { amount: '100', currency: 'USD' }, + amountType = 'SEND' + } = {}) => { + const { kafkaConfig } = new Config() + const { topic, config } = kafkaConfig.PRODUCER.QUOTE.POST + const topicConfig = dto.topicConfigDto({ topicName: topic }) + const payload = mocks.postQuotesPayloadDto({ from, to, amount, amountType }) + const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + return payload + } + + const getResponseWithRetry = async () => { + return wrapWithRetries(() => hubClient.getHistory(), + retryConf.remainingRetries, + retryConf.timeout, + (result) => result.data.history.length > 0 + ) + } + test('should handle the JWS signing when a switch error event is produced to the PUT topic', async () => { // create test quote to prevent db (row reference) error on PUT request const quoteCreated = await createQuote() @@ -115,12 +114,7 @@ describe('PUT callback Tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) const { headers, url } = response.data.history[0] @@ -129,7 +123,7 @@ describe('PUT callback Tests --> ', () => { const { signature, protectedHeader } = JSON.parse(headers['fspiop-signature']) expect(signature).toBeTruthy() expect(protectedHeader).toBeTruthy() - }, TEST_TIMEOUT) + }) test('should pass validation for PUT /quotes/{ID} request if request transferAmount/payeeReceiveAmount currency is registered (position account exists) for the payee pariticpant', async () => { // create test quote to prevent db (row reference) error on PUT request @@ -142,12 +136,7 @@ describe('PUT callback Tests --> ', () => { const { topic, config } = kafkaConfig.PRODUCER.QUOTE.PUT const topicConfig = dto.topicConfigDto({ topicName: topic }) - const payload = { - transferAmount: { amount: '100', currency: 'USD' }, - payeeReceiveAmount: { amount: '100', currency: 'USD' }, - ilpPacket: 'test', - condition: 'test' - } + const payload = mocks.putQuotesPayloadDto() const message = mocks.kafkaMessagePayloadDto({ from: 'greenbank', to: 'pinkbank', @@ -158,16 +147,12 @@ describe('PUT callback Tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) const { url } = response.data.history[0] expect(url).toBe(`/${message.to}/quotes/${message.id}`) - }, TEST_TIMEOUT) + }) test('should pass validation for PUT /quotes/{ID} request if source is proxied participant', async () => { // create test quote to prevent db (row reference) error on PUT request @@ -187,14 +172,9 @@ describe('PUT callback Tests --> ', () => { proxyClient = createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) const isAdded = await proxyClient.addDfspIdToProxyMapping(from, proxyId) expect(isAdded).toBe(true) - const payload = { - transferAmount: { amount: '100', currency: 'USD' }, - payeeReceiveAmount: { amount: '100', currency: 'USD' }, - ilpPacket: 'test', - condition: 'test' - } + const payload = mocks.putQuotesPayloadDto() const message = mocks.kafkaMessagePayloadDto({ - from: 'greenbank', + from, to: 'pinkbank', id: quoteCreated.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) @@ -203,11 +183,7 @@ describe('PUT callback Tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) const { url } = response.data.history[0] @@ -216,7 +192,7 @@ describe('PUT callback Tests --> ', () => { await proxyClient.removeDfspIdFromProxyMapping(from) await proxyClient.disconnect() } - }, TEST_TIMEOUT) + }) test('should fail validation for PUT /quotes/{ID} request if request transferAmount/payeeReceiveAmount currency is not registered (position account does not exist) for the payee pariticpant', async () => { // test the same scenario with only transferAmount set @@ -230,11 +206,8 @@ describe('PUT callback Tests --> ', () => { const { topic, config } = kafkaConfig.PRODUCER.QUOTE.PUT const topicConfig = dto.topicConfigDto({ topicName: topic }) - const payload = { - transferAmount: { amount: '100', currency: 'ZKW' }, - ilpPacket: 'test', - condition: 'test' - } + const payload = mocks.putQuotesPayloadDto({ transferAmount: { amount: '100', currency: 'ZKW' } }) + delete payload.payeeReceiveAmount let message = mocks.kafkaMessagePayloadDto({ from: 'greenbank', to: 'pinkbank', @@ -245,11 +218,7 @@ describe('PUT callback Tests --> ', () => { let isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) const { url, body } = response.data.history[0] @@ -275,20 +244,16 @@ describe('PUT callback Tests --> ', () => { isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) const { url: url2, body: body2 } = response.data.history[0] expect(url2).toBe(`/${message.from}/quotes/${message.id}/error`) expect(body2.errorInformation.errorCode).toBe('3201') expect(body2.errorInformation.errorDescription).toBe(`Destination FSP Error - Unsupported participant '${message.from}'`) - }, TEST_TIMEOUT) + }) - test('should forward PUT /quotes/{ID} request to proxy if the payer dfsp is not registered in the hub', async () => { + test.only('should forward PUT /quotes/{ID} request to proxy if the payer dfsp is not registered in the hub', async () => { let response = await hubClient.getHistory() expect(response.data.history.length).toBe(0) @@ -316,30 +281,12 @@ describe('PUT callback Tests --> ', () => { expect(isAdded).toBe(true) expect(representative).toBe(proxyId) - const quoteId = uuid() - const postPayload = { - quoteId, - transactionId: uuid(), - amountType: 'SEND', - amount: { amount: '100', currency: 'USD' }, - transactionType: { scenario: 'DEPOSIT', initiator: 'PAYER', initiatorType: 'CONSUMER' }, - payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from } }, - payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } } - } - const postMessage = mocks.kafkaMessagePayloadPostDto({ - from, - to, - id: postPayload.quoteId, - payloadBase64: base64Encode(JSON.stringify(postPayload)) - }) + const postPayload = mocks.postQuotesPayloadDto({ from, to }) + const postMessage = mocks.kafkaMessagePayloadPostDto({ from, to, id: postPayload.quoteId, payloadBase64: base64Encode(JSON.stringify(postPayload)) }) const postIsOk = await Producer.produceMessage(postMessage, postTopicConfig, kafkaConfig.PRODUCER.QUOTE.POST.config) expect(postIsOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect([1, 2]).toContain(response.data.history.length) await hubClient.clearHistory() @@ -348,16 +295,12 @@ describe('PUT callback Tests --> ', () => { ilpPacket: 'test', condition: 'test' } - const message = mocks.kafkaMessagePayloadDto({ from, to, id: quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const message = mocks.kafkaMessagePayloadDto({ from, to, id: postPayload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) delete message.content.headers.accept const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect([1, 2]).toContain(response.data.history.length) const request = response.data.history[0] @@ -368,302 +311,7 @@ describe('PUT callback Tests --> ', () => { } finally { await proxyClient.disconnect() } - }, TEST_TIMEOUT) - - test('should forward PUT /fxQuotes/{ID} request to proxy if the payer dfsp is not registered in the hub', async () => { - let response = await hubClient.getHistory() - expect(response.data.history.length).toBe(0) - - const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.PUT - const topicConfig = dto.topicConfigDto({ topicName: topic }) - const postTopicConfig = dto.topicConfigDto({ topicName: kafkaConfig.PRODUCER.FX_QUOTE.POST.topic }) - - const from = 'greenbank' - // redbank not in the hub db - const to = 'redbank' - - // register proxy representative for redbank - const proxyId = 'redbankproxy' - let proxyClient - - try { - proxyClient = createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) - const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) - - // assert that the proxy representative is mapped in the cache - const key = `dfsp:${to}` - const representative = await proxyClient.redisClient.get(key) - - expect(isAdded).toBe(true) - expect(representative).toBe(proxyId) - const conversionRequestId = uuid() - const postPayload = { - conversionRequestId, - conversionTerms: { - conversionId: uuid(), - initiatingFsp: from, - counterPartyFsp: to, - amountType: 'SEND', - sourceAmount: { - currency: 'USD', - amount: 300 - }, - targetAmount: { - currency: 'ZMW' - }, - expiration: new Date(Date.now() + 5 * 60 * 1000).toISOString(), - extensionList: { - extension: [ - { - key: 'Test', - value: 'Data' - } - ] - } - } - } - const postMessage = mocks.kafkaMessagePayloadPostDto({ from, to, id: conversionRequestId, payloadBase64: base64Encode(JSON.stringify(postPayload)) }) - const postIsOk = await Producer.produceMessage(postMessage, postTopicConfig, kafkaConfig.PRODUCER.FX_QUOTE.POST.config) - - expect(postIsOk).toBe(true) - - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) - expect(response.data.history.length).toBe(1) - - // check fx quote details were saved to db - const fxQuoteDetails = await db._getFxQuoteDetails(postPayload.conversionRequestId) - expect(fxQuoteDetails).toEqual({ - conversionRequestId: postPayload.conversionRequestId, - conversionId: postPayload.conversionTerms.conversionId, - determiningTransferId: null, - amountTypeId: 1, - initiatingFsp: postPayload.conversionTerms.initiatingFsp, - counterPartyFsp: postPayload.conversionTerms.counterPartyFsp, - sourceAmount: postPayload.conversionTerms.sourceAmount.amount, - sourceCurrency: postPayload.conversionTerms.sourceAmount.currency, - targetAmount: null, - targetCurrency: postPayload.conversionTerms.targetAmount.currency, - extensions: expect.anything(), - expirationDate: expect.anything(), - createdDate: expect.anything() - }) - expect(JSON.parse(fxQuoteDetails.extensions)).toEqual(postPayload.conversionTerms.extensionList.extension) - await hubClient.clearHistory() - - const payload = { - condition: 'test', - conversionTerms: { - conversionId: uuid(), - initiatingFsp: from, - counterPartyFsp: to, - amountType: 'SEND', - sourceAmount: { amount: 100, currency: 'USD' }, - targetAmount: { amount: 100, currency: 'ZMW' }, - expiration: new Date(Date.now() + 5 * 60 * 1000).toISOString(), - charges: [ - { - chargeType: 'TEST', - sourceAmount: { - currency: 'USD', - amount: 1 - }, - targetAmount: { - currency: 'ZMW', - amount: 1 - } - } - ], - extensionList: { - extension: [ - { - key: 'Test', - value: 'Data' - } - ] - } - } - } - const message = mocks.kafkaMessagePayloadDto({ from, to, id: conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) - delete message.content.headers.accept - const isOk = await Producer.produceMessage(message, topicConfig, config) - expect(isOk).toBe(true) - - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) - expect(response.data.history.length).toBe(1) - - const request = response.data.history[0] - expect(request.url).toBe(`/${proxyId}/fxQuotes/${message.id}`) - expect(request.body).toEqual(payload) - expect(request.headers['fspiop-source']).toBe(from) - expect(request.headers['fspiop-destination']).toBe(to) - - const fxQuoteResponseDetails = await db._getFxQuoteResponseDetails(conversionRequestId) - expect(fxQuoteResponseDetails).toEqual({ - conversionRequestId, - fxQuoteResponseId: expect.anything(), - ilpCondition: payload.condition, - conversionId: payload.conversionTerms.conversionId, - amountTypeId: 1, - determiningTransferId: null, - counterPartyFsp: payload.conversionTerms.counterPartyFsp, - initiatingFsp: payload.conversionTerms.initiatingFsp, - sourceAmount: payload.conversionTerms.sourceAmount.amount, - sourceCurrency: payload.conversionTerms.sourceAmount.currency, - targetAmount: payload.conversionTerms.targetAmount.amount, - targetCurrency: payload.conversionTerms.targetAmount.currency, - expirationDate: expect.anything(), - createdDate: expect.anything(), - charges: expect.anything(), - extensions: expect.anything() - }) - expect(JSON.parse(fxQuoteResponseDetails.extensions)).toEqual(payload.conversionTerms.extensionList.extension) - const charges = JSON.parse(fxQuoteResponseDetails.charges) - const expectedCharges = charges.map(charge => ({ - chargeType: charge.chargeType, - sourceAmount: { - currency: charge.sourceCurrency, - amount: charge.sourceAmount - }, - targetAmount: { - currency: charge.targetCurrency, - amount: charge.targetAmount - } - })) - expect(expectedCharges).toEqual(payload.conversionTerms.charges) - } finally { - await proxyClient.disconnect() - } - }, TEST_TIMEOUT) - - test('should forward PUT /fxQuotes/{ID}/error request to proxy if the payer dfsp is not registered in the hub', async () => { - let response = await hubClient.getHistory() - expect(response.data.history.length).toBe(0) - - const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.PUT - const topicConfig = dto.topicConfigDto({ topicName: topic }) - const postTopicConfig = dto.topicConfigDto({ topicName: kafkaConfig.PRODUCER.FX_QUOTE.POST.topic }) - - const from = 'greenbank' - // redbank not in the hub db - const to = 'redbank' - - // register proxy representative for redbank - const proxyId = 'redbankproxy' - let proxyClient - - try { - proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) - const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) - - // assert that the proxy representative is mapped in the cache - const key = `dfsp:${to}` - const representative = await proxyClient.redisClient.get(key) - - expect(isAdded).toBe(true) - expect(representative).toBe(proxyId) - const conversionRequestId = uuid() - const postPayload = { - conversionRequestId, - conversionTerms: { - conversionId: uuid(), - initiatingFsp: from, - counterPartyFsp: to, - amountType: 'SEND', - sourceAmount: { - currency: 'USD', - amount: 300 - }, - targetAmount: { - currency: 'ZMW' - }, - expiration: new Date(Date.now() + 5 * 60 * 1000).toISOString(), - extensionList: { - extension: [ - { - key: 'Test', - value: 'Data' - } - ] - } - } - } - const postMessage = mocks.kafkaMessagePayloadPostDto({ from, to, id: conversionRequestId, payloadBase64: base64Encode(JSON.stringify(postPayload)) }) - const postIsOk = await Producer.produceMessage(postMessage, postTopicConfig, kafkaConfig.PRODUCER.FX_QUOTE.POST.config) - - expect(postIsOk).toBe(true) - - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) - expect(response.data.history.length).toBe(1) - - // check fx quote details were saved to db - const fxQuoteDetails = await db._getFxQuoteDetails(postPayload.conversionRequestId) - expect(fxQuoteDetails).toEqual({ - conversionRequestId: postPayload.conversionRequestId, - conversionId: postPayload.conversionTerms.conversionId, - determiningTransferId: null, - amountTypeId: 1, - initiatingFsp: postPayload.conversionTerms.initiatingFsp, - counterPartyFsp: postPayload.conversionTerms.counterPartyFsp, - sourceAmount: postPayload.conversionTerms.sourceAmount.amount, - sourceCurrency: postPayload.conversionTerms.sourceAmount.currency, - targetAmount: null, - targetCurrency: postPayload.conversionTerms.targetAmount.currency, - extensions: expect.anything(), - expirationDate: expect.anything(), - createdDate: expect.anything() - }) - expect(JSON.parse(fxQuoteDetails.extensions)).toEqual(postPayload.conversionTerms.extensionList.extension) - await hubClient.clearHistory() - - const payload = { - errorInformation: { - errorCode: '5000', - errorDescription: 'Test error' - } - } - const message = mocks.kafkaMessagePayloadDto({ from, to, id: conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) - delete message.content.headers.accept - const isOk = await Producer.produceMessage(message, topicConfig, config) - expect(isOk).toBe(true) - - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) - expect(response.data.history.length).toBe(1) - - const request = response.data.history[0] - expect(request.url).toBe(`/${proxyId}/fxQuotes/${message.id}/error`) - expect(request.body).toEqual(payload) - expect(request.headers['fspiop-source']).toBe(from) - expect(request.headers['fspiop-destination']).toBe(to) - - const fxQuoteErrorDetails = await db._getFxQuoteErrorDetails(conversionRequestId) - expect(fxQuoteErrorDetails).toEqual({ - conversionRequestId, - fxQuoteResponseId: null, - fxQuoteErrorId: expect.anything(), - errorCode: Number(payload.errorInformation.errorCode), - errorDescription: payload.errorInformation.errorDescription, - createdDate: expect.anything() - }) - } finally { - await proxyClient.disconnect() - } - }, TEST_TIMEOUT) + }) test('should forward PUT /bulkQuotes/{ID} request to proxy if the payer dfsp is not registered in the hub', async () => { let response = await hubClient.getHistory() @@ -712,11 +360,7 @@ describe('PUT callback Tests --> ', () => { const postIsOk = await Producer.produceMessage(postMessage, postTopicConfig, kafkaConfig.PRODUCER.BULK_QUOTE.POST.config) expect(postIsOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) await hubClient.clearHistory() @@ -740,11 +384,7 @@ describe('PUT callback Tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) const request = response.data.history[0] @@ -755,5 +395,5 @@ describe('PUT callback Tests --> ', () => { } finally { await proxyClient.disconnect() } - }, TEST_TIMEOUT) + }) }) diff --git a/test/mocks.js b/test/mocks.js index ac4e3dc9..9655a35b 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -192,6 +192,18 @@ const postQuotesPayloadDto = ({ payee }) +const putQuotesPayloadDto = ({ + transferAmount = { amount: '100', currency: 'USD' }, + payeeReceiveAmount = { amount: '100', currency: 'USD' }, + ilpPacket = 'test-ilp-packet', + condition = 'test-condition' +} = {}) => ({ + transferAmount, + payeeReceiveAmount, + ilpPacket, + condition +}) + const postBulkQuotesPayloadDto = ({ from = 'payer', to = 'payee', @@ -223,5 +235,6 @@ module.exports = { postFxQuotesPayloadDto, putFxQuotesPayloadDto, postQuotesPayloadDto, + putQuotesPayloadDto, postBulkQuotesPayloadDto } From 318b6269391a17bc3a6375932364202540b983b8 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 20 Aug 2024 18:11:59 +0100 Subject: [PATCH 25/28] test: update post quotes test - persistent mode --- test/integration/postRequest.test.js | 87 +++++++++------------------- 1 file changed, 28 insertions(+), 59 deletions(-) diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index 15de946a..67714c6a 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -37,21 +37,18 @@ const uuid = require('crypto').randomUUID const { wrapWithRetries } = require('../util/helper') const Database = require('../../src/data/cachedDatabase') -const hubClient = new MockServerClient() -const base64Encode = (data) => Buffer.from(data).toString('base64') - -const retryConf = { - remainingRetries: process?.env?.TEST_INT_RETRY_COUNT || 20, - timeout: process?.env?.TEST_INT_RETRY_DELAY || 1 -} - const TEST_TIMEOUT = 20_000 -let db describe('POST request tests --> ', () => { jest.setTimeout(TEST_TIMEOUT) + let db const config = new Config() const { kafkaConfig, proxyCache } = config + const hubClient = new MockServerClient() + const retryConf = { + remainingRetries: process?.env?.TEST_INT_RETRY_COUNT || 20, + timeout: process?.env?.TEST_INT_RETRY_DELAY || 1 + } beforeEach(async () => { await hubClient.clearHistory() @@ -69,6 +66,16 @@ describe('POST request tests --> ', () => { await Producer.disconnect() }) + const base64Encode = (data) => Buffer.from(data).toString('base64') + + const getResponseWithRetry = async () => { + return wrapWithRetries(() => hubClient.getHistory(), + retryConf.remainingRetries, + retryConf.timeout, + (result) => result.data.history.length > 0 + ) + } + test('should pass validation for POST /quotes request if request amount currency is registered (position account exists) for the payer participant', async () => { let response = await hubClient.getHistory() expect(response.data.history.length).toBe(0) @@ -82,11 +89,7 @@ describe('POST request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBeGreaterThan(0) const { url } = response.data.history[0] @@ -113,11 +116,7 @@ describe('POST request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) const { url } = response.data.history[0] @@ -142,11 +141,7 @@ describe('POST request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) const { url, body } = response.data.history[0] @@ -163,20 +158,15 @@ describe('POST request tests --> ', () => { const topicConfig = dto.topicConfigDto({ topicName: topic }) const from = 'pinkbank' const to = 'greenbank' - const payload = mocks.postQuotesPayloadDto({ - from, - to, + const payload = mocks.postQuotesPayloadDto({ + from, to, payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from }, supportedCurrencies: ['USD', 'ZMW'] } }) const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) const { url } = response.data.history[0] @@ -192,19 +182,14 @@ describe('POST request tests --> ', () => { const from = 'pinkbank' const to = 'greenbank' const payload = mocks.postQuotesPayloadDto({ - from, - to, + from, to, payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from }, supportedCurrencies: ['USD', 'ZMW', 'GBP'] } }) const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) const { url, body } = response.data.history[0] @@ -227,11 +212,7 @@ describe('POST request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect([1, 2]).toContain(response.data.history.length) const request = response.data.history[0] @@ -272,11 +253,7 @@ describe('POST request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect([1, 2]).toContain(response.data.history.length) const request = response.data.history[0] @@ -305,11 +282,7 @@ describe('POST request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) const request = response.data.history[0] @@ -362,11 +335,7 @@ describe('POST request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - response = await wrapWithRetries(() => hubClient.getHistory(), - retryConf.remainingRetries, - retryConf.timeout, - (result) => result.data.history.length > 0 - ) + response = await getResponseWithRetry() expect(response.data.history.length).toBe(1) const request = response.data.history[0] From d97ec774d096dbf9f203a42ac2f93a0e35d7aea5 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 20 Aug 2024 18:12:27 +0100 Subject: [PATCH 26/28] chore: linting --- test/integration/postRequest.test.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index 67714c6a..3d2ded3d 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -158,8 +158,9 @@ describe('POST request tests --> ', () => { const topicConfig = dto.topicConfigDto({ topicName: topic }) const from = 'pinkbank' const to = 'greenbank' - const payload = mocks.postQuotesPayloadDto({ - from, to, + const payload = mocks.postQuotesPayloadDto({ + from, + to, payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from }, supportedCurrencies: ['USD', 'ZMW'] } }) const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) @@ -182,7 +183,8 @@ describe('POST request tests --> ', () => { const from = 'pinkbank' const to = 'greenbank' const payload = mocks.postQuotesPayloadDto({ - from, to, + from, + to, payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from }, supportedCurrencies: ['USD', 'ZMW', 'GBP'] } }) const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) From 3a5cad90ad7856ff0b857e2a092aa811726139e3 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 20 Aug 2024 18:29:35 +0100 Subject: [PATCH 27/28] test: refactor --- test/integration/putCallback.test.js | 39 +++++----------------------- test/mocks.js | 27 +++++++++++++++++-- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/test/integration/putCallback.test.js b/test/integration/putCallback.test.js index 0f16b7fb..454a2969 100644 --- a/test/integration/putCallback.test.js +++ b/test/integration/putCallback.test.js @@ -253,7 +253,7 @@ describe('PUT callback Tests --> ', () => { expect(body2.errorInformation.errorDescription).toBe(`Destination FSP Error - Unsupported participant '${message.from}'`) }) - test.only('should forward PUT /quotes/{ID} request to proxy if the payer dfsp is not registered in the hub', async () => { + test('should forward PUT /quotes/{ID} request to proxy if the payer dfsp is not registered in the hub', async () => { let response = await hubClient.getHistory() expect(response.data.history.length).toBe(0) @@ -341,21 +341,8 @@ describe('PUT callback Tests --> ', () => { expect(representative).toBe(proxyId) const bulkQuoteId = uuid() - const quoteId = uuid() - const postPayload = { - bulkQuoteId, - payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from } }, - individualQuotes: [ - { - quoteId, - transactionId: uuid(), - payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } }, - amountType: 'SEND', - amount: { amount: '100', currency: 'USD' }, - transactionType: { scenario: 'DEPOSIT', initiator: 'PAYER', initiatorType: 'CONSUMER' } - } - ] - } + const quoteIds = [uuid()] + const postPayload = mocks.postBulkQuotesPayloadDto({ from, to, bulkQuoteId, quoteIds }) const postMessage = mocks.kafkaMessagePayloadPostDto({ from, to, id: null, payloadBase64: base64Encode(JSON.stringify(postPayload)) }) const postIsOk = await Producer.produceMessage(postMessage, postTopicConfig, kafkaConfig.PRODUCER.BULK_QUOTE.POST.config) expect(postIsOk).toBe(true) @@ -364,22 +351,8 @@ describe('PUT callback Tests --> ', () => { expect(response.data.history.length).toBe(1) await hubClient.clearHistory() - const payload = { - individualQuoteResults: [ - { - quoteId, - payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } }, - transferAmount: { amount: '100', currency: 'USD' }, - payeeReceiveAmount: { amount: '100', currency: 'USD' }, - payeeFspFee: { amount: '0', currency: 'USD' }, - payeeFspCommission: { amount: '0', currency: 'USD' }, - ilpPacket: 'test', - condition: 'test' - } - ], - expiration: new Date(Date.now() + 5 * 60 * 1000).toISOString() - } - const message = mocks.kafkaMessagePayloadDto({ from, to, id: uuid(), payloadBase64: base64Encode(JSON.stringify(payload)) }) + const putPayload = mocks.putQuotesPayloadDto({ to, quoteIds }) + const message = mocks.kafkaMessagePayloadDto({ from, to, id: uuid(), payloadBase64: base64Encode(JSON.stringify(putPayload)) }) delete message.content.headers.accept const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) @@ -389,7 +362,7 @@ describe('PUT callback Tests --> ', () => { const request = response.data.history[0] expect(request.url).toBe(`/${proxyId}/bulkQuotes/${message.id}`) - expect(request.body).toEqual(payload) + expect(request.body).toEqual(putPayload) expect(request.headers['fspiop-source']).toBe(from) expect(request.headers['fspiop-destination']).toBe(to) } finally { diff --git a/test/mocks.js b/test/mocks.js index 9655a35b..ca8ca12e 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -208,10 +208,11 @@ const postBulkQuotesPayloadDto = ({ from = 'payer', to = 'payee', bulkQuoteId = uuid(), + quoteIds = [uuid()], payer = { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from } }, individualQuotes = [ { - quoteId: uuid(), + quoteId: quoteIds[0], transactionId: uuid(), payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } }, amountType: 'SEND', @@ -225,6 +226,27 @@ const postBulkQuotesPayloadDto = ({ individualQuotes }) +const putBulkQuotesPayloadDto = ({ + to = 'payee', + quoteIds = [uuid()], + individualQuoteResults = [ + { + quoteId: quoteIds[0], + payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } }, + transferAmount: { amount: '100', currency: 'USD' }, + payeeReceiveAmount: { amount: '100', currency: 'USD' }, + payeeFspFee: { amount: '0', currency: 'USD' }, + payeeFspCommission: { amount: '0', currency: 'USD' }, + ilpPacket: 'test-ilp-packet', + condition: 'test-condition' + } + ], + expiration = new Date(Date.now() + 5 * 60 * 1000).toISOString() +} = {}) => ({ + individualQuoteResults, + expiration +}) + module.exports = { kafkaMessagePayloadDto, kafkaMessagePayloadPostDto, @@ -236,5 +258,6 @@ module.exports = { putFxQuotesPayloadDto, postQuotesPayloadDto, putQuotesPayloadDto, - postBulkQuotesPayloadDto + postBulkQuotesPayloadDto, + putBulkQuotesPayloadDto } From 98807384de5ed78271100f13aaf50ae2c4a68cbf Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 20 Aug 2024 18:30:24 +0100 Subject: [PATCH 28/28] chore --- config/default.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/default.json b/config/default.json index 7f739be3..b6395f58 100644 --- a/config/default.json +++ b/config/default.json @@ -53,7 +53,7 @@ "includeCauseExtension": false, "truncateExtensions": true }, - "SIMPLE_ROUTING_MODE": false, + "SIMPLE_ROUTING_MODE": true, "ENDPOINT_SECURITY":{ "JWS": { "JWS_SIGN": false,