diff --git a/package-lock.json b/package-lock.json index d906b930..7b6bc94c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "progress": "2.0.3", "semver": "7.6.3", "shamsi-date-converter": "1.0.5", + "sinon": "^19.0.2", "tar": "7.4.3", "ua-parser-js": "1.0.38", "update-notifier": "7.1.0", @@ -44,21 +45,27 @@ "liara": "bin/run.js" }, "devDependencies": { + "@oclif/test": "^4.1.11", "@types/async-retry": "^1.4.9", "@types/bytes": "^3.1.5", + "@types/chai": "^5.0.1", "@types/fs-extra": "^11.0.4", "@types/inquirer": "^9.0.7", "@types/jest": "^29.5.14", "@types/node": "18.15.11", "@types/progress": "^2.0.7", "@types/semver": "^7.5.8", + "@types/sinon": "^17.0.4", "@types/ua-parser-js": "^0.7.39", "@types/update-notifier": "^6.0.8", "@types/ws": "^8.5.13", + "chai": "^5.2.0", "eslint": "^8.53.0", "eslint-config-oclif": "^5.2.2", "eslint-config-oclif-typescript": "^3.1.13", "husky": "^9.1.7", + "mocha": "^11.1.0", + "nock": "^14.0.1", "oclif": "^4.14.12", "prettier": "^3.3.3", "pretty-quick": "^4.0.0", @@ -2724,6 +2731,24 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mswjs/interceptors": { + "version": "0.37.6", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.37.6.tgz", + "integrity": "sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3437,6 +3462,48 @@ "node": ">=8" } }, + "node_modules/@oclif/test": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@oclif/test/-/test-4.1.11.tgz", + "integrity": "sha512-j689R13E2so1Rj6jJUfQ67yJ4N7u6L5KFzv87cvUfD9AZ79xAtCxGrd34/iOLUDJmv1huFt/0QumBcjKoWUSYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^3.16.0", + "debug": "^4.4.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@oclif/core": ">= 3.0.0" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3518,11 +3585,10 @@ } }, "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", - "dev": true, - "peer": true, + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" } @@ -3537,6 +3603,32 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "lodash.get": "^4.4.2", + "type-detect": "^4.1.0" + } + }, + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "license": "(Unlicense OR Apache-2.0)" + }, "node_modules/@smithy/abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", @@ -4320,6 +4412,16 @@ "integrity": "sha512-VgZkrJckypj85YxEsEavcMmmSOIzkUHqWmM4CCyia5dc54YwsXzJ5uT4fYxBQNEXx+oF1krlhgCbvfubXqZYsQ==", "dev": true }, + "node_modules/@types/chai": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.0.1.tgz", + "integrity": "sha512-5T8ajsg3M/FOncpLYW7sdOcD6yf4+722sze/tc4KQV0P8Z2rAr3SAuHCIkYmYpt8VbcQlnz8SxlOlPQYefe4cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, "node_modules/@types/cli-progress": { "version": "3.11.5", "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.5.tgz", @@ -4334,6 +4436,13 @@ "integrity": "sha512-GUvNiia85zTDDIx0iPrtF3pI8dwrQkfuokEqxqPDE55qxH0U5SZz4awVZjiJLWN2ZZRkXCUqgsMUbygXY+kytA==", "dev": true }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/fs-extra": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", @@ -4465,6 +4574,23 @@ "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, + "node_modules/@types/sinon": { + "version": "17.0.4", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.4.tgz", + "integrity": "sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -4837,6 +4963,16 @@ "string-width": "^4.1.0" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", @@ -4876,11 +5012,12 @@ "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==" }, "node_modules/ansis": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.3.2.tgz", - "integrity": "sha512-cFthbBlt+Oi0i9Pv/j6YdVWJh54CtjGACaMPCIrEV4Ha7HWsIjXDwseYV79TIL0B4+KfSwD5S70PeQDkPUd1rA==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.16.0.tgz", + "integrity": "sha512-sU7d/tfZiYrsIAXbdL/CNZld5bCkruzwT5KmqmadCJYxuLxHAOBjidxD5+iLmN/6xEfjcQq1l7OpsiCBlc4LzA==", + "license": "ISC", "engines": { - "node": ">=15" + "node": ">=14" } }, "node_modules/anymatch": { @@ -4888,7 +5025,6 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "peer": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -5032,6 +5168,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -5233,6 +5379,19 @@ } ] }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -5388,6 +5547,13 @@ "node": ">=8" } }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, "node_modules/browserslist": { "version": "4.21.9", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", @@ -5662,6 +5828,23 @@ "cdl": "bin/cdl.js" } }, + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -5708,6 +5891,54 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/chrono-node": { "version": "2.7.7", "resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.7.7.tgz", @@ -6092,9 +6323,10 @@ "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -6107,6 +6339,19 @@ } } }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -6147,6 +6392,16 @@ } } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -7544,6 +7799,16 @@ "micromatch": "^4.0.2" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -7648,7 +7913,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -8102,6 +8366,16 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/header-case": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", @@ -8547,6 +8821,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-boolean-object": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", @@ -8822,6 +9109,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, "node_modules/is-npm": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz", @@ -10334,6 +10628,13 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -10357,6 +10658,12 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "license": "MIT" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -10461,6 +10768,13 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -10499,6 +10813,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -10695,6 +11016,208 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mocha": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", + "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/mocha/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, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/mocha/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -10745,6 +11268,28 @@ "node": "*" } }, + "node_modules/nise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", + "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.1", + "@sinonjs/text-encoding": "^0.7.3", + "just-extend": "^6.2.0", + "path-to-regexp": "^8.1.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -10755,6 +11300,21 @@ "tslib": "^2.0.3" } }, + "node_modules/nock": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.1.tgz", + "integrity": "sha512-IJN4O9pturuRdn60NjQ7YkFt6Rwei7ZKaOwb1tvUIIqTgeD0SDDAX3vrqZD4wcXczeEy/AsUXxpGpP/yHqV7xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mswjs/interceptors": "^0.37.3", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">=18.20.0 <20 || >=20.12.1" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -10788,7 +11348,6 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -11316,6 +11875,13 @@ "node": ">=0.10.0" } }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, "node_modules/p-cancelable": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", @@ -11380,6 +11946,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -11502,15 +12075,16 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", - "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -11524,6 +12098,15 @@ "node": "14 || >=16.14" } }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -11532,6 +12115,16 @@ "node": ">=8" } }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -11754,6 +12347,16 @@ "node": ">= 6" } }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -11835,6 +12438,16 @@ "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", "dev": true }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -12010,6 +12623,19 @@ "node": ">= 6" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/redeyed": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", @@ -12419,6 +13045,16 @@ "upper-case-first": "^2.0.2" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -12573,6 +13209,54 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "node_modules/sinon": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz", + "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.2", + "@sinonjs/samsam": "^8.0.1", + "diff": "^7.0.0", + "nise": "^6.1.1", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -12781,6 +13465,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -13369,8 +14060,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -13871,6 +14560,13 @@ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -13998,6 +14694,45 @@ "node": ">=12" } }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 65efa6f5..dd1f2479 100644 --- a/package.json +++ b/package.json @@ -35,27 +35,34 @@ "progress": "2.0.3", "semver": "7.6.3", "shamsi-date-converter": "1.0.5", + "sinon": "^19.0.2", "tar": "7.4.3", "ua-parser-js": "1.0.38", "update-notifier": "7.1.0", "ws": "8.18.0" }, "devDependencies": { + "@oclif/test": "^4.1.11", "@types/async-retry": "^1.4.9", "@types/bytes": "^3.1.5", + "@types/chai": "^5.0.1", "@types/fs-extra": "^11.0.4", "@types/inquirer": "^9.0.7", "@types/jest": "^29.5.14", "@types/node": "18.15.11", "@types/progress": "^2.0.7", "@types/semver": "^7.5.8", + "@types/sinon": "^17.0.4", "@types/ua-parser-js": "^0.7.39", "@types/update-notifier": "^6.0.8", "@types/ws": "^8.5.13", + "chai": "^5.2.0", "eslint": "^8.53.0", "eslint-config-oclif": "^5.2.2", "eslint-config-oclif-typescript": "^3.1.13", "husky": "^9.1.7", + "mocha": "^11.1.0", + "nock": "^14.0.1", "oclif": "^4.14.12", "prettier": "^3.3.3", "pretty-quick": "^4.0.0", @@ -169,7 +176,7 @@ "format": "prettier \"**/*.ts\" \"**/*.js\" \"**/*.json\" --ignore-path ./.prettierignore --write", "postpack": "rm -f oclif.manifest.json tsconfig.tsbuildinfo", "prepack": "set -ex; rm -rf lib && tsc -b && oclif manifest && oclif readme", - "test": "NODE_OPTIONS=--experimental-vm-modules jest", + "test": "node --loader ts-node/esm ./node_modules/.bin/mocha --forbid-only --timeout 5000 \"test/**/*.test.ts\"", "version": "oclif readme && git add README.md", "readme": "oclif readme", "prepare": "husky install", diff --git a/src/base.ts b/src/base.ts index 0e13a835..755b7d0c 100644 --- a/src/base.ts +++ b/src/base.ts @@ -77,6 +77,10 @@ export interface IProject { project_id: string; created_at: string; isDeployed: boolean; + network?: { + _id: string; + name: string; + }; } export interface IGetProjectsResponse { @@ -123,14 +127,14 @@ export interface IProjectDetails { bundlePlanID: string; fixedIPStatus: string; created_at: string; - node: { - _id: string; - IP: string; - }; hourlyPrice: number; isDeployed: boolean; reservedDiskSpace: number; - network: string; + readOnlyRootFilesystem: boolean; + network: { + _id: string; + name: string; + }; } export interface IProjectDetailsResponse { diff --git a/src/commands/account/use.ts b/src/commands/account/use.ts index ddce1a88..823459de 100644 --- a/src/commands/account/use.ts +++ b/src/commands/account/use.ts @@ -16,13 +16,14 @@ export default class AccountUse extends Command { async run() { const { flags } = await this.parse(AccountUse); const liara_json = await this.readGlobalConfig(); + if ( !liara_json || !liara_json.accounts || Object.keys(liara_json.accounts).length === 0 ) { this.error( - "Please add your accounts via 'liara account:add' command, first." + "Please add your accounts via 'liara account:add' command, first.", ); } @@ -30,7 +31,7 @@ export default class AccountUse extends Command { const selectedAccount = liara_json.accounts[name]; !selectedAccount && this.error( - `Could not find any account associated with this name ${name}.` + `Could not find any account associated with this name ${name}.`, ); for (const account of Object.keys(liara_json.accounts)) { @@ -45,7 +46,7 @@ export default class AccountUse extends Command { JSON.stringify({ version: GLOBAL_CONF_VERSION, accounts: liara_json.accounts, - }) + }), ); this.log(chalk.green('> Auth credentials changed.')); } diff --git a/src/commands/login.ts b/src/commands/login.ts index 872242c4..bf52be65 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -7,6 +7,7 @@ import AccountAdd from './account/add.js'; import AccountUse from './account/use.js'; import { createDebugLogger } from '../utils/output.js'; import { GLOBAL_CONF_PATH, GLOBAL_CONF_VERSION } from '../constants.js'; +import ora from 'ora'; export default class Login extends Command { static description = 'login to your account'; @@ -26,6 +27,7 @@ export default class Login extends Command { }; async run() { + this.spinner = ora(); const { flags } = await this.parse(Login); const debug = createDebugLogger(flags.debug); @@ -75,7 +77,7 @@ export default class Login extends Command { JSON.stringify({ accounts: currentAccounts, version: GLOBAL_CONF_VERSION, - }) + }), ); this.spinner.succeed('You have logged in successfully.'); @@ -93,7 +95,7 @@ export default class Login extends Command { debug(`${error.message}\n`); this.spinner.fail( - 'Cannot open browser. Browser unavailable or lacks permissions.' + 'Cannot open browser. Browser unavailable or lacks permissions.', ); } } diff --git a/src/types/network.ts b/src/types/network.ts index 2dff0b6f..9de02c7d 100644 --- a/src/types/network.ts +++ b/src/types/network.ts @@ -2,4 +2,6 @@ export default interface INetwork { _id: string; name: string; createdAt: string; + projectCount: number, + databaseCount: number } diff --git a/test/app/create.unit.test.ts b/test/app/create.unit.test.ts new file mode 100644 index 00000000..75919ade --- /dev/null +++ b/test/app/create.unit.test.ts @@ -0,0 +1,201 @@ +import { runCommand } from '@oclif/test'; +import nock from 'nock'; +import { expect } from 'chai'; +import { networks } from '../fixtures/networks/fixture.ts'; + +describe('app:create', function () { + const api = nock('https://api.iran.liara.ir'); + + beforeEach(() => { + api.get('/v1/networks').query({ teamID: '' }).reply(200, networks); + }); + + afterEach(() => { + api.done(); + nock.cleanAll(); + }); + + it('creates an app with the specified flags', async () => { + api + .post('/v1/projects/', { + name: 'test-app', + planID: 'small-g2', + platform: 'laravel', + bundlePlanID: 'standard', + network: networks.networks[0]._id, + readOnlyRootFilesystem: true, + }) + .query({ teamID: '' }) + .reply(200); + + const { stdout } = await runCommand([ + 'app:create', + '--app', + 'test-app', + '--platform', + 'laravel', + '--plan', + 'small-g2', + '--feature-plan', + 'standard', + '--network', + networks.networks[0].name, + '--read-only', + 'true', + ]); + + expect(stdout).to.equal(`App test-app created.\n`); + }); + + it('throws an error if app name already exists', async () => { + api + .post('/v1/projects/', { + name: 'test-app', + planID: 'small-g2', + platform: 'laravel', + bundlePlanID: 'standard', + network: networks.networks[0]._id, + readOnlyRootFilesystem: true, + }) + .query({ teamID: '' }) + .reply(409, { + statusCode: 409, + error: 'Conflict', + message: 'Project exists.', + data: null, + }); + + const { error } = await runCommand([ + 'app:create', + '--app', + 'test-app', + '--platform', + 'laravel', + '--plan', + 'small-g2', + '--feature-plan', + 'standard', + '--network', + networks.networks[0].name, + '--read-only', + 'true', + ]); + + expect(error?.message).to.equal( + 'The app already exists. Please use a unique name for your app.', + ); + }); + + it('throws an error if the network is not found', async () => { + const { error } = await runCommand([ + 'app:create', + '--app', + 'test-app', + '--platform', + 'laravel', + '--plan', + 'small-g2', + '--feature-plan', + 'standard', + '--network', + 'not-found', + '--read-only', + 'true', + ]); + + expect(error?.message).to.equal('Network not-found not found.'); + }); + + it('thorws an error if user select a feature plan that is not available for free plan', async () => { + const { error } = await runCommand([ + 'app:create', + '--app', + 'test-app', + '--platform', + 'laravel', + '--plan', + 'free', + '--feature-plan', + 'standard', + '--network', + networks.networks[0].name, + '--read-only', + 'true', + ]); + + expect(error?.message).to.equal( + `Only "free" feature bundle plan is available for free plan.`, + ); + }); + + it('throws an error if the user does not have enough balance', async () => { + api + .post('/v1/projects/', { + name: 'test-app', + planID: 'small-g2', + platform: 'laravel', + bundlePlanID: 'standard', + network: networks.networks[0]._id, + readOnlyRootFilesystem: true, + }) + .query({ teamID: '' }) + .reply(402); + + const { error } = await runCommand([ + 'app:create', + '--app', + 'test-app', + '--platform', + 'laravel', + '--plan', + 'small-g2', + '--feature-plan', + 'standard', + '--network', + networks.networks[0].name, + '--read-only', + 'true', + ]); + + expect(error?.message).to.equal( + 'Not enough balance. Please charge your account.', + ); + }); + + it('throws proper error if the app creation fails with status code 500', async () => { + api + .post('/v1/projects/', { + name: 'test-app', + planID: 'small-g2', + platform: 'laravel', + bundlePlanID: 'standard', + network: networks.networks[0]._id, + readOnlyRootFilesystem: true, + }) + .query({ teamID: '' }) + .reply(500); + + const { error } = await runCommand([ + 'app:create', + '--app', + 'test-app', + '--platform', + 'laravel', + '--plan', + 'small-g2', + '--feature-plan', + 'standard', + '--network', + networks.networks[0].name, + '--read-only', + 'true', + ]); + + expect(error?.message).to.equal(`Error: Unable to Create App + Please try the following steps: + 1. Check your internet connection. + 2. Ensure you have enough balance. + 3. Try again later. + 4. If you still have problems, please contact support by submitting a ticket at https://console.liara.ir/tickets`); + }); +}); diff --git a/test/auth/account-use.unit.test.ts b/test/auth/account-use.unit.test.ts new file mode 100644 index 00000000..a3e15d9b --- /dev/null +++ b/test/auth/account-use.unit.test.ts @@ -0,0 +1,74 @@ +import { runCommand } from '@oclif/test'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { accounts, currentAccounts } from '../fixtures/accounts/fixture.ts'; +import fs from 'fs-extra'; +import path from 'path'; +import os from 'node:os'; +import AccountUse from '../../src/commands/account/use.ts'; + +describe('account:use', async () => { + let fsStub: sinon.SinonStub; + let promptAccount: sinon.SinonStub; + let liaraAuthConfigFile: sinon.SinonStub; + + beforeEach(async () => { + fsStub = sinon.stub(fs, 'writeFileSync'); + promptAccount = sinon.stub(AccountUse.prototype, 'promptName'); + liaraAuthConfigFile = sinon.stub(AccountUse.prototype, 'readGlobalConfig'); + }); + + afterEach(async () => { + sinon.restore(); + }); + it('should throw an error when the specified account does not exist', async () => { + liaraAuthConfigFile.resolves({ version: '1', accounts: currentAccounts }); + + const { error } = await runCommand([ + 'account:use', + '--account', + 'test5_iran', + ]); + + expect(error?.message).to.equal( + 'Could not find any account associated with this name test5_iran.', + ); + }); + + it('should switch the current account and persist changes to config', async () => { + liaraAuthConfigFile.resolves({ version: '1', accounts: currentAccounts }); + + const { error } = await runCommand([ + 'account:use', + '--account', + 'test4_iran', + ]); + + const expectedAccounts = { + test3_iran: { ...currentAccounts['test3_iran'], current: false }, + test4_iran: { ...currentAccounts['test4_iran'], current: true }, + }; + + expect( + fsStub.calledWithExactly( + path.join(os.homedir(), '.liara-auth.json'), + JSON.stringify({ version: '1', accounts: expectedAccounts }), + ), + ).to.be.true; + expect(error).to.be.undefined; + }); + + it('should throw an error if .liara-auth.json does not exist', async () => { + liaraAuthConfigFile.resolves(undefined); + + const { error } = await runCommand([ + 'account:use', + '--account', + 'test4_iran', + ]); + + expect(error?.message).to.equal( + "Please add your accounts via 'liara account:add' command, first.", + ); + }); +}); diff --git a/test/auth/login.unit.test.ts b/test/auth/login.unit.test.ts new file mode 100644 index 00000000..9189e03c --- /dev/null +++ b/test/auth/login.unit.test.ts @@ -0,0 +1,137 @@ +import { runCommand } from '@oclif/test'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { accounts, currentAccounts } from '../fixtures/accounts/fixture.ts'; +import Login from '../../src/commands/login.ts'; +import fs from 'fs-extra'; +import path from 'path'; +import os from 'node:os'; +import AccountUse from '../../src/commands/account/use.ts'; +import AccountAdd from '../../src/commands/account/add.ts'; + +describe('login', async () => { + let fsStub: sinon.SinonStub; + let browserStub: sinon.SinonStub; + let AccountUseStub: sinon.SinonStub; + let AccountAddStub: sinon.SinonStub; + + beforeEach(() => { + AccountUseStub = sinon.stub(AccountUse.prototype, 'run'); + AccountAddStub = sinon.stub(AccountAdd.prototype, 'run'); + browserStub = sinon + .stub(Login.prototype, 'browser') + .resolves(accounts.slice(0, 2)); + fsStub = sinon.stub(fs, 'writeFileSync'); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should create a new config file with accounts when none exist', async () => { + sinon + .stub(Login.prototype, 'readGlobalConfig') + .resolves({ version: '1', accounts: {} }); + await runCommand(['login']); + + const expectedData = JSON.stringify({ + accounts: { + [`${accounts[0].email.split('@')[0]}_${accounts[0].region}`]: { + email: accounts[0].email, + region: accounts[0].region, + avatar: accounts[0].avatar, + api_token: accounts[0].token, + fullname: accounts[0].fullname, + current: false, + }, + [`${accounts[1].email.split('@')[0]}_${accounts[1].region}`]: { + email: accounts[1].email, + region: accounts[1].region, + avatar: accounts[1].avatar, + api_token: accounts[1].token, + fullname: accounts[1].fullname, + current: false, + }, + }, + version: '1', + }); + expect( + fsStub.calledWithExactly( + path.join(os.homedir(), '.liara-auth.json'), + expectedData, + ), + ).to.be.true; + }); + + it('should merge new accounts into existing config without overwriting', async () => { + sinon + .stub(Login.prototype, 'readGlobalConfig') + .resolves({ version: '1', accounts: currentAccounts }); + + await runCommand(['login']); + + const expectedAccounts = { + ...currentAccounts, + [`${accounts[0].email.split('@')[0]}_${accounts[0].region}`]: { + email: accounts[0].email, + region: accounts[0].region, + avatar: accounts[0].avatar, + api_token: accounts[0].token, + fullname: accounts[0].fullname, + current: false, + }, + [`${accounts[1].email.split('@')[0]}_${accounts[1].region}`]: { + email: accounts[1].email, + region: accounts[1].region, + avatar: accounts[1].avatar, + api_token: accounts[1].token, + fullname: accounts[1].fullname, + current: false, + }, + }; + + const expectedData = JSON.stringify({ + accounts: expectedAccounts, + version: '1', + }); + + expect( + fsStub.calledWithExactly( + path.join(os.homedir(), '.liara-auth.json'), + expectedData, + ), + ).to.be.true; + }); + it('should pass the current account to account use command', async () => { + sinon + .stub(Login.prototype, 'readGlobalConfig') + .resolves({ version: '1', accounts: {} }); + + await runCommand(['login']); + + const currentAccount = accounts.find((data) => data.current); + const currentAccountName = `${currentAccount!.email.split('@')[0]}_${currentAccount!.region}`; + + expect(AccountUseStub.calledWithExactly(currentAccountName)); + }); + it('should delegate credentials to account:add in interactive mode (-i)', async () => { + await runCommand([ + 'login', + '-i', + `--email ${accounts[0].email}`, + `--password 123456`, + ]); + + expect( + AccountAddStub.calledWithExactly([ + '--api-token', + '', + '--email', + accounts[0].email, + '--password', + '', + 123456, + ]), + ); + }); +}); diff --git a/test/deploy/create-archive.test.ts b/test/deploy/create-archive.test.ts new file mode 100644 index 00000000..5a3b605d --- /dev/null +++ b/test/deploy/create-archive.test.ts @@ -0,0 +1,245 @@ +import { expect } from 'chai'; +import createArchive from '../../src/utils/create-archive.js'; +import prepareTmpDirectory from '../../src/services/tmp-dir.js'; +import getAllFiles from '../helpers/getAllFiles.js'; +import { extract } from 'tar'; +import fs from 'fs-extra'; + +describe('create-archive', async () => { + it('should throw an error if all files are ignored', async () => { + const sourcePath = prepareTmpDirectory(); + try { + await createArchive(sourcePath, 'test/fixtures/all-ignored', 'node'); + } catch (error) { + expect(error.message).to + .equal(`Seems like you have ignored everything so we can't upload any of your files. Please double-check the content of your .gitignore, .dockerignore and .liaraignore files. + +> Read more: https://docs.liara.ir/app-features/ignore`); + } + }); + + it('should create an archive and ignore files and directories listed in .gitignore', async () => { + const sourcePath = prepareTmpDirectory(); + + const originFiles = getAllFiles('test/fixtures/simple-gitignore'); + + await createArchive(sourcePath, 'test/fixtures/simple-gitignore', 'php'); + + fs.mkdirSync(`${sourcePath}-dir`); + + await extract({ + file: sourcePath, + cwd: `${sourcePath}-dir`, + }); + + const extractedFiles = getAllFiles(`${sourcePath}-dir`); + + expect(extractedFiles.sort()).to.deep.equal( + originFiles + .filter((val) => val != 'ignore_1.html' && val != 'ignore_2.html') + .sort(), + ); + }); + + it('should create an archive and ignore files and directories listed in .liaraignore', async () => { + const sourcePath = prepareTmpDirectory(); + + const originFiles = getAllFiles('test/fixtures/liaraignore'); + + await createArchive(sourcePath, 'test/fixtures/liaraignore', 'php'); + + fs.mkdirSync(`${sourcePath}-dir`); + + await extract({ + file: sourcePath, + cwd: `${sourcePath}-dir`, + }); + + const extractedFiles = getAllFiles(`${sourcePath}-dir`); + + expect(extractedFiles.sort()).to.deep.equal( + originFiles + .filter((val) => val != 'ignore_1.html' && val != 'ignore_2.html') + .sort(), + ); + }); + + it('should create an archive and ignore files and directories listed in .dockerignore', async () => { + const sourcePath = prepareTmpDirectory(); + + const originFiles = getAllFiles('test/fixtures/dockerignore'); + + await createArchive(sourcePath, 'test/fixtures/dockerignore', 'php'); + + fs.mkdirSync(`${sourcePath}-dir`); + + await extract({ + file: sourcePath, + cwd: `${sourcePath}-dir`, + }); + + const extractedFiles = getAllFiles(`${sourcePath}-dir`); + + expect(extractedFiles.sort()).to.deep.equal( + originFiles + .filter( + (val) => + val != 'ignore_1.html' && + val != 'ignore_2.html' && + val != '.dockerignore', + ) + .sort(), + ); + }); + + it('should ignore some files and directories by default', async () => { + const sourcePath = prepareTmpDirectory(); + + await createArchive(sourcePath, 'test/fixtures/default-ignores', 'node'); + + fs.mkdirSync(`${sourcePath}-dir`); + + await extract({ + file: sourcePath, + cwd: `${sourcePath}-dir`, + }); + + const extractedFiles = getAllFiles(`${sourcePath}-dir`); + expect(extractedFiles).to.deep.equal(['file.txt']); + }); + + it('should ignore files listed in .liaraignore and disregard .gitignore and .dockerignore if present', async () => { + const sourcePath = prepareTmpDirectory(); + + const originFiles = getAllFiles('test/fixtures/multiple-ignores'); + + await createArchive(sourcePath, 'test/fixtures/multiple-ignores', 'php'); + + fs.mkdirSync(`${sourcePath}-dir`); + + await extract({ + file: sourcePath, + cwd: `${sourcePath}-dir`, + }); + + const extractedFiles = getAllFiles(`${sourcePath}-dir`); + + expect(extractedFiles.sort()).to.deep.equal( + originFiles + .filter((val) => val != 'ignore_1.html' && val != '.dockerignore') + .sort(), + ); + }); + + it('should respect a nested .gitignore file and apply its rules only to its directory and subdirectories', async () => { + const sourcePath = prepareTmpDirectory(); + + const originFiles = getAllFiles('test/fixtures/nested-ignore-files'); + + await createArchive(sourcePath, 'test/fixtures/nested-ignore-files', 'php'); + + fs.mkdirSync(`${sourcePath}-dir`); + + await extract({ + file: sourcePath, + cwd: `${sourcePath}-dir`, + }); + + const extractedFiles = getAllFiles(`${sourcePath}-dir`); + + expect(extractedFiles.sort()).to.deep.equal( + originFiles + .filter((val) => val != 'ignore.me' && val != 'sub1/hello.html') + .sort(), + ); + }); + + it('should ignore some files and directories in django platform', async () => { + const sourcePath = prepareTmpDirectory(); + + await createArchive(sourcePath, 'test/fixtures/python-ignores', 'django'); + + fs.mkdirSync(`${sourcePath}-dir`); + + await extract({ + file: sourcePath, + cwd: `${sourcePath}-dir`, + }); + const extractedFiles = getAllFiles(`${sourcePath}-dir`); + + expect(extractedFiles.sort()).to.deep.equal( + ['test.txt', '.webassets-cache', 'instance/test.txt'].sort(), + ); + }); + + it('should ignore some files and directories in dotnet platform', async () => { + const sourcePath = prepareTmpDirectory(); + + await createArchive(sourcePath, 'test/fixtures/dotnet-ignores', 'dotnet'); + + fs.mkdirSync(`${sourcePath}-dir`); + + await extract({ + file: sourcePath, + cwd: `${sourcePath}-dir`, + }); + + const extractedFiles = getAllFiles(`${sourcePath}-dir`); + expect(extractedFiles).to.deep.equal(['test.txt']); + }); + + it('should ignore /vendor directory in php platform', async () => { + const sourcePath = prepareTmpDirectory(); + + await createArchive(sourcePath, 'test/fixtures/php-ignores', 'php'); + + fs.mkdirSync(`${sourcePath}-dir`); + + await extract({ + file: sourcePath, + cwd: `${sourcePath}-dir`, + }); + + const extractedFiles = getAllFiles(`${sourcePath}-dir`); + expect(extractedFiles).to.deep.equal(['index.php']); + }); + + it('should ignore /vendor directory in laravel platform', async () => { + const sourcePath = prepareTmpDirectory(); + + await createArchive(sourcePath, 'test/fixtures/php-ignores', 'laravel'); + + fs.mkdirSync(`${sourcePath}-dir`); + + await extract({ + file: sourcePath, + cwd: `${sourcePath}-dir`, + }); + + const extractedFiles = getAllFiles(`${sourcePath}-dir`); + expect(extractedFiles).to.deep.equal(['index.php']); + }); + + it('should ignore some files and directories in flask platform', async () => { + const sourcePath = prepareTmpDirectory(); + + await createArchive(sourcePath, 'test/fixtures/python-ignores', 'flask'); + + fs.mkdirSync(`${sourcePath}-dir`); + + await extract({ + file: sourcePath, + cwd: `${sourcePath}-dir`, + }); + const extractedFiles = getAllFiles(`${sourcePath}-dir`); + + expect(extractedFiles.sort()).to.deep.equal( + [ + 'test.txt', + 'test.log', + 'local_settings.py', + 'staticfiles/test.txt', + ].sort(), + ); + }); +}); diff --git a/test/deploy/deploy.unit.test.ts b/test/deploy/deploy.unit.test.ts new file mode 100644 index 00000000..b2b15722 --- /dev/null +++ b/test/deploy/deploy.unit.test.ts @@ -0,0 +1,134 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { runCommand } from '@oclif/test'; +import deploy from '../../src/commands/deploy.ts'; +import nock from 'nock'; +import { + projects, + getNodeProject, + getDockerProject, +} from '../fixtures/projects/fixture.ts'; + +describe('deploy', () => { + const api = nock('https://api.iran.liara.ir'); + + let getConfigs: sinon.SinonStub; + beforeEach(() => { + getConfigs = sinon.stub(deploy.prototype, 'getMergedConfig'); + }); + afterEach(() => { + sinon.restore(); + }); + it('should thorw an error when project path is empty and image flag is specified', async () => { + getConfigs.returns({ path: 'test/fixtures/empty-project' }); + + const { error } = await runCommand(['deploy']); + + expect(error?.message).to.equal('Directory is empty!'); + }); + + it('should throw an error if healthcheck in specefied but healthcheck command is not', async () => { + getConfigs.returns({ healthCheck: {}, path: 'test/fixtures/nodejs-app' }); + + const { error } = await runCommand(['deploy']); + + expect(error?.message).to.equal( + '`command` field in healthCheck is required.', + ); + }); + + it('should throw an error if cache config is not a boolean', async () => { + getConfigs.returns({ + build: { + cache: 'string', + }, + path: 'test/fixtures/nodejs-app', + }); + + const { error } = await runCommand(['deploy']); + + expect(error?.message).to.equal( + '`cache` parameter field must be a boolean.', + ); + }); + + it('should throw an error if platform is node and start command is not provided in package.json.', async () => { + getConfigs.returns({ + platform: 'node', + path: 'test/fixtures/nodejs-app', + app: getNodeProject.project.project_id, + }); + + api + .get(`/v1/projects/${getNodeProject.project.project_id}`) + .query({ teamID: '' }) + .reply(200, getNodeProject); + + const { stdout } = await runCommand(['deploy', '--debug']); + expect(stdout).to.contains( + `Error: A NodeJS app must be runnable with 'npm start'`, + ); + }); + + it('should create a release if platform is docker.', async () => { + getConfigs.returns({ + path: 'test/fixtures/docker-platform/', + app: 'testdocker', + image: 'getmeili/meilisearch:v0.28', + port: 7700, + disks: [{ name: 'data', mountTo: '/meili_data' }], + platform: 'docker', + detach: true, + 'no-app-logs': false, + args: undefined, + }); + api + .get('/v1/projects/testdocker') + .query({ teamID: '' }) + .reply(200, getDockerProject); + + api + .post('/v2/projects/testdocker/releases', { + build: { + cache: true, + args: undefined, + dockerfile: undefined, + location: undefined, + }, + cron: undefined, + args: undefined, + port: 7700, + type: 'docker', + message: undefined, + disks: [ + { + name: 'data', + mountTo: '/meili_data', + }, + ], + image: 'getmeili/meilisearch:v0.28', + }) + .query({ teamID: '' }) + .reply(200, { releaseID: '6rqwrqwrqweBwd34124314' }); + + const { stdout } = await runCommand([ + 'deploy', + '--debug', + '--detach', + '--platform', + 'docker', + '--path', + 'test/fixtures/docker-platform', + ]); + + expect(stdout).to.match(/Disks:\s+data -> \/meili_data/); + expect(stdout).to.match( + /\[debug\] \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] Using Build Cache: Enabled/, + ); + expect(stdout).to.contain('App: testdocker'); + expect(stdout).to.contain('Path: test/fixtures/docker-platform/'); + expect(stdout).to.contain('Platform: docker'); + expect(stdout).to.contain('Port: 7700'); + expect(stdout).to.contain('Deployment created successfully.'); + }); +}); diff --git a/test/fixtures/accounts/fixture.ts b/test/fixtures/accounts/fixture.ts new file mode 100644 index 00000000..9c7d634a --- /dev/null +++ b/test/fixtures/accounts/fixture.ts @@ -0,0 +1,53 @@ +import IBrowserLogin from '../../../src/types/browser-login'; +export const accounts: IBrowserLogin[] = [ + { + fullname: 'test-name1', + region: 'iran', + avatar: '//www.gravatar.com/avatar/test1?d=mp', + email: 'test1@gmail.com', + token: 'dkmfewfp[ewkfp[kewp[fkef[pewf', + current: false, + }, + { + fullname: 'test-name2', + region: 'iran', + avatar: '//www.gravatar.com/avatar/test2?d=mp', + email: 'test2@gmail.com', + token: 'dfhefiowejhniofjiowejfijewiopf', + current: false, + }, + { + fullname: 'test-name3', + region: 'iran', + avatar: '//www.gravatar.com/avatar/test3?d=mp', + email: 'test3@gmail.com', + token: 'dasdsaddkmfewfp[ewkfp[kewp[fkef[pewf', + current: true, + }, + { + fullname: 'test-name4', + region: 'iran', + avatar: '//www.gravatar.com/avatar/test4?d=mp', + email: 'test4@gmail.com', + token: 'dfhefiowejdsdsadasdhniofjiowejfijewiopf', + current: false, + }, +]; +export const currentAccounts = { + test3_iran: { + fullname: 'test-name3', + region: 'iran', + avatar: '//www.gravatar.com/avatar/test3?d=mp', + email: 'test3@gmail.com', + token: 'dasdsaddkmfewfp[ewkfp[kewp[fkef[pewf', + current: true, + }, + test4_iran: { + fullname: 'test-name4', + region: 'iran', + avatar: '//www.gravatar.com/avatar/test4?d=mp', + email: 'test4@gmail.com', + token: 'dfhefiowejdsdsadasdhniofjiowejfijewiopf', + current: false, + }, +}; diff --git a/test/fixtures/all-ignored/.gitignore b/test/fixtures/all-ignored/.gitignore new file mode 100644 index 00000000..5fb03d00 --- /dev/null +++ b/test/fixtures/all-ignored/.gitignore @@ -0,0 +1 @@ +./* \ No newline at end of file diff --git a/test/fixtures/default-ignores/bower_components/ignore.txt b/test/fixtures/all-ignored/app.js similarity index 100% rename from test/fixtures/default-ignores/bower_components/ignore.txt rename to test/fixtures/all-ignored/app.js diff --git a/test/fixtures/default-ignores/index.js b/test/fixtures/all-ignored/server.js similarity index 100% rename from test/fixtures/default-ignores/index.js rename to test/fixtures/all-ignored/server.js diff --git a/test/fixtures/dotnet-apps/app1/nest1/nest2/nest3/nest4/nest5/Program.cs b/test/fixtures/default-ignores/.DS_Store similarity index 100% rename from test/fixtures/dotnet-apps/app1/nest1/nest2/nest3/nest4/nest5/Program.cs rename to test/fixtures/default-ignores/.DS_Store diff --git a/test/fixtures/dotnet-apps/app1/nest1/nest2/nest3/nest4/nest5/app.csproj b/test/fixtures/default-ignores/.dockerignore similarity index 100% rename from test/fixtures/dotnet-apps/app1/nest1/nest2/nest3/nest4/nest5/app.csproj rename to test/fixtures/default-ignores/.dockerignore diff --git a/test/fixtures/dotnet-apps/app2/nest1/nest2/nest3/nest4/Program.cs b/test/fixtures/default-ignores/.idea/.gitkeep similarity index 100% rename from test/fixtures/dotnet-apps/app2/nest1/nest2/nest3/nest4/Program.cs rename to test/fixtures/default-ignores/.idea/.gitkeep diff --git a/test/fixtures/dotnet-apps/app2/nest1/nest2/nest3/nest4/app.csproj b/test/fixtures/default-ignores/.next/.gitkeep similarity index 100% rename from test/fixtures/dotnet-apps/app2/nest1/nest2/nest3/nest4/app.csproj rename to test/fixtures/default-ignores/.next/.gitkeep diff --git a/test/fixtures/dotnet-apps/app3/app.csproj b/test/fixtures/default-ignores/.vscode/.gitkeep similarity index 100% rename from test/fixtures/dotnet-apps/app3/app.csproj rename to test/fixtures/default-ignores/.vscode/.gitkeep diff --git a/test/fixtures/dotnet-apps/app3/nest1/Program.cs b/test/fixtures/default-ignores/bower_components/.gitkeep similarity index 100% rename from test/fixtures/dotnet-apps/app3/nest1/Program.cs rename to test/fixtures/default-ignores/bower_components/.gitkeep diff --git a/test/fixtures/dotnet-apps/app4/Startup.cs b/test/fixtures/default-ignores/example.txt~ similarity index 100% rename from test/fixtures/dotnet-apps/app4/Startup.cs rename to test/fixtures/default-ignores/example.txt~ diff --git a/test/fixtures/default-ignores/file.txt b/test/fixtures/default-ignores/file.txt new file mode 100644 index 00000000..7f6809e0 --- /dev/null +++ b/test/fixtures/default-ignores/file.txt @@ -0,0 +1 @@ +This should be kept \ No newline at end of file diff --git a/test/fixtures/default-ignores/liara.json b/test/fixtures/default-ignores/liara.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/test/fixtures/default-ignores/liara.json @@ -0,0 +1 @@ +{} diff --git a/test/fixtures/dockerignore/.dockerignore b/test/fixtures/dockerignore/.dockerignore new file mode 100644 index 00000000..f91a63f2 --- /dev/null +++ b/test/fixtures/dockerignore/.dockerignore @@ -0,0 +1,2 @@ +ignore_1.html +ignore_2.html \ No newline at end of file diff --git a/test/fixtures/dockerignore/ignore_1.html b/test/fixtures/dockerignore/ignore_1.html new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/test/fixtures/dockerignore/ignore_1.html @@ -0,0 +1 @@ +test diff --git a/test/fixtures/dockerignore/ignore_2.html b/test/fixtures/dockerignore/ignore_2.html new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/test/fixtures/dockerignore/ignore_2.html @@ -0,0 +1 @@ +test diff --git a/test/fixtures/dotnet-apps/app4/boom.csproj b/test/fixtures/dockerignore/index.php similarity index 100% rename from test/fixtures/dotnet-apps/app4/boom.csproj rename to test/fixtures/dockerignore/index.php diff --git a/test/fixtures/dotnet-ignores/test.txt b/test/fixtures/dotnet-ignores/test.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/dotnet-ignores/x64 b/test/fixtures/dotnet-ignores/x64 new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/dotnet-ignores/x86 b/test/fixtures/dotnet-ignores/x86 new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/liaraignore/.liaraignore b/test/fixtures/liaraignore/.liaraignore new file mode 100644 index 00000000..f91a63f2 --- /dev/null +++ b/test/fixtures/liaraignore/.liaraignore @@ -0,0 +1,2 @@ +ignore_1.html +ignore_2.html \ No newline at end of file diff --git a/test/fixtures/liaraignore/ignore_1.html b/test/fixtures/liaraignore/ignore_1.html new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/test/fixtures/liaraignore/ignore_1.html @@ -0,0 +1 @@ +test diff --git a/test/fixtures/liaraignore/ignore_2.html b/test/fixtures/liaraignore/ignore_2.html new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/test/fixtures/liaraignore/ignore_2.html @@ -0,0 +1 @@ +test diff --git a/test/fixtures/liaraignore/index.php b/test/fixtures/liaraignore/index.php new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/multiple-ignores/.dockerignore b/test/fixtures/multiple-ignores/.dockerignore new file mode 100644 index 00000000..603c9aa1 --- /dev/null +++ b/test/fixtures/multiple-ignores/.dockerignore @@ -0,0 +1 @@ +ignore_3.html diff --git a/test/fixtures/multiple-ignores/.gitignore b/test/fixtures/multiple-ignores/.gitignore new file mode 100644 index 00000000..29a83eae --- /dev/null +++ b/test/fixtures/multiple-ignores/.gitignore @@ -0,0 +1 @@ +ignore_2.html diff --git a/test/fixtures/multiple-ignores/.liaraignore b/test/fixtures/multiple-ignores/.liaraignore new file mode 100644 index 00000000..9dfe8fd2 --- /dev/null +++ b/test/fixtures/multiple-ignores/.liaraignore @@ -0,0 +1 @@ +ignore_1.html diff --git a/test/fixtures/multiple-ignores/ignore_1.html b/test/fixtures/multiple-ignores/ignore_1.html new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/test/fixtures/multiple-ignores/ignore_1.html @@ -0,0 +1 @@ +test diff --git a/test/fixtures/multiple-ignores/ignore_2.html b/test/fixtures/multiple-ignores/ignore_2.html new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/test/fixtures/multiple-ignores/ignore_2.html @@ -0,0 +1 @@ +test diff --git a/test/fixtures/multiple-ignores/ignore_3.html b/test/fixtures/multiple-ignores/ignore_3.html new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/multiple-ignores/index.php b/test/fixtures/multiple-ignores/index.php new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/networks/fixture.ts b/test/fixtures/networks/fixture.ts new file mode 100644 index 00000000..2fb04da1 --- /dev/null +++ b/test/fixtures/networks/fixture.ts @@ -0,0 +1,20 @@ +import IGetNetworkResponse from "../../../src/types/get-network-response" +export const networks: IGetNetworkResponse = { + networks: [ + { + _id: "64c7f1a2b3e8c91d0e5f7b2a", + name: "network-abc123", + createdAt: "2023-10-05T12:34:56Z", + projectCount: 1, + databaseCount: 0 + }, + { + _id: "64c7f1a2b3e8c91d0e5f7b2b", + name: "network-xyz789", + createdAt: "2023-10-01T09:15:30Z", + projectCount: 1, + databaseCount: 0 + } + ] +}; + diff --git a/test/fixtures/nodejs-app/.gitignore b/test/fixtures/nodejs-app/.gitignore new file mode 100644 index 00000000..40b878db --- /dev/null +++ b/test/fixtures/nodejs-app/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/test/fixtures/nodejs-app/README.md b/test/fixtures/nodejs-app/README.md new file mode 100644 index 00000000..a4dc9434 --- /dev/null +++ b/test/fixtures/nodejs-app/README.md @@ -0,0 +1,28 @@ +# Node apps getting started + +Example of how deploy a simple Node project on [liara](https://liara.ir). + +## Deploying + +[Create New Node App](https://console.liara.ir/apps/create) & install the [Liara CLI](https://docs.liara.ir/cli/install) + +```bash +$ git clone https://github.com/liara-cloud/nodejs-getting-started.git # or clone your own fork + +$ cd node-getting-started + +$ liara deploy +``` + +## Availabe Branches + +1. [Adding liara.json file](https://github.com/liara-cloud/nodejs-getting-started/tree/liaraJson) +2. [Disk setup](https://github.com/liara-cloud/nodejs-getting-started/tree/diskSetup) +3. [Email Server in Liara](https://github.com/liara-cloud/nodejs-getting-started/tree/email-server) +4. [Oject Srorage in Liara](https://github.com/liara-cloud/nodejs-getting-started/tree/object-storage) +5. [Headless Chrome Puppeteer](https://github.com/liara-cloud/nodejs-getting-started/tree/headless-chrome-puppeteer) +6. [Prisma](https://github.com/liara-cloud/nodejs-getting-started/tree/prisma) + +## Documentation + +Read more on liara [Node apps documentation](https://docs.liara.ir/app-deploy/nodejs/getting-started) diff --git a/test/fixtures/nodejs-app/index.html b/test/fixtures/nodejs-app/index.html new file mode 100644 index 00000000..841956db --- /dev/null +++ b/test/fixtures/nodejs-app/index.html @@ -0,0 +1,19 @@ + + + + + + + Deployed to Liara + + + + + +

Hooray!

+ + + + + + diff --git a/test/fixtures/nodejs-app/package.json b/test/fixtures/nodejs-app/package.json new file mode 100644 index 00000000..c9c4e4e1 --- /dev/null +++ b/test/fixtures/nodejs-app/package.json @@ -0,0 +1,11 @@ +{ + "name": "node-getting-started", + "version": "1.0.0", + "description": "simple web server with expressjs", + "main": "server.js", + "scripts": {}, + "license": "ISC", + "dependencies": { + "express": "^4.17.1" + } +} diff --git a/test/fixtures/nodejs-app/server.js b/test/fixtures/nodejs-app/server.js new file mode 100644 index 00000000..416a3b42 --- /dev/null +++ b/test/fixtures/nodejs-app/server.js @@ -0,0 +1,14 @@ +const express = require('express'); +const app = express(); +const path = require('path'); +const LIARA_URL = process.env.LIARA_URL || 'localhost'; + +app.use(express.static('public')); + +app.get('/', function (req, res) { + res.sendFile(path.join(__dirname + '/index.html')); +}); + +app.listen(3005, () => + console.log(`app listening on port 3005 on ${LIARA_URL}`), +); diff --git a/test/fixtures/php-ignores/index.php b/test/fixtures/php-ignores/index.php new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/php-ignores/vendor/.gitkeep b/test/fixtures/php-ignores/vendor/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/projects/fixture.ts b/test/fixtures/projects/fixture.ts new file mode 100644 index 00000000..75ea4e8b --- /dev/null +++ b/test/fixtures/projects/fixture.ts @@ -0,0 +1,88 @@ +import { + IGetProjectsResponse, + IProjectDetailsResponse, +} from '../../../src/base'; +export const projects: IGetProjectsResponse = { + projects: [ + { + _id: '64c7f1a2b3e8c91d0e5f7b2a', + project_id: 'proj12345', + type: 'node', + status: 'ACTIVE', + scale: 1, + planID: 'standard-plus-g2', + bundlePlanID: 'basic', + created_at: '2023-10-05T12:34:56Z', + isDeployed: true, + network: { + _id: '64c7f1a2b3e8c91d0e5f7b2b', + name: 'network-abc123', + }, + }, + { + _id: '64c7f1a2b3e8c91d0e5f7b2c', + project_id: 'testdocker', + type: 'docker', + status: 'ACTIVE', + scale: 1, + planID: 'medium-g2', + bundlePlanID: 'standard', + created_at: '2025-03-16T10:00:19.277Z', + isDeployed: false, + network: { + _id: '64c7f1a2b3e8c91d0e5f7b2d', + name: 'network-xyz789', + }, + }, + ], +}; + +export const getNodeProject: IProjectDetailsResponse = { + project: { + _id: '64c7f1a2b3e8c91d0e5f7b2a', + project_id: 'proj12345', + type: 'node', + status: 'ACTIVE', + readOnlyRootFilesystem: true, + defaultSubdomain: true, + zeroDowntime: true, + scale: 1, + envs: [], + planID: 'standard-plus-g2', + bundlePlanID: 'basic', + network: { + _id: '64c7f1a2b3e8c91d0e5f7b2b', + name: 'network-abc123', + }, + created_at: '2023-10-05T12:34:56Z', + fixedIPStatus: 'ACTIVE', + hourlyPrice: 137.5, + isDeployed: true, + reservedDiskSpace: 0, + }, +}; + +export const getDockerProject: IProjectDetailsResponse = { + project: { + _id: '65435435432565kihvudoifjoip', + project_id: 'testdocker', + type: 'docker', + status: 'ACTIVE', + readOnlyRootFilesystem: false, + defaultSubdomain: true, + zeroDowntime: true, + scale: 1, + envs: [], + planID: 'medium-g2', + bundlePlanID: 'standard', + network: { + _id: '64c7f1a2b3e8c91d0e5f7b2d', + name: 'network-xyz789', + }, + created_at: '2025-03-16T10:00:19.277Z', + hourlyPrice: 206.9, + isDeployed: false, + fixedIPStatus: 'ACTIVE', + reservedDiskSpace: 2, + }, +}; diff --git a/test/fixtures/python-ignores/.cache b/test/fixtures/python-ignores/.cache new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/.env b/test/fixtures/python-ignores/.env new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/.python-version b/test/fixtures/python-ignores/.python-version new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/.venv b/test/fixtures/python-ignores/.venv new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/.webassets-cache b/test/fixtures/python-ignores/.webassets-cache new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/ENV b/test/fixtures/python-ignores/ENV new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/celerybeat-schedule b/test/fixtures/python-ignores/celerybeat-schedule new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/example$py.class b/test/fixtures/python-ignores/example$py.class new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/example.pyd b/test/fixtures/python-ignores/example.pyd new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/instance/test.txt b/test/fixtures/python-ignores/instance/test.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/lib/.gitkeep b/test/fixtures/python-ignores/lib/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/lib64/.gitkeep b/test/fixtures/python-ignores/lib64/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/local_settings.py b/test/fixtures/python-ignores/local_settings.py new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/pip-delete-this-directory.txt b/test/fixtures/python-ignores/pip-delete-this-directory.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/pip-log.txt b/test/fixtures/python-ignores/pip-log.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/staticfiles/test.txt b/test/fixtures/python-ignores/staticfiles/test.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/test.log b/test/fixtures/python-ignores/test.log new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/python-ignores/test.txt b/test/fixtures/python-ignores/test.txt new file mode 100644 index 00000000..30d74d25 --- /dev/null +++ b/test/fixtures/python-ignores/test.txt @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/test/fixtures/python-ignores/venv b/test/fixtures/python-ignores/venv new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/simple-gitignore/.gitignore b/test/fixtures/simple-gitignore/.gitignore index dcaf7169..15efd3a0 100644 --- a/test/fixtures/simple-gitignore/.gitignore +++ b/test/fixtures/simple-gitignore/.gitignore @@ -1 +1,2 @@ -index.html +ignore_2.html +ignore_1.html diff --git a/test/fixtures/simple-gitignore/ignore_1.html b/test/fixtures/simple-gitignore/ignore_1.html new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/test/fixtures/simple-gitignore/ignore_1.html @@ -0,0 +1 @@ +test diff --git a/test/fixtures/simple-gitignore/ignore_2.html b/test/fixtures/simple-gitignore/ignore_2.html new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/test/fixtures/simple-gitignore/ignore_2.html @@ -0,0 +1 @@ +test diff --git a/test/fixtures/simple-gitignore/index.html b/test/fixtures/simple-gitignore/index.html deleted file mode 100644 index ce013625..00000000 --- a/test/fixtures/simple-gitignore/index.html +++ /dev/null @@ -1 +0,0 @@ -hello diff --git a/test/global.config.spec.ts b/test/global.config.spec.ts deleted file mode 100644 index afa1ca70..00000000 --- a/test/global.config.spec.ts +++ /dev/null @@ -1,254 +0,0 @@ -import got from 'got'; -import fs from 'fs-extra'; -import { Config } from '@oclif/core'; -import Command, { IConfig } from '../src/base'; -import { - GLOBAL_CONF_PATH, - GLOBAL_CONF_VERSION, - PREVIOUS_GLOBAL_CONF_PATH, -} from '../src/constants'; - -// test users credentials -const newContentCredentials = { - accounts: { - test1: { - email: 'test1@gmail.com', - avatar: '//www.gravatar.com/avatar/b27b143b69933c34caafcce34453f2b3?d=mp', - region: 'germany', - current: true, - fullname: 'test1', - api_token: 'test-api-token', - }, - test2: { - email: 'test2@gmail.com', - avatar: '//www.gravatar.com/avatar/d44cd1682dd31bf9ef8a5a67ca399bc1?d=mp', - region: 'iran', - current: false, - fullname: 'test2', - api_token: 'test2-api-token', - }, - }, - version: '1', -}; - -const oldContentCredentialsLogin = { - api_token: 'test-api-token', - region: 'iran', - current: null, -}; - -const oldContentCredentialsAccounts = { - api_token: 'test-multiaccount-api-token', - region: 'iran', - current: null, - accounts: { - user1: { - email: 'userone@gmail.com', - api_token: 'user1-multiaccount-api-token', - region: 'iran', - }, - user2: { - email: 'usertwo@gmail.com', - api_token: 'user2-multiaccount-api-token', - region: 'germany', - }, - }, -}; - -jest.mock('got'); - -// mocking config (user credentials) change direcotry to /tmp -jest.mock('../src/constants.ts', () => ({ - get GLOBAL_CONF_PATH() { - return '/tmp/.liara-auth.json'; - }, - get PREVIOUS_GLOBAL_CONF_PATH() { - return '/tmp/.liara.json'; - }, - - get GLOBAL_CONF_VERSION(): string { - return '1'; - }, - - REGIONS_API_URL: { - iran: 'https://api.liara.ir', - germany: 'https://api.liara.ir', - }, -})); - -// create files for user credentials in /tmp directory -async function createCredentials(path: string, content: any) { - // 1) create .liara-auth.json file - await fs.writeJSON(path, content); -} - -class TestConfig extends Command { - async run() { - // const {api_token, region} = oldContentCredentialsLogin - // console.log( - // await this.setAxiosConfig({ region: "iran", "api-token": "test" }) - // ); - // console.log(this.axiosConfig); - this.setGotConfig = (config: IConfig): Promise => { - return Promise.resolve(); - }; - - this.got = got; - return this.readGlobalConfig(); - } -} - -class TestGotRequest extends Command { - async run() { - await this.setGotConfig({ region: 'iran', 'api-token': 'test' }); - return this.got; - } -} -beforeAll(async () => { - await createCredentials('/tmp/.liara-auth.json', newContentCredentials); -}); - -describe('reading global configuration', () => { - test('check if new global path exist', async () => { - const content = await new TestConfig([], {} as Config).run(); - - // check if previous global path not exist any more. - const previousConfigExists = await fs.pathExists(PREVIOUS_GLOBAL_CONF_PATH); - expect(previousConfigExists).toBeFalsy(); - - // checking content of new config path (.liara-auth.json) - expect(content.version).toBe(GLOBAL_CONF_VERSION); - expect(content.accounts).toBeDefined(); - - expect(content.accounts.test1.email).toBe( - newContentCredentials.accounts.test1.email, - ); - expect(content.accounts.test1.avatar).toBe( - newContentCredentials.accounts.test1.avatar, - ); - expect(content.accounts.test1.region).toBe( - newContentCredentials.accounts.test1.region, - ); - expect(content.accounts.test1.current).toBe( - newContentCredentials.accounts.test1.current, - ); - expect(content.accounts.test1.fullname).toBe( - newContentCredentials.accounts.test1.fullname, - ); - expect(content.accounts.test1.api_token).toBe( - newContentCredentials.accounts.test1.api_token, - ); - expect(content.accounts.test2.email).toBe( - newContentCredentials.accounts.test2.email, - ); - expect(content.accounts.test2.avatar).toBe( - newContentCredentials.accounts.test2.avatar, - ); - expect(content.accounts.test2.region).toBe( - newContentCredentials.accounts.test2.region, - ); - expect(content.accounts.test2.current).toBe( - newContentCredentials.accounts.test2.current, - ); - expect(content.accounts.test2.fullname).toBe( - newContentCredentials.accounts.test2.fullname, - ); - expect(content.accounts.test2.api_token).toBe( - newContentCredentials.accounts.test2.api_token, - ); - }); - test('not only .liara-auth.json not exists but also .liara.json not exists too', async () => { - // delete both file credentials first - fs.removeSync(PREVIOUS_GLOBAL_CONF_PATH); - fs.removeSync(GLOBAL_CONF_PATH); - - const content = await new TestConfig([], {} as Config).run(); - expect(typeof content.accounts).toBe('object'); - expect(content.accounts).toBeDefined(); - expect(content.version).toBe(GLOBAL_CONF_VERSION); - }); - test('check if only .liara.json exist and user never add any accounts just login', async () => { - await createCredentials('/tmp/.liara.json', oldContentCredentialsLogin); - const data = { - user: { - api_token: 'test-api-token-from-server', - avatar: 'user-avatar', - fullname: 'test-user-name', - email: 'testuser@gmail.com', - }, - }; - //@ts-ignore - - got.get.mockImplementation((path: string, config: GotOptions) => { - return { - json() { - return Promise.resolve({ ...data }); - }, - }; - }); - - const content = await new TestConfig([], {} as Config).run(); - const accountName = `${data.user.email.split('@')[0]}_${ - oldContentCredentialsLogin.region - }`; - - expect(content.version).toBe(GLOBAL_CONF_VERSION); - expect(content.accounts).toBeDefined(); - expect(content.accounts[accountName]).toBeDefined(); - expect(content.accounts[accountName].api_token).toBe( - oldContentCredentialsLogin.api_token, - ); - expect(content.accounts[accountName].region).toBe( - oldContentCredentialsLogin.region, - ); - expect(content.accounts[accountName].fullname).toBe(data.user.fullname); - expect(content.accounts[accountName].email).toBe(data.user.email); - expect(content.accounts[accountName].avatar).toBe(data.user.avatar); - expect(content.accounts[accountName].current).toBe(true); - }); - test('check if only .liara.json exist and user add accounts', async () => { - const data = { - user: { - api_token: 'test-api-token-from-server', - avatar: 'user-avatar', - fullname: 'test-user-name', - email: 'testuser@gmail.com', - }, - }; - //@ts-ignore - got.get.mockImplementation((path: string, config: GotOptions) => { - return { - json() { - return Promise.resolve({ ...data }); - }, - }; - }); - await createCredentials('/tmp/.liara.json', oldContentCredentialsAccounts); - const content = await new TestConfig([], {} as Config).run(); - - expect(content.accounts).toBeDefined(); - expect(content.version).toBe(GLOBAL_CONF_VERSION); - expect(content.accounts['user1']).toBeDefined(); - expect(content.accounts['user2']).toBeDefined(); - expect(content.accounts['user1'].current).toBe(false); - expect(content.accounts['user1'].avatar).toBe(data.user.avatar); - expect(content.accounts['user1'].fullname).toBe(data.user.fullname); - expect(content.accounts['user1'].api_token).toBe( - oldContentCredentialsAccounts.accounts.user1.api_token, - ); - expect(content.accounts['user1'].email).toBe(data.user.email); - expect(content.accounts['user1'].region).toBe( - oldContentCredentialsAccounts.accounts.user1.region, - ); - expect(content.accounts['user2'].current).toBe(false); - expect(content.accounts['user2'].avatar).toBe(data.user.avatar); - expect(content.accounts['user2'].fullname).toBe(data.user.fullname); - expect(content.accounts['user2'].api_token).toBe( - oldContentCredentialsAccounts.accounts.user2.api_token, - ); - expect(content.accounts['user2'].email).toBe(data.user.email); - expect(content.accounts['user2'].region).toBe( - oldContentCredentialsAccounts.accounts.user2.region, - ); - }); -}); diff --git a/test/got.config.spec.ts b/test/got.config.spec.ts deleted file mode 100644 index 5b5c8437..00000000 --- a/test/got.config.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import Command from '../src/base'; -import { Config } from '@oclif/core'; - -class TestGotRequest extends Command { - async run() { - await this.setGotConfig({ region: 'iran', 'api-token': 'test' }); - return this.got; - } -} - -test('http configuration', async () => { - const configs = await new TestGotRequest([], {} as Config).run(); - console.log(configs.defaults.options); - expect(configs.defaults.options.timeout.request).toBe(10000); - expect(configs.defaults.options.headers.authorization).toBe('Bearer test'); - expect(configs.defaults.options.prefixUrl).toBe('https://api.liara.ir/'); -}); diff --git a/test/helpers/getAllFiles.ts b/test/helpers/getAllFiles.ts new file mode 100644 index 00000000..82b1b6b6 --- /dev/null +++ b/test/helpers/getAllFiles.ts @@ -0,0 +1,20 @@ +import path from 'path'; +import fs from 'fs-extra'; + +export default function getAllFiles( + dir: string, + baseDir: string = dir, + filePaths: string[] = [], +): string[] { + const items = fs.readdirSync(dir); + items.forEach((item) => { + const itemPath = path.join(dir, item); + if (fs.statSync(itemPath).isDirectory()) { + getAllFiles(itemPath, baseDir, filePaths); + } else { + const relativePath = path.relative(baseDir, itemPath); + filePaths.push(relativePath); + } + }); + return filePaths; +} diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index 73fb8366..00000000 --- a/test/mocha.opts +++ /dev/null @@ -1,5 +0,0 @@ ---require ts-node/register ---watch-extensions ts ---recursive ---reporter spec ---timeout 5000 diff --git a/test/utils.test.ts b/test/utils.test.ts deleted file mode 100644 index 3a5683ba..00000000 --- a/test/utils.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { expect } from '@oclif/test'; - -import fixture from './utils/fixture'; -import getFiles from '../src/utils/get-files'; -import validatePort from '../src/utils/validate-port'; -import detectPlatform from '../src/utils/detect-platform'; - -describe('utils', () => { - it('should detect DotNet platform', () => { - expect(detectPlatform(fixture('dotnet-apps/app1'))).to.not.eq('dotnet'); // Too deep - expect(detectPlatform(fixture('dotnet-apps/app3'))).to.not.eq('dotnet'); // No .csproj file - - expect(detectPlatform(fixture('dotnet-apps/app2'))).to.eq('dotnet'); // Max deep - expect(detectPlatform(fixture('dotnet-apps/app4'))).to.eq('dotnet'); - }); - - it('should throw an error for invalid liara.json file', async () => { - expect(validatePort('asdf')).to.contain('number'); - expect(validatePort('3.2')).to.contain('integer'); - expect(validatePort('-3.2')).to.contain('integer'); - expect(validatePort('-80')).to.contain('integer'); - expect(validatePort('80')).to.be.eq(true); - expect(validatePort('5000')).to.be.eq(true); - expect(validatePort(5000)).to.be.eq(true); - expect(validatePort(80)).to.be.eq(true); - }); - - it('should respect .gitignore', async () => { - const { files } = await getFiles(fixture('simple-gitignore')); - expect(files).to.have.length(1); - }); - - it('should respect nested ignore files', async () => { - const { files } = await getFiles(fixture('nested-ignore-files')); - expect(files).to.have.length(2); - }); - - it("should respect ignore files' priority", async () => { - const { files } = await getFiles(fixture('ignore-files-priority')); - expect(files).to.have.length(2); - }); - - it('should ignore default ignore patterns', async () => { - const { files } = await getFiles(fixture('default-ignores')); - expect(files).to.have.length(1); - }); - - it('should override default ignore patterns', async () => { - const { files } = await getFiles(fixture('override-default-ignores')); - expect(files).to.have.length(2); - }); - - it('case sensitive ignore', async () => { - const { files } = await getFiles(fixture('ignore-case-sensitive')); - expect(files).to.have.length(1); - }); - - it('should ignore absolute patterns', async () => { - const { files } = await getFiles(fixture('ignore-absolute-patterns')); - expect(files).to.have.length(1); - }); -}); diff --git a/test/utils/fixture.ts b/test/utils/fixture.ts deleted file mode 100644 index e9655bef..00000000 --- a/test/utils/fixture.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function fixture(name: string): string { - return __dirname + '/../fixtures/' + name; -} diff --git a/test/utils/run.ts b/test/utils/run.ts deleted file mode 100644 index d810537f..00000000 --- a/test/utils/run.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { exec } from 'child_process'; -import * as path from 'path'; -import { Readable } from 'stream'; - -export default async function run(args: string[]) { - args.unshift( - 'node', - path.resolve(path.join(__dirname, '..', '..', 'bin', 'run')) - ); - const result = await exec(args.join(' ')); - return { - stderr: await concatStreamPromise(result.stderr), - stdout: await concatStreamPromise(result.stdout), - }; -} - -function concatStreamPromise(stream: Readable) { - return new Promise((resolve) => { - let result = ''; - stream.on('data', (data) => (result += data)); - stream.on('end', () => resolve(result)); - }); -}