From c47d37f2826b64416a5925a4c220014c5809b65b Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 28 May 2025 14:19:18 +0200 Subject: [PATCH 01/38] Remove workflowsPath from worker config and add env validation This commit removes the unused `workflowsPath` field from the worker configuration, simplifying the setup. Additionally, it introduces an environment validation utility to verify required environment variables, improving runtime robustness. Finally, updates to dependencies and `package-lock.json` reflect added packages like `uuid` and `dotenv` for functionality and testing. --- workers/main/.env.test | 5 + workers/main/package-lock.json | 913 +++++++++----------- workers/main/package.json | 9 +- workers/main/src/__tests__/utils.test.ts | 58 ++ workers/main/src/common/utils.ts | 15 + workers/main/src/configs/index.ts | 2 + workers/main/src/configs/redmineDatabase.ts | 15 + workers/main/src/configs/worker.ts | 3 - workers/main/vitest.config.ts | 9 +- 9 files changed, 489 insertions(+), 540 deletions(-) create mode 100644 workers/main/.env.test create mode 100644 workers/main/src/__tests__/utils.test.ts create mode 100644 workers/main/src/common/utils.ts create mode 100644 workers/main/src/configs/redmineDatabase.ts diff --git a/workers/main/.env.test b/workers/main/.env.test new file mode 100644 index 0000000..34e160e --- /dev/null +++ b/workers/main/.env.test @@ -0,0 +1,5 @@ +REDMINE_DB_HOST=localhost +REDMINE_DB_USER=testuser +REDMINE_DB_PASSWORD=testpassword +REDMINE_DB_NAME=testdb +DEBUG= \ No newline at end of file diff --git a/workers/main/package-lock.json b/workers/main/package-lock.json index 017f1b0..80767f6 100644 --- a/workers/main/package-lock.json +++ b/workers/main/package-lock.json @@ -17,9 +17,11 @@ }, "devDependencies": { "@eslint/js": "9.27.0", + "@temporalio/testing": "1.11.8", "@types/node": "22.15.21", "@vitest/coverage-v8": "3.1.3", "c8": "10.1.3", + "dotenv": "16.5.0", "eslint": "9.27.0", "eslint-config-prettier": "10.1.5", "eslint-import-resolver-typescript": "4.3.5", @@ -31,6 +33,7 @@ "ts-node": "10.9.1", "typescript": "5.8.3", "typescript-eslint": "8.32.1", + "uuid": "11.1.0", "vite": "6.3.5", "vitest": "3.1.3" } @@ -138,7 +141,6 @@ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "@emnapi/wasi-threads": "1.0.2", @@ -150,7 +152,6 @@ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" @@ -161,7 +162,6 @@ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" @@ -649,6 +649,30 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/config-helpers": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", @@ -696,6 +720,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { "version": "9.27.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", @@ -737,7 +785,6 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==", - "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.7.13", "@js-sdsl/ordered-map": "^4.4.2" @@ -750,7 +797,6 @@ "version": "0.7.15", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", - "license": "Apache-2.0", "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", @@ -894,7 +940,6 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -920,7 +965,6 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", - "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/js-sdsl" @@ -930,7 +974,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", - "license": "Apache-2.0", "engines": { "node": ">=10.0" }, @@ -946,7 +989,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz", "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==", - "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/base64": "^1.1.1", "@jsonjoy.com/util": "^1.1.2", @@ -968,7 +1010,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.6.0.tgz", "integrity": "sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==", - "license": "Apache-2.0", "engines": { "node": ">=10.0" }, @@ -985,7 +1026,6 @@ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.10.tgz", "integrity": "sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "@emnapi/core": "^1.4.3", @@ -1058,32 +1098,27 @@ "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" }, "node_modules/@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause" + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" }, "node_modules/@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -1092,32 +1127,27 @@ "node_modules/@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" }, "node_modules/@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" }, "node_modules/@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause" + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.41.0", @@ -1411,7 +1441,6 @@ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.29.tgz", "integrity": "sha512-g4mThMIpWbNhV8G2rWp5a5/Igv8/2UFRJx2yImrLGMgrDDYZIopqZ/z0jZxDgqNA1QDx93rpwNF7jGsxVWcMlA==", "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.21" @@ -1451,7 +1480,6 @@ "cpu": [ "arm64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -1467,7 +1495,6 @@ "cpu": [ "x64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -1483,7 +1510,6 @@ "cpu": [ "arm" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -1499,7 +1525,6 @@ "cpu": [ "arm64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -1515,7 +1540,6 @@ "cpu": [ "arm64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -1531,7 +1555,6 @@ "cpu": [ "x64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -1547,7 +1570,6 @@ "cpu": [ "x64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -1563,7 +1585,6 @@ "cpu": [ "arm64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -1579,7 +1600,6 @@ "cpu": [ "ia32" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -1595,7 +1615,6 @@ "cpu": [ "x64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -1607,14 +1626,12 @@ "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "license": "Apache-2.0" + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" }, "node_modules/@swc/types": { "version": "0.1.21", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.21.tgz", "integrity": "sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==", - "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" } @@ -1623,7 +1640,6 @@ "version": "1.11.8", "resolved": "https://registry.npmjs.org/@temporalio/activity/-/activity-1.11.8.tgz", "integrity": "sha512-XWjj/EL0YiKsnh0W4W5kBm+Xp1jhpUEaQMGA25+BxKEYBn8qJ+mLQOwuqWn6punS9a6bvnT7P/pUlnhjPxH6KQ==", - "license": "MIT", "dependencies": { "@temporalio/common": "1.11.8", "abort-controller": "^3.0.0" @@ -1633,7 +1649,6 @@ "version": "1.11.8", "resolved": "https://registry.npmjs.org/@temporalio/client/-/client-1.11.8.tgz", "integrity": "sha512-UpiU+awykWYjQSSJH5NLY5Id/uDdiD9yz5oaardv1YF80rMN7U1Q6W9lgPEbg/b62qOv3Mia1+fE5hrav7ying==", - "license": "MIT", "dependencies": { "@grpc/grpc-js": "^1.10.7", "@temporalio/common": "1.11.8", @@ -1643,11 +1658,22 @@ "uuid": "^9.0.1" } }, + "node_modules/@temporalio/client/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@temporalio/common": { "version": "1.11.8", "resolved": "https://registry.npmjs.org/@temporalio/common/-/common-1.11.8.tgz", "integrity": "sha512-hB+TSB/P2Vm8chYyZGcKALdywGvcBSqzSUzFG1lOm/JPDh2d4uK6lvE2S8OneEndlJSdzE++d2Mt5B+iKe2ytA==", - "license": "MIT", "dependencies": { "@temporalio/proto": "1.11.8", "long": "^5.2.3", @@ -1655,12 +1681,19 @@ "proto3-json-serializer": "^2.0.0" } }, + "node_modules/@temporalio/common/node_modules/ms": { + "version": "3.0.0-canary.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz", + "integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==", + "engines": { + "node": ">=12.13" + } + }, "node_modules/@temporalio/core-bridge": { "version": "1.11.8", "resolved": "https://registry.npmjs.org/@temporalio/core-bridge/-/core-bridge-1.11.8.tgz", "integrity": "sha512-s1EkihwUHGIOylF31UFnsnntZmVqcKEgVTu8UvAf/J/br4jhs+nEO/mBFexqrzSWnraJhpq9UnzTNAklNHFVKg==", "hasInstallScript": true, - "license": "MIT", "dependencies": { "@temporalio/common": "1.11.8", "arg": "^5.0.2", @@ -1668,21 +1701,62 @@ "which": "^4.0.0" } }, + "node_modules/@temporalio/core-bridge/node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, + "node_modules/@temporalio/core-bridge/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/@temporalio/core-bridge/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, "node_modules/@temporalio/proto": { "version": "1.11.8", "resolved": "https://registry.npmjs.org/@temporalio/proto/-/proto-1.11.8.tgz", "integrity": "sha512-L6QsIAq3PrnQpARAcqLCe4xMpbkpBEvA/9FQGddpzCESvViUOUAIHPwckL1DokYPedEAt19C+uuBGVBMCNx7vw==", - "license": "MIT", "dependencies": { "long": "^5.2.3", "protobufjs": "^7.2.5" } }, + "node_modules/@temporalio/testing": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@temporalio/testing/-/testing-1.11.8.tgz", + "integrity": "sha512-FC6A2YFDH6CDp7eeZ9aW9RDSzGxQqH6Old5UxBvIk5ZFgsCMOltpWqYMTIrv9EQbe6xMOTgPjP/6XgAqvIPj/g==", + "dev": true, + "dependencies": { + "@temporalio/activity": "1.11.8", + "@temporalio/client": "1.11.8", + "@temporalio/common": "1.11.8", + "@temporalio/core-bridge": "1.11.8", + "@temporalio/proto": "1.11.8", + "@temporalio/worker": "1.11.8", + "@temporalio/workflow": "1.11.8", + "abort-controller": "^3.0.0" + } + }, "node_modules/@temporalio/worker": { "version": "1.11.8", "resolved": "https://registry.npmjs.org/@temporalio/worker/-/worker-1.11.8.tgz", "integrity": "sha512-KNUEw8khhFoVLhOy93j9EkA6KfKtOVn7N7+c4kn9AM7QXkcxe73+1AUwNKgFYKZph4i6I26NBxs7MPrgh3GKxQ==", - "license": "MIT", "dependencies": { "@swc/core": "^1.3.102", "@temporalio/activity": "1.11.8", @@ -1706,11 +1780,24 @@ "node": ">= 16.0.0" } }, + "node_modules/@temporalio/worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/@temporalio/workflow": { "version": "1.11.8", "resolved": "https://registry.npmjs.org/@temporalio/workflow/-/workflow-1.11.8.tgz", "integrity": "sha512-gJy5Rr4WQCejdWskluHY/C20om0Yvx7/cLl9KHCweTyDLRfTu2VtMJRS8LPFQkKzoHBbLaqzPo2XdHh/u4JQoA==", - "license": "MIT", "dependencies": { "@temporalio/common": "1.11.8", "@temporalio/proto": "1.11.8" @@ -1749,7 +1836,6 @@ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" @@ -1759,7 +1845,6 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "license": "MIT", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -1769,7 +1854,6 @@ "version": "3.7.7", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "license": "MIT", "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -1805,7 +1889,6 @@ "version": "22.15.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==", - "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } @@ -1958,45 +2041,6 @@ "typescript": ">=4.8.4 <5.9.0" } }, - "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils": { "version": "8.32.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz", @@ -2047,7 +2091,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -2061,7 +2104,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -2075,7 +2117,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2089,7 +2130,6 @@ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -2103,7 +2143,6 @@ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -2117,7 +2156,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -2131,7 +2169,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -2145,7 +2182,6 @@ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -2159,7 +2195,6 @@ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -2173,7 +2208,6 @@ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -2187,7 +2221,6 @@ "s390x" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -2201,7 +2234,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -2215,7 +2247,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -2229,7 +2260,6 @@ "wasm32" ], "dev": true, - "license": "MIT", "optional": true, "dependencies": { "@napi-rs/wasm-runtime": "^0.2.9" @@ -2246,7 +2276,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -2260,7 +2289,6 @@ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -2274,7 +2302,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -2357,9 +2384,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.4.tgz", - "integrity": "sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.3.tgz", + "integrity": "sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==", "dev": true, "license": "MIT", "dependencies": { @@ -2398,19 +2425,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot/node_modules/@vitest/pretty-format": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.3.tgz", - "integrity": "sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/@vitest/spy": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.3.tgz", @@ -2439,24 +2453,10 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/utils/node_modules/@vitest/pretty-format": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.3.tgz", - "integrity": "sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "license": "MIT", "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -2465,26 +2465,22 @@ "node_modules/@webassemblyjs/floating-point-hex-parser": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT" + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT" + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT" + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "license": "MIT", "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -2494,14 +2490,12 @@ "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT" + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -2513,7 +2507,6 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -2522,7 +2515,6 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } @@ -2530,14 +2522,12 @@ "node_modules/@webassemblyjs/utf8": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT" + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -2553,7 +2543,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -2566,7 +2555,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -2578,7 +2566,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -2592,7 +2579,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -2601,20 +2587,17 @@ "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause" + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0" + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", "dependencies": { "event-target-shim": "^5.0.0" }, @@ -2678,7 +2661,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "license": "MIT", "dependencies": { "ajv": "^8.0.0" }, @@ -2695,7 +2677,6 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -2710,8 +2691,7 @@ "node_modules/ajv-formats/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/ansi-regex": { "version": "6.1.0", @@ -2727,24 +2707,23 @@ } }, "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, "license": "MIT" }, "node_modules/argparse": { @@ -2914,7 +2893,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", - "license": "MIT", "engines": { "node": ">= 6.0.0" } @@ -2927,14 +2905,13 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "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", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -2968,7 +2945,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "caniuse-lite": "^1.0.30001716", "electron-to-chromium": "^1.5.149", @@ -2985,8 +2961,7 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "node_modules/c8": { "version": "10.1.3", @@ -3109,14 +3084,12 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] }, "node_modules/cargo-cp-artifact": { "version": "0.1.9", "resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.9.tgz", "integrity": "sha512-6F+UYzTaGB+awsTXg0uSJA1/b/B3DDJzpKVRu0UmyI7DmNeaAl2RFHuTGIN6fEgpadRxoXGb7gbC1xo4C3IdyA==", - "license": "MIT", "bin": { "cargo-cp-artifact": "bin/cargo-cp-artifact.js" } @@ -3155,17 +3128,20 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "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==", + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "color-convert": "^2.0.1" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/check-error": { @@ -3182,7 +3158,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "license": "MIT", "engines": { "node": ">=6.0" } @@ -3210,6 +3185,21 @@ "node": ">=8" } }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -3280,8 +3270,7 @@ "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT" + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "node_modules/concat-map": { "version": "0.0.1", @@ -3319,29 +3308,6 @@ "node": ">= 8" } }, - "node_modules/cross-spawn/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -3414,13 +3380,6 @@ } } }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -3478,7 +3437,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "license": "Apache-2.0", "engines": { "node": ">=0.10" } @@ -3506,6 +3464,18 @@ "node": ">=0.10.0" } }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3529,10 +3499,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.155", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", - "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==", - "license": "ISC" + "version": "1.5.159", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.159.tgz", + "integrity": "sha512-CEvHptWAMV5p6GJ0Lq8aheyvVbfzVrv5mmidu1D3pidoVNkB3tTBsTMVtPJ+rzRK5oV229mCLz9Zj/hNvU8GBA==" }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -3545,7 +3514,6 @@ "version": "5.18.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -3555,9 +3523,9 @@ } }, "node_modules/es-abstract": { - "version": "1.23.10", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.10.tgz", - "integrity": "sha512-MtUbM072wlJNyeYAe0mhzrD+M6DIJa96CZAOBBrhDbgKnB4MApIKefcyAB1eOdYn8cUNZgvwBvEzdoAYsxgEIw==", + "version": "1.23.9", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", "dev": true, "license": "MIT", "dependencies": { @@ -3565,18 +3533,18 @@ "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", - "call-bound": "^1.0.4", + "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", + "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", @@ -3592,13 +3560,13 @@ "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", + "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", + "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", + "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", @@ -3611,7 +3579,7 @@ "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" + "which-typed-array": "^1.1.18" }, "engines": { "node": ">= 0.4" @@ -3835,7 +3803,6 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", "dev": true, - "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -3868,19 +3835,11 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-import-resolver-node/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/eslint-import-resolver-typescript": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.3.5.tgz", "integrity": "sha512-QGwhLrwn/WGOsdrWvjhm9n8BvKN/Wr41SQERMV7DQ2hm9+Ozas39CyQUxum///l2G2vefQVr7VbIaCFS5h9g5g==", "dev": true, - "license": "ISC", "dependencies": { "debug": "^4.4.0", "get-tsconfig": "^4.10.0", @@ -3937,13 +3896,6 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-module-utils/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/eslint-plugin-import": { "version": "2.31.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", @@ -3978,6 +3930,17 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -3988,12 +3951,28 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-import/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT" + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, "node_modules/eslint-plugin-prettier": { "version": "5.4.0", @@ -4066,6 +4045,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/espree": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", @@ -4142,7 +4145,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", "engines": { "node": ">=6" } @@ -4151,7 +4153,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", "engines": { "node": ">=0.8.x" } @@ -4236,8 +4237,7 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ], - "license": "BSD-3-Clause" + ] }, "node_modules/fastq": { "version": "1.19.1", @@ -4364,8 +4364,7 @@ "node_modules/fs-monkey": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", - "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", - "license": "Unlicense" + "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -4427,7 +4426,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "license": "MIT", "dependencies": { "is-property": "^1.0.2" } @@ -4499,11 +4497,10 @@ } }, "node_modules/get-tsconfig": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", - "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", "dev": true, - "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -4548,34 +4545,7 @@ "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause" - }, - "node_modules/glob/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/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" - } + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "node_modules/globals": { "version": "14.0.0", @@ -4623,8 +4593,7 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/graphemer": { "version": "1.4.0", @@ -4730,7 +4699,6 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/heap-js/-/heap-js-2.6.0.tgz", "integrity": "sha512-trFMIq3PATiFRiQmNNeHtsrkwYRByIXUbYNbotiY9RLVfMkdwZdd2eQ38mGt7BRiCKBaj1DyBAIHmm7mmXPuuw==", - "license": "BSD-3-Clause", "engines": { "node": ">=10.0.0" } @@ -4746,7 +4714,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", - "license": "MIT", "engines": { "node": ">=10.18" } @@ -4755,7 +4722,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -4888,25 +4854,11 @@ }, "node_modules/is-bun-module": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", - "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.7.1" - } - }, - "node_modules/is-bun-module/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "dependencies": { + "semver": "^7.7.1" } }, "node_modules/is-callable": { @@ -5083,8 +5035,7 @@ "node_modules/is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", - "license": "MIT" + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" }, "node_modules/is-regex": { "version": "1.2.1", @@ -5239,13 +5190,11 @@ "license": "MIT" }, "node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "license": "ISC", - "engines": { - "node": ">=16" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -5272,19 +5221,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/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/istanbul-lib-source-maps": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", @@ -5334,7 +5270,6 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "license": "MIT", "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -5344,6 +5279,20 @@ "node": ">= 10.13.0" } }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -5367,8 +5316,7 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -5425,7 +5373,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "license": "MIT", "engines": { "node": ">=6.11.5" } @@ -5449,8 +5396,7 @@ "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT" + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -5462,8 +5408,7 @@ "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" }, "node_modules/loupe": { "version": "3.1.3", @@ -5473,19 +5418,16 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "license": "ISC", - "engines": { - "node": ">=12" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" }, "node_modules/lru.min": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", - "license": "MIT", "engines": { "bun": ">=1.0.0", "deno": ">=1.30.0", @@ -5534,19 +5476,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -5568,7 +5497,6 @@ "version": "4.17.2", "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz", "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==", - "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/json-pack": "^1.0.3", "@jsonjoy.com/util": "^1.3.0", @@ -5586,8 +5514,7 @@ "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT" + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "node_modules/merge2": { "version": "1.4.1", @@ -5630,7 +5557,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5639,7 +5565,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -5648,16 +5573,19 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -5681,19 +5609,16 @@ } }, "node_modules/ms": { - "version": "3.0.0-canary.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz", - "integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==", - "license": "MIT", - "engines": { - "node": ">=12.13" - } + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/mysql2": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.1.tgz", "integrity": "sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w==", - "license": "MIT", "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", @@ -5713,7 +5638,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", - "license": "MIT", "dependencies": { "lru-cache": "^7.14.1" }, @@ -5721,6 +5645,14 @@ "node": ">=12.0.0" } }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -5745,7 +5677,6 @@ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.4.tgz", "integrity": "sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==", "dev": true, - "license": "MIT", "bin": { "napi-postinstall": "lib/cli.js" }, @@ -5766,14 +5697,12 @@ "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "license": "MIT" + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" }, "node_modules/object-inspect": { "version": "1.13.4", @@ -6004,13 +5933,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -6129,7 +6051,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", - "license": "Apache-2.0", "dependencies": { "protobufjs": "^7.2.5" }, @@ -6142,7 +6063,6 @@ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", "hasInstallScript": true, - "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -6196,7 +6116,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } @@ -6258,7 +6177,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6299,7 +6217,6 @@ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, - "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } @@ -6383,7 +6300,6 @@ "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } @@ -6425,8 +6341,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/safe-push-apply": { "version": "1.0.0", @@ -6466,14 +6381,12 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/schema-utils": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -6492,7 +6405,6 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6508,7 +6420,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -6519,17 +6430,19 @@ "node_modules/schema-utils/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/seq-queue": { @@ -6541,7 +6454,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } @@ -6718,7 +6630,6 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "license": "BSD-3-Clause", "engines": { "node": ">= 8" } @@ -6736,7 +6647,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.2.tgz", "integrity": "sha512-oYwAqCuL0OZhBoSgmdrLa7mv9MjommVMiQIWgcztf+eS4+8BfcUee6nenFnDhKOhzAVnk5gpZdfnz1iiBv+5sg==", - "license": "MIT", "dependencies": { "iconv-lite": "^0.6.3", "source-map-js": "^1.0.2" @@ -6756,7 +6666,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -6766,7 +6675,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -6775,7 +6683,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -6784,8 +6691,7 @@ "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/stackback": { "version": "0.0.2", @@ -6988,18 +6894,16 @@ } }, "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "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": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=8" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -7019,7 +6923,6 @@ "version": "0.2.6", "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.6.tgz", "integrity": "sha512-9Zi9UP2YmDpgmQVbyOPJClY0dwf58JDyDMQ7uRc4krmc72twNI2fvlBWHLqVekBpPc7h5NJkGVT1zNDxFrqhvg==", - "license": "MIT", "dependencies": { "@swc/counter": "^0.1.3" }, @@ -7048,16 +6951,14 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/terser": { - "version": "5.39.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.2.tgz", - "integrity": "sha512-yEPUmWve+VA78bI71BW70Dh0TuV4HHd+I5SHOAfS1+QBOmvmCiiffgjR8ryyEd3KIfvPGFqoADt8LdQ6XpXIvg==", - "license": "BSD-2-Clause", + "version": "5.40.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.40.0.tgz", + "integrity": "sha512-cfeKl/jjwSR5ar7d0FGmave9hFGJT8obyo0z+CrQOylLDbk7X81nPU6vq9VORa5jU30SkDnT2FXjLbR8HLP+xA==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.14.0", @@ -7075,7 +6976,6 @@ "version": "5.3.14", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", - "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -7120,37 +7020,10 @@ "node": ">=18" } }, - "node_modules/test-exclude/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/test-exclude/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/thingies": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", - "license": "Unlicense", "engines": { "node": ">=10.18" }, @@ -7236,7 +7109,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz", "integrity": "sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==", - "license": "Apache-2.0", "engines": { "node": ">=10.0" }, @@ -7266,7 +7138,6 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, - "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -7305,13 +7176,6 @@ } } }, - "node_modules/ts-node/node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -7328,8 +7192,7 @@ "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "node_modules/type-check": { "version": "0.4.0", @@ -7481,8 +7344,7 @@ "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" }, "node_modules/unionfs": { "version": "4.5.4", @@ -7498,7 +7360,6 @@ "integrity": "sha512-BBKpaylOW8KbHsu378Zky/dGh4ckT/4NW/0SHRABdqRLcQJ2dAOjDo9g97p04sWflm0kqPqpUatxReNV/dqI5A==", "dev": true, "hasInstallScript": true, - "license": "MIT", "dependencies": { "napi-postinstall": "^0.2.2" }, @@ -7543,7 +7404,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -7566,16 +7426,16 @@ } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "dev": true, "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], - "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/v8-compile-cache-lib": { @@ -7703,7 +7563,6 @@ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.3.tgz", "integrity": "sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw==", "dev": true, - "license": "MIT", "dependencies": { "@vitest/expect": "3.1.3", "@vitest/mocker": "3.1.3", @@ -7773,7 +7632,6 @@ "version": "2.4.4", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", - "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -7786,7 +7644,6 @@ "version": "5.99.9", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", - "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -7830,10 +7687,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "license": "MIT", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.0.tgz", + "integrity": "sha512-77R0RDmJfj9dyv5p3bM5pOHa+X8/ZkO9c7kpDstigkC4nIDobadsfSGCwB4bKhMVxqAok8tajaoR8rirM7+VFQ==", "engines": { "node": ">=10.13.0" } @@ -7842,7 +7698,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -7855,24 +7710,24 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, "node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^2.0.0" }, "bin": { - "node-which": "bin/which.js" + "node-which": "bin/node-which" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": ">= 8" } }, "node_modules/which-boxed-primitive": { @@ -8038,6 +7893,22 @@ "node": ">=8" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -8073,19 +7944,6 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -8190,7 +8048,6 @@ "version": "3.25.17", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.17.tgz", "integrity": "sha512-8hQzQ/kMOIFbwOgPrm9Sf9rtFHpFUMy4HvN0yEB0spw14aYi0uT5xG5CE2DB9cd51GWNsz+DNO7se1kztHMKnw==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/workers/main/package.json b/workers/main/package.json index 0a17e86..3da92b8 100644 --- a/workers/main/package.json +++ b/workers/main/package.json @@ -10,8 +10,10 @@ "devDependencies": { "@eslint/js": "9.27.0", "@types/node": "22.15.21", + "@temporalio/testing": "1.11.8", "@vitest/coverage-v8": "3.1.3", "c8": "10.1.3", + "dotenv": "16.5.0", "eslint": "9.27.0", "eslint-config-prettier": "10.1.5", "eslint-import-resolver-typescript": "4.3.5", @@ -19,18 +21,19 @@ "eslint-plugin-prettier": "5.4.0", "eslint-plugin-simple-import-sort": "12.1.1", "prettier": "3.5.3", + "source-map-support": "^0.5.21", "ts-node": "10.9.1", "typescript": "5.8.3", "typescript-eslint": "8.32.1", + "uuid": "11.1.0", "vite": "6.3.5", - "vitest": "3.1.3", - "source-map-support": "^0.5.21" + "vitest": "3.1.3" }, "dependencies": { + "@temporalio/activity": "1.11.8", "@temporalio/client": "1.11.8", "@temporalio/worker": "1.11.8", "@temporalio/workflow": "1.11.8", - "@temporalio/activity": "1.11.8", "mysql2": "3.14.1", "zod": "3.25.17" } diff --git a/workers/main/src/__tests__/utils.test.ts b/workers/main/src/__tests__/utils.test.ts new file mode 100644 index 0000000..2a56244 --- /dev/null +++ b/workers/main/src/__tests__/utils.test.ts @@ -0,0 +1,58 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('../common/../configs', () => ({ + validationResult: { success: true }, +})); + +import * as configs from '../common/../configs'; +import { validateEnv } from '../common/utils'; + +type ValidationResult = { + success: boolean; + error?: { issues: { path: unknown[]; message: string }[] }; +}; +function setValidationResult(result: ValidationResult) { + (configs as { validationResult: ValidationResult }).validationResult = result; +} + +describe('validateEnv', () => { + let errorSpy: ReturnType; + let exitSpy: ReturnType; + + beforeEach(() => { + errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => { + throw new Error('exit'); + }) as unknown as ReturnType; + }); + + afterEach(() => { + errorSpy.mockRestore(); + exitSpy.mockRestore(); + }); + + it('does nothing if validationResult.success is true', () => { + setValidationResult({ success: true }); + expect(() => validateEnv()).not.toThrow(); + expect(errorSpy).not.toHaveBeenCalled(); + expect(exitSpy).not.toHaveBeenCalled(); + }); + + it('logs error and exits if validationResult.success is false', () => { + setValidationResult({ + success: false, + error: { + issues: [ + { path: ['FOO'], message: 'is required' }, + { path: [], message: 'unknown' }, + ], + }, + }); + expect(() => validateEnv()).toThrow('exit'); + expect(errorSpy).toHaveBeenCalledWith( + 'Missing or invalid environment variable: FOO (is required)\n' + + 'Missing or invalid environment variable: (unknown variable) (unknown)', + ); + expect(exitSpy).toHaveBeenCalledWith(1); + }); +}); diff --git a/workers/main/src/common/utils.ts b/workers/main/src/common/utils.ts new file mode 100644 index 0000000..b9b43a6 --- /dev/null +++ b/workers/main/src/common/utils.ts @@ -0,0 +1,15 @@ +import { validationResult } from '../configs'; + +export function validateEnv() { + if (!validationResult.success) { + const message = validationResult.error.issues + .map( + ({ path, message }) => + `Missing or invalid environment variable: ${path.join('.') || '(unknown variable)'} (${message})`, + ) + .join('\n'); + + console.error(message); + process.exit(1); + } +} diff --git a/workers/main/src/configs/index.ts b/workers/main/src/configs/index.ts index a8aff72..b3179e4 100644 --- a/workers/main/src/configs/index.ts +++ b/workers/main/src/configs/index.ts @@ -1,6 +1,8 @@ +import { redmineDatabaseSchema } from './redmineDatabase'; import { temporalSchema } from './temporal'; import { workerSchema } from './worker'; export const validationResult = temporalSchema .merge(workerSchema) + .merge(redmineDatabaseSchema) .safeParse(process.env); diff --git a/workers/main/src/configs/redmineDatabase.ts b/workers/main/src/configs/redmineDatabase.ts new file mode 100644 index 0000000..4d5854a --- /dev/null +++ b/workers/main/src/configs/redmineDatabase.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; + +export const redmineDatabaseConfig = { + host: process.env.REDMINE_DB_HOST, + user: process.env.REDMINE_DB_USER, + password: process.env.REDMINE_DB_PASSWORD, + database: process.env.REDMINE_DB_NAME, +}; + +export const redmineDatabaseSchema = z.object({ + REDMINE_DB_HOST: z.string(), + REDMINE_DB_USER: z.string(), + REDMINE_DB_PASSWORD: z.string(), + REDMINE_DB_NAME: z.string(), +}); diff --git a/workers/main/src/configs/worker.ts b/workers/main/src/configs/worker.ts index 5ab7fcf..af66aaa 100644 --- a/workers/main/src/configs/worker.ts +++ b/workers/main/src/configs/worker.ts @@ -1,11 +1,8 @@ import { WorkerOptions } from '@temporalio/worker'; -import path from 'path'; import { z } from 'zod'; export const workerConfig: WorkerOptions = { taskQueue: 'main-queue', - workflowsPath: - process.env.WORKFLOWS_PATH || path.join(__dirname, '../workflows'), }; export const workerSchema = z.object({ diff --git a/workers/main/vitest.config.ts b/workers/main/vitest.config.ts index 8e75863..7f365f6 100644 --- a/workers/main/vitest.config.ts +++ b/workers/main/vitest.config.ts @@ -1,5 +1,8 @@ +import dotenv from 'dotenv'; import { defineConfig } from 'vitest/config'; +dotenv.config({ path: '.env.test' }); + export default defineConfig({ test: { globals: true, @@ -11,12 +14,6 @@ export default defineConfig({ all: true, include: ['src/**/*.ts'], exclude: ['src/__tests__/**', 'src/dist/**'], - thresholds: { - statements: 70, - branches: 70, - functions: 70, - lines: 70, - }, }, }, }); From d3968ac4ddbeca8ef9e1ee23f3405a46d37cbbba Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 28 May 2025 14:21:59 +0200 Subject: [PATCH 02/38] Fix error message variable in environment validation utility This commit updates the variable name from `message` to `errorMessage` in the `validateEnv` function within the `utils.ts` file. This change improves clarity and consistency in error logging for missing or invalid environment variables, ensuring that the correct variable is referenced when logging errors. The overall functionality of the environment validation remains unchanged. --- workers/main/src/common/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workers/main/src/common/utils.ts b/workers/main/src/common/utils.ts index b9b43a6..9d945c8 100644 --- a/workers/main/src/common/utils.ts +++ b/workers/main/src/common/utils.ts @@ -2,14 +2,14 @@ import { validationResult } from '../configs'; export function validateEnv() { if (!validationResult.success) { - const message = validationResult.error.issues + const errorMessage = validationResult.error.issues .map( ({ path, message }) => `Missing or invalid environment variable: ${path.join('.') || '(unknown variable)'} (${message})`, ) .join('\n'); - console.error(message); + console.error(errorMessage); process.exit(1); } } From 7e4d067165c860de16d6364200d704ad26d826f9 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 28 May 2025 14:26:23 +0200 Subject: [PATCH 03/38] Remove DEBUG variable from .env.test file to streamline environment configuration --- workers/main/.env.test | 1 - 1 file changed, 1 deletion(-) diff --git a/workers/main/.env.test b/workers/main/.env.test index 34e160e..5d61dd0 100644 --- a/workers/main/.env.test +++ b/workers/main/.env.test @@ -2,4 +2,3 @@ REDMINE_DB_HOST=localhost REDMINE_DB_USER=testuser REDMINE_DB_PASSWORD=testpassword REDMINE_DB_NAME=testdb -DEBUG= \ No newline at end of file From 39e62866098c9b37afae4fa4abea48222c18ff3d Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 28 May 2025 14:46:10 +0200 Subject: [PATCH 04/38] feat(tests): add unit tests for Redmine activities and weekly financial reports - Introduced unit tests for the `getProjectUnits` function in `redmine.test.ts`, ensuring it returns project units correctly and handles errors gracefully. - Enhanced `weeklyFinancialReports.test.ts` with mocked activities to validate the report generation, including project units and financial data. - Created new activity files for `getProjectUnits` and `fetchFinancialData`, establishing a clear structure for financial report generation. - Added type definitions for `ProjectUnit` and `FinancialData` to improve type safety and clarity in the codebase. These changes enhance test coverage and reliability of the Redmine integration and financial reporting functionalities. --- workers/main/src/__tests__/redmine.test.ts | 71 ++++++++++++ .../__tests__/weeklyFinancialReports.test.ts | 103 +++++++++++++----- workers/main/src/activities/index.ts | 1 + .../weeklyFinancialReports/index.ts | 2 + .../weeklyFinancialReports/redmine.ts | 25 +++++ .../weeklyFinancialReports/redmine.types.ts | 18 +++ workers/main/src/common/Redmine.ts | 60 ++++++++++ workers/main/src/common/utils.ts | 19 ++-- workers/main/src/workflows/index.ts | 2 +- .../workflows/weeklyFinancialReports/index.ts | 27 +++-- 10 files changed, 279 insertions(+), 49 deletions(-) create mode 100644 workers/main/src/__tests__/redmine.test.ts create mode 100644 workers/main/src/activities/index.ts create mode 100644 workers/main/src/activities/weeklyFinancialReports/index.ts create mode 100644 workers/main/src/activities/weeklyFinancialReports/redmine.ts create mode 100644 workers/main/src/activities/weeklyFinancialReports/redmine.types.ts create mode 100644 workers/main/src/common/Redmine.ts diff --git a/workers/main/src/__tests__/redmine.test.ts b/workers/main/src/__tests__/redmine.test.ts new file mode 100644 index 0000000..040ae9a --- /dev/null +++ b/workers/main/src/__tests__/redmine.test.ts @@ -0,0 +1,71 @@ +import { + MockActivityEnvironment, + TestWorkflowEnvironment, +} from '@temporalio/testing'; +import { DefaultLogger, LogEntry, Runtime } from '@temporalio/worker'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; + +import { getProjectUnits } from '../activities'; +import { Redmine } from '../common/Redmine'; + +// Mock data +const mockProjectUnits = [ + { + group_id: 1, + group_name: 'Engineering', + project_id: 101, + project_name: 'Project Alpha', + }, + { + group_id: 2, + group_name: 'QA', + project_id: 102, + project_name: 'Project Beta', + }, +]; + +describe('Redmine Activities', () => { + let testEnv: TestWorkflowEnvironment; + let activityContext: MockActivityEnvironment; + + beforeAll(async () => { + Runtime.install({ + logger: new DefaultLogger('WARN', (entry: LogEntry) => + // eslint-disable-next-line no-console + console.log(`[${entry.level}]`, entry.message), + ), + }); + + testEnv = await TestWorkflowEnvironment.createTimeSkipping(); + activityContext = new MockActivityEnvironment(); + }); + + afterAll(async () => { + await testEnv?.teardown(); + }); + + it('getProjectUnits returns project units from Redmine', async () => { + vi.spyOn(Redmine.prototype, 'getProjectUnits').mockResolvedValue( + mockProjectUnits, + ); + + const result = await activityContext.run(getProjectUnits); + + expect(result).toBeDefined(); + }); + + it('getProjectUnits handles errors gracefully', async () => { + const errorMessage = 'Database connection failed'; + const mockError = new Error(errorMessage); + + const mockGetProjectUnits = vi + .spyOn(Redmine.prototype, 'getProjectUnits') + .mockRejectedValue(mockError); + + await expect(activityContext.run(getProjectUnits)).rejects.toThrow( + errorMessage, + ); + + expect(mockGetProjectUnits).toHaveBeenCalledTimes(1); + }); +}); diff --git a/workers/main/src/__tests__/weeklyFinancialReports.test.ts b/workers/main/src/__tests__/weeklyFinancialReports.test.ts index 573accb..83f5065 100644 --- a/workers/main/src/__tests__/weeklyFinancialReports.test.ts +++ b/workers/main/src/__tests__/weeklyFinancialReports.test.ts @@ -1,46 +1,89 @@ -import { describe, expect, it, vi } from 'vitest'; +import { TestWorkflowEnvironment } from '@temporalio/testing'; +import { DefaultLogger, LogEntry, Runtime, Worker } from '@temporalio/worker'; +import { v4 as uuidv4 } from 'uuid'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import * as utils from '../../../common/utils'; +import { FinancialData, ProjectUnit } from '../activities'; import { weeklyFinancialReportsWorkflow } from '../workflows'; -describe('weeklyFinancialReportsWorkflow', () => { - it('should return the report string with default parameters', async () => { - const result = await weeklyFinancialReportsWorkflow(); +// Mock data +const mockProjectUnits: ProjectUnit[] = [ + { + group_id: 1, + group_name: 'Engineering', + project_id: 101, + project_name: 'Project Alpha', + }, + { + group_id: 2, + group_name: 'QA', + project_id: 102, + project_name: 'Project Beta', + }, +]; - expect(typeof result).toBe('string'); - expect(result.length).toBeGreaterThan(0); - }); +const mockFinancialData: FinancialData = { + period: 'current', + contractType: 'T&M', + revenue: 120000, + cogs: 80000, + margin: 40000, + marginality: 33.3, + effectiveRevenue: 110000, + effectiveMargin: 35000, + effectiveMarginality: 31.8, +}; - it('should return the report string for a custom period', async () => { - const result = await weeklyFinancialReportsWorkflow({ - period: 'Q1 2025', +describe('weeklyFinancialReportsWorkflow', () => { + let testEnv: TestWorkflowEnvironment; + + beforeAll(async () => { + Runtime.install({ + logger: new DefaultLogger('WARN', (entry: LogEntry) => + // eslint-disable-next-line no-console + console.log(`[${entry.level}]`, entry.message), + ), }); - expect(result.startsWith('Weekly Financial Report')).toBe(true); - expect(result).toContain('Period: Q1 2025'); + testEnv = await TestWorkflowEnvironment.createTimeSkipping(); }); - it('should log and rethrow errors', async () => { - const logSpy = vi - .spyOn(utils, 'logWorkflowError') - .mockImplementation(() => {}); - const originalToLocaleString = Number.prototype.toLocaleString.bind( - Number.prototype, - ); + afterAll(async () => { + await testEnv?.teardown(); + }); - Number.prototype.toLocaleString = () => { - throw new Error('Test error'); + it('generates a report with mocked activities', async () => { + const { client, nativeConnection } = testEnv; + const mockActivities = { + getProjectUnits: async () => mockProjectUnits, + fetchFinancialData: async () => mockFinancialData, }; - await expect(weeklyFinancialReportsWorkflow()).rejects.toThrow( - 'Test error', - ); - expect(logSpy).toHaveBeenCalledWith( - 'Weekly Financial Reports', - expect.any(Error), + const taskQueue = `test-${uuidv4()}`; + + const worker = await Worker.create({ + connection: nativeConnection, + taskQueue, + workflowsPath: require.resolve('../workflows/index.ts'), + activities: mockActivities, + }); + + const result = await worker.runUntil( + client.workflow.execute(weeklyFinancialReportsWorkflow, { + workflowId: uuidv4(), + taskQueue, + }), ); - Number.prototype.toLocaleString = originalToLocaleString; - logSpy.mockRestore(); + expect(result).toContain('Weekly Financial Report'); + expect(result).toContain('Engineering'); + expect(result).toContain('Project Alpha'); + expect(result).toContain('Revenue: $120,000'); + expect(result).toContain('COGS: $80,000'); + expect(result).toContain('Margin: $40,000'); + expect(result).toContain('Marginality: 33.3%'); + expect(result).toContain('Effective Revenue (last 4 months): $110,000'); + expect(result).toContain('Effective Margin: $35,000'); + expect(result).toContain('Effective Marginality: 31.8%'); }); }); diff --git a/workers/main/src/activities/index.ts b/workers/main/src/activities/index.ts new file mode 100644 index 0000000..1aab94a --- /dev/null +++ b/workers/main/src/activities/index.ts @@ -0,0 +1 @@ +export * from './weeklyFinancialReports'; diff --git a/workers/main/src/activities/weeklyFinancialReports/index.ts b/workers/main/src/activities/weeklyFinancialReports/index.ts new file mode 100644 index 0000000..8c1f12b --- /dev/null +++ b/workers/main/src/activities/weeklyFinancialReports/index.ts @@ -0,0 +1,2 @@ +export * from './redmine'; +export * from './redmine.types'; diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.ts new file mode 100644 index 0000000..fcf5a9a --- /dev/null +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.ts @@ -0,0 +1,25 @@ +import { Redmine } from '../../common/Redmine'; +import { redmineDatabaseConfig } from '../../configs/redmineDatabase'; +import type { FinancialData, ProjectUnit } from './redmine.types'; + +export const getProjectUnits = async (): Promise => { + const redmine = new Redmine(redmineDatabaseConfig); + + return redmine.getProjectUnits(); +}; + +export async function fetchFinancialData( + period: string = 'current', +): Promise { + return { + period: period, + contractType: 'T&M', + revenue: 120000, + cogs: 80000, + margin: 40000, + marginality: 33.3, + effectiveRevenue: 110000, + effectiveMargin: 35000, + effectiveMarginality: 31.8, + }; +} diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.types.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.types.ts new file mode 100644 index 0000000..7dcd3f9 --- /dev/null +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.types.ts @@ -0,0 +1,18 @@ +export interface ProjectUnit { + group_id: number; + group_name: string; + project_id: number; + project_name: string; +} + +export interface FinancialData { + period: string; + contractType: string; + revenue: number; + cogs: number; + margin: number; + marginality: number; + effectiveRevenue: number; + effectiveMargin: number; + effectiveMarginality: number; +} diff --git a/workers/main/src/common/Redmine.ts b/workers/main/src/common/Redmine.ts new file mode 100644 index 0000000..72644e2 --- /dev/null +++ b/workers/main/src/common/Redmine.ts @@ -0,0 +1,60 @@ +import * as mysql from 'mysql2/promise'; +import { Pool, PoolOptions, RowDataPacket } from 'mysql2/promise'; + +export interface ProjectUnit { + group_id: number; + group_name: string; + project_id: number; + project_name: string; +} + +export class Redmine { + private pool: Pool; + private credentials: PoolOptions; + + constructor(credentials: PoolOptions) { + this.credentials = credentials; + this.pool = mysql.createPool(this.credentials); + } + + private ensureConnection() { + if (!this?.pool) this.pool = mysql.createPool(this.credentials); + } + + async getProjectUnits(options?: { + unitName?: string; + unitId?: number; + }): Promise { + this.ensureConnection(); + + let whereClause = "g.type = 'Group'"; + const params: (string | number)[] = []; + + if (options?.unitId) { + whereClause += ' AND g.id = ?'; + params.push(options.unitId); + } else if (options?.unitName) { + whereClause += ' AND g.lastname = ?'; + params.push(options.unitName); + } + + const query = `SELECT + g.id AS group_id, + g.lastname AS group_name, + p.id AS project_id, + p.name AS project_name + FROM users AS g + JOIN members AS m ON m.user_id = g.id + JOIN projects AS p ON p.id = m.project_id + WHERE ${whereClause}`; + + const [rows] = await this.pool.execute(query, params); + + return rows as ProjectUnit[]; + } + + /** Expose the Pool Connection */ + get connection() { + return this.pool; + } +} diff --git a/workers/main/src/common/utils.ts b/workers/main/src/common/utils.ts index 9d945c8..ebe6155 100644 --- a/workers/main/src/common/utils.ts +++ b/workers/main/src/common/utils.ts @@ -1,15 +1,18 @@ import { validationResult } from '../configs'; +export const formatValidationIssues = ( + issues: { path: (string | number)[]; message: string }[], +): string => + issues + .map( + ({ path, message }) => + `Missing or invalid environment variable: ${path.join('.') || '(unknown variable)'} (${message})`, + ) + .join('\n'); + export function validateEnv() { if (!validationResult.success) { - const errorMessage = validationResult.error.issues - .map( - ({ path, message }) => - `Missing or invalid environment variable: ${path.join('.') || '(unknown variable)'} (${message})`, - ) - .join('\n'); - - console.error(errorMessage); + console.error(formatValidationIssues(validationResult.error.issues)); process.exit(1); } } diff --git a/workers/main/src/workflows/index.ts b/workers/main/src/workflows/index.ts index 1aab94a..2309cad 100644 --- a/workers/main/src/workflows/index.ts +++ b/workers/main/src/workflows/index.ts @@ -1 +1 @@ -export * from './weeklyFinancialReports'; +export { weeklyFinancialReportsWorkflow } from './weeklyFinancialReports'; diff --git a/workers/main/src/workflows/weeklyFinancialReports/index.ts b/workers/main/src/workflows/weeklyFinancialReports/index.ts index 2951d6d..efc556c 100644 --- a/workers/main/src/workflows/weeklyFinancialReports/index.ts +++ b/workers/main/src/workflows/weeklyFinancialReports/index.ts @@ -1,13 +1,20 @@ -import { logWorkflowError } from '../../../../common/utils'; -import { fetchFinancialData } from '../../activities/fetchFinancialData'; +import { proxyActivities } from '@temporalio/workflow'; -export async function weeklyFinancialReportsWorkflow({ - period = 'current', -}: { period?: string } = {}): Promise { +import type * as activities from '../../activities/weeklyFinancialReports'; + +const { getProjectUnits, fetchFinancialData } = proxyActivities< + typeof activities +>({ + startToCloseTimeout: '10 minutes', +}); + +export const weeklyFinancialReportsWorkflow = async (): Promise => { try { const reportTitle = 'Weekly Financial Report'; - const data = await fetchFinancialData(period); - const report = `Period: ${data.period} + const projectUnits = await getProjectUnits(); + + const data = await fetchFinancialData(); + const report = `Period: ${reportTitle} Contract Type: ${data.contractType} Revenue: $${data.revenue.toLocaleString()} COGS: $${data.cogs.toLocaleString()} @@ -16,9 +23,9 @@ Marginality: ${data.marginality}%\n\nEffective Revenue (last 4 months): $${data. Effective Margin: $${data.effectiveMargin.toLocaleString()} Effective Marginality: ${data.effectiveMarginality}%`; - return `${reportTitle}\n${report}`; + return `${report}\n${JSON.stringify(projectUnits, null, 2)}`; } catch (error) { - logWorkflowError('Weekly Financial Reports', error); + console.error('Weekly Financial Reports', error); throw error; } -} +}; From 7af896a58a03ce1e29bfe919d6fcdd2c6bb3ca4e Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 28 May 2025 15:09:30 +0200 Subject: [PATCH 05/38] feat(tests): add unit tests for fetchFinancialData function - Introduced unit tests for the `fetchFinancialData` function in `redmine.test.ts`, validating its behavior for both default and custom periods. - Ensured that the function returns the expected mock data, enhancing test coverage for financial reporting functionalities. These changes improve the reliability of the Redmine integration tests. --- workers/main/src/__tests__/redmine.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/workers/main/src/__tests__/redmine.test.ts b/workers/main/src/__tests__/redmine.test.ts index 040ae9a..660be4c 100644 --- a/workers/main/src/__tests__/redmine.test.ts +++ b/workers/main/src/__tests__/redmine.test.ts @@ -7,6 +7,7 @@ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { getProjectUnits } from '../activities'; import { Redmine } from '../common/Redmine'; +import { fetchFinancialData } from '../activities/weeklyFinancialReports/redmine'; // Mock data const mockProjectUnits = [ @@ -68,4 +69,18 @@ describe('Redmine Activities', () => { expect(mockGetProjectUnits).toHaveBeenCalledTimes(1); }); + + it('fetchFinancialData returns expected mock data for default period', async () => { + const data = await fetchFinancialData(); + expect(data).toBeDefined(); + expect(data.period).toBe('current'); + expect(data.contractType).toBe('T&M'); + expect(data.revenue).toBe(120000); + }); + + it('fetchFinancialData returns expected mock data for custom period', async () => { + const data = await fetchFinancialData('previous'); + expect(data).toBeDefined(); + expect(data.period).toBe('previous'); + }); }); From 449f65c6181fda68394819371b391e568600a34a Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 28 May 2025 15:10:36 +0200 Subject: [PATCH 06/38] fix(tests): correct import order and add spacing in redmine.test.ts - Adjusted the import order in `redmine.test.ts` to ensure consistency and clarity. - Added spacing in test cases for improved readability. These changes enhance the organization and maintainability of the test file. --- workers/main/src/__tests__/redmine.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/workers/main/src/__tests__/redmine.test.ts b/workers/main/src/__tests__/redmine.test.ts index 660be4c..694bec3 100644 --- a/workers/main/src/__tests__/redmine.test.ts +++ b/workers/main/src/__tests__/redmine.test.ts @@ -6,8 +6,8 @@ import { DefaultLogger, LogEntry, Runtime } from '@temporalio/worker'; import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { getProjectUnits } from '../activities'; -import { Redmine } from '../common/Redmine'; import { fetchFinancialData } from '../activities/weeklyFinancialReports/redmine'; +import { Redmine } from '../common/Redmine'; // Mock data const mockProjectUnits = [ @@ -72,6 +72,7 @@ describe('Redmine Activities', () => { it('fetchFinancialData returns expected mock data for default period', async () => { const data = await fetchFinancialData(); + expect(data).toBeDefined(); expect(data.period).toBe('current'); expect(data.contractType).toBe('T&M'); @@ -80,6 +81,7 @@ describe('Redmine Activities', () => { it('fetchFinancialData returns expected mock data for custom period', async () => { const data = await fetchFinancialData('previous'); + expect(data).toBeDefined(); expect(data.period).toBe('previous'); }); From 4490220fdd787189b5aacacd8df2bbc7466f0767 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 28 May 2025 15:37:09 +0200 Subject: [PATCH 07/38] refactor(tests): remove unused utils.ts and streamline error handling - Deleted the `utils.ts` file as its functions are no longer utilized in the codebase. - Updated the `index.ts` file to directly implement error logging within the `handleRunError` function, enhancing clarity and reducing dependencies. - Adjusted the Vitest configuration to include all test files in the `src` directory for improved test coverage. These changes simplify the code structure and improve error handling consistency across the application. --- workers/common/utils.ts | 36 ------------------- .../weeklyFinancialReports/redmine.test.ts | 21 +++++++++++ .../Redmine.test.ts} | 19 +--------- .../src/{__tests__ => common}/utils.test.ts | 4 +-- .../main/src/{__tests__ => }/index.test.ts | 13 +++---- workers/main/src/index.ts | 10 +++--- .../weeklyFinancialReports.test.ts | 2 +- workers/main/vitest.config.ts | 6 ++-- 8 files changed, 41 insertions(+), 70 deletions(-) delete mode 100644 workers/common/utils.ts create mode 100644 workers/main/src/activities/weeklyFinancialReports/redmine.test.ts rename workers/main/src/{__tests__/redmine.test.ts => common/Redmine.test.ts} (73%) rename workers/main/src/{__tests__ => common}/utils.test.ts (94%) rename workers/main/src/{__tests__ => }/index.test.ts (71%) rename workers/main/src/{__tests__ => workflows}/weeklyFinancialReports.test.ts (97%) diff --git a/workers/common/utils.ts b/workers/common/utils.ts deleted file mode 100644 index a62be87..0000000 --- a/workers/common/utils.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { validationResult } from '../main/src/configs'; -import {logger} from "../main"; - -export const formatValidationIssues = (issues: { path: (string | number)[]; message: string }[]): string => - issues - .map(({ path, message }) => `Missing or invalid environment variable: ${path.join('.') || '(unknown variable)'} (${message})`) - .join('\n'); - -export function validateEnv() { - if (!validationResult.success) { - console.error(formatValidationIssues(validationResult.error.issues)); - process.exit(1); - } -} - -/** - * Logs a worker error in a consistent format. - * @param workerName - The name of the workflow - * @param error - The error object - */ -export function logWorkerError(workerName: string, error: unknown) { - logger.error( - `Error in ${workerName} workerName: ${error instanceof Error ? error.message : String(error)}`, - ); -} - -/** - * Logs a workflow error in a consistent format. - * @param workflowName - The name of the workflow - * @param error - The error object - */ -export function logWorkflowError(workflowName: string, error: unknown) { - logger.error( - `Error in ${workflowName} workflow: ${error instanceof Error ? error.message : String(error)}`, - ); -} \ No newline at end of file diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts new file mode 100644 index 0000000..1b3e9eb --- /dev/null +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from 'vitest'; + +import { fetchFinancialData } from './redmine'; + +describe('fetchFinancialData', () => { + it('returns expected mock data for default period', async () => { + const data = await fetchFinancialData(); + + expect(data).toBeDefined(); + expect(data.period).toBe('current'); + expect(data.contractType).toBe('T&M'); + expect(data.revenue).toBe(120000); + }); + + it('returns expected mock data for custom period', async () => { + const data = await fetchFinancialData('previous'); + + expect(data).toBeDefined(); + expect(data.period).toBe('previous'); + }); +}); diff --git a/workers/main/src/__tests__/redmine.test.ts b/workers/main/src/common/Redmine.test.ts similarity index 73% rename from workers/main/src/__tests__/redmine.test.ts rename to workers/main/src/common/Redmine.test.ts index 694bec3..20b7303 100644 --- a/workers/main/src/__tests__/redmine.test.ts +++ b/workers/main/src/common/Redmine.test.ts @@ -6,8 +6,7 @@ import { DefaultLogger, LogEntry, Runtime } from '@temporalio/worker'; import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { getProjectUnits } from '../activities'; -import { fetchFinancialData } from '../activities/weeklyFinancialReports/redmine'; -import { Redmine } from '../common/Redmine'; +import { Redmine } from './Redmine'; // Mock data const mockProjectUnits = [ @@ -69,20 +68,4 @@ describe('Redmine Activities', () => { expect(mockGetProjectUnits).toHaveBeenCalledTimes(1); }); - - it('fetchFinancialData returns expected mock data for default period', async () => { - const data = await fetchFinancialData(); - - expect(data).toBeDefined(); - expect(data.period).toBe('current'); - expect(data.contractType).toBe('T&M'); - expect(data.revenue).toBe(120000); - }); - - it('fetchFinancialData returns expected mock data for custom period', async () => { - const data = await fetchFinancialData('previous'); - - expect(data).toBeDefined(); - expect(data.period).toBe('previous'); - }); }); diff --git a/workers/main/src/__tests__/utils.test.ts b/workers/main/src/common/utils.test.ts similarity index 94% rename from workers/main/src/__tests__/utils.test.ts rename to workers/main/src/common/utils.test.ts index 2a56244..d628838 100644 --- a/workers/main/src/__tests__/utils.test.ts +++ b/workers/main/src/common/utils.test.ts @@ -4,8 +4,8 @@ vi.mock('../common/../configs', () => ({ validationResult: { success: true }, })); -import * as configs from '../common/../configs'; -import { validateEnv } from '../common/utils'; +import * as configs from '../configs'; +import { validateEnv } from './utils'; type ValidationResult = { success: boolean; diff --git a/workers/main/src/__tests__/index.test.ts b/workers/main/src/index.test.ts similarity index 71% rename from workers/main/src/__tests__/index.test.ts rename to workers/main/src/index.test.ts index b03f648..35c7e79 100644 --- a/workers/main/src/__tests__/index.test.ts +++ b/workers/main/src/index.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; import { vi } from 'vitest'; -import * as utils from '../../../common/utils'; -import { handleRunError, run } from '../index'; +import * as utils from './common/utils'; +import { handleRunError, run, logger } from './index'; vi.mock('@temporalio/worker', () => ({ DefaultLogger: class { @@ -26,13 +26,14 @@ describe('run', () => { describe('handleRunError', () => { it('should log the error and throw the error', () => { - const logSpy = vi - .spyOn(utils, 'logWorkerError') - .mockImplementation(() => {}); const error = new Error('test error'); + // Spy on logger.error + const logSpy = vi.spyOn(logger, 'error').mockImplementation(() => {}); expect(() => handleRunError(error)).toThrow(error); - expect(logSpy).toHaveBeenCalledWith('main', error); + expect(logSpy).toHaveBeenCalledWith( + `Error in main worker: ${error.message}` + ); logSpy.mockRestore(); }); }); diff --git a/workers/main/src/index.ts b/workers/main/src/index.ts index 178664b..e62eeca 100644 --- a/workers/main/src/index.ts +++ b/workers/main/src/index.ts @@ -1,6 +1,6 @@ import { DefaultLogger, NativeConnection, Worker } from '@temporalio/worker'; -import { logWorkerError, validateEnv } from '../../common/utils'; +import { validateEnv } from './common/utils'; import { temporalConfig } from './configs/temporal'; import { workerConfig } from './configs/worker'; @@ -37,10 +37,12 @@ export async function run(): Promise { } } -export function handleRunError(err: unknown): never { - logWorkerError('main', err); +export function handleRunError(error: unknown): never { + logger.error( + `Error in main worker: ${error instanceof Error ? error.message : String(error)}`, + ); setTimeout(() => process.exit(1), 100); - throw err; + throw error; } export function mainEntry() { diff --git a/workers/main/src/__tests__/weeklyFinancialReports.test.ts b/workers/main/src/workflows/weeklyFinancialReports.test.ts similarity index 97% rename from workers/main/src/__tests__/weeklyFinancialReports.test.ts rename to workers/main/src/workflows/weeklyFinancialReports.test.ts index 83f5065..6cbfbed 100644 --- a/workers/main/src/__tests__/weeklyFinancialReports.test.ts +++ b/workers/main/src/workflows/weeklyFinancialReports.test.ts @@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { FinancialData, ProjectUnit } from '../activities'; -import { weeklyFinancialReportsWorkflow } from '../workflows'; +import { weeklyFinancialReportsWorkflow } from '.'; // Mock data const mockProjectUnits: ProjectUnit[] = [ diff --git a/workers/main/vitest.config.ts b/workers/main/vitest.config.ts index 7f365f6..b51da2d 100644 --- a/workers/main/vitest.config.ts +++ b/workers/main/vitest.config.ts @@ -7,13 +7,13 @@ export default defineConfig({ test: { globals: true, environment: 'node', - include: ['src/__tests__/**/*.test.ts'], + include: ['src/**/*.test.ts'], coverage: { provider: 'v8', reporter: ['text', 'lcov'], all: true, - include: ['src/**/*.ts'], - exclude: ['src/__tests__/**', 'src/dist/**'], + // include: ['src/**/*.ts'], + exclude: ['src/dist/**'], }, }, }); From 77e61c0ac799752b6da257eb454da6680b42429b Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 28 May 2025 15:42:12 +0200 Subject: [PATCH 08/38] refactor(tests): streamline imports and maintain error logging consistency - Removed the unused `utils.ts` import from `index.test.ts` to simplify the code structure. - Ensured consistent error logging format in the `handleRunError` function. These changes enhance the clarity and maintainability of the test file. --- workers/main/src/index.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/workers/main/src/index.test.ts b/workers/main/src/index.test.ts index 35c7e79..3d5fdd8 100644 --- a/workers/main/src/index.test.ts +++ b/workers/main/src/index.test.ts @@ -1,8 +1,7 @@ import { describe, expect, it } from 'vitest'; import { vi } from 'vitest'; -import * as utils from './common/utils'; -import { handleRunError, run, logger } from './index'; +import { handleRunError, logger, run } from './index'; vi.mock('@temporalio/worker', () => ({ DefaultLogger: class { @@ -32,7 +31,7 @@ describe('handleRunError', () => { expect(() => handleRunError(error)).toThrow(error); expect(logSpy).toHaveBeenCalledWith( - `Error in main worker: ${error.message}` + `Error in main worker: ${error.message}`, ); logSpy.mockRestore(); }); From 1e6a7279358aa142ddf3092575fd79492ff4dbc1 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 28 May 2025 15:51:03 +0200 Subject: [PATCH 09/38] refactor(financial): remove fetchFinancialData function and interface - Deleted the `fetchFinancialData.ts` file, which contained the `FinancialData` interface and the associated function for fetching financial data. This change simplifies the codebase by removing unused code that is no longer needed. This update enhances maintainability by eliminating obsolete components related to financial data fetching. --- .../main/src/activities/fetchFinancialData.ts | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 workers/main/src/activities/fetchFinancialData.ts diff --git a/workers/main/src/activities/fetchFinancialData.ts b/workers/main/src/activities/fetchFinancialData.ts deleted file mode 100644 index ab56ea7..0000000 --- a/workers/main/src/activities/fetchFinancialData.ts +++ /dev/null @@ -1,32 +0,0 @@ -export interface FinancialData { - period: string; - contractType: string; - revenue: number; - cogs: number; - margin: number; - marginality: number; - effectiveRevenue: number; - effectiveMargin: number; - effectiveMarginality: number; -} - -/** - * Fetches financial data for a given period from an external source or database. - * @param period - The period to fetch data for (e.g., 'Q1 2025', 'current') - */ -export async function fetchFinancialData( - period: string = 'current', -): Promise { - // TODO: Replace this stub with actual data fetching logic (e.g., DB query, API call) - return { - period: period, - contractType: 'T&M', - revenue: 120000, - cogs: 80000, - margin: 40000, - marginality: 33.3, - effectiveRevenue: 110000, - effectiveMargin: 35000, - effectiveMarginality: 31.8, - }; -} From 89d918e3dfafec6a954544757087d0548f6fcb43 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 28 May 2025 16:20:41 +0200 Subject: [PATCH 10/38] feat(financial): add weekly financial reports workflow and tests - Introduced a new workflow for generating weekly financial reports, utilizing mocked activities for testing. - Created unit tests for the `weeklyFinancialReportsWorkflow` and `generateReport` functions to ensure correct report generation and formatting. - Updated the Vitest configuration to exclude specific files from coverage, enhancing test accuracy. These changes improve the functionality and test coverage of the financial reporting features. --- .../index.test.ts} | 25 ++++++++++++++++--- .../workflows/weeklyFinancialReports/index.ts | 23 +++++++++++------ workers/main/vitest.config.ts | 2 +- 3 files changed, 37 insertions(+), 13 deletions(-) rename workers/main/src/workflows/{weeklyFinancialReports.test.ts => weeklyFinancialReports/index.test.ts} (72%) diff --git a/workers/main/src/workflows/weeklyFinancialReports.test.ts b/workers/main/src/workflows/weeklyFinancialReports/index.test.ts similarity index 72% rename from workers/main/src/workflows/weeklyFinancialReports.test.ts rename to workers/main/src/workflows/weeklyFinancialReports/index.test.ts index 6cbfbed..327486c 100644 --- a/workers/main/src/workflows/weeklyFinancialReports.test.ts +++ b/workers/main/src/workflows/weeklyFinancialReports/index.test.ts @@ -3,10 +3,10 @@ import { DefaultLogger, LogEntry, Runtime, Worker } from '@temporalio/worker'; import { v4 as uuidv4 } from 'uuid'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { FinancialData, ProjectUnit } from '../activities'; -import { weeklyFinancialReportsWorkflow } from '.'; +import type { FinancialData, ProjectUnit } from '../../activities'; +import { weeklyFinancialReportsWorkflow } from '..'; +import { generateReport } from './index'; -// Mock data const mockProjectUnits: ProjectUnit[] = [ { group_id: 1, @@ -64,7 +64,7 @@ describe('weeklyFinancialReportsWorkflow', () => { const worker = await Worker.create({ connection: nativeConnection, taskQueue, - workflowsPath: require.resolve('../workflows/index.ts'), + workflowsPath: require.resolve('./index.ts'), activities: mockActivities, }); @@ -87,3 +87,20 @@ describe('weeklyFinancialReportsWorkflow', () => { expect(result).toContain('Effective Marginality: 31.8%'); }); }); + +describe('generateReport', () => { + it('formats the report string as expected', () => { + const reportTitle = 'Test Report'; + const report = generateReport(reportTitle, mockFinancialData); + + expect(report).toContain('Period: Test Report'); + expect(report).toContain('Contract Type: T&M'); + expect(report).toContain('Revenue: $120,000'); + expect(report).toContain('COGS: $80,000'); + expect(report).toContain('Margin: $40,000'); + expect(report).toContain('Marginality: 33.3%'); + expect(report).toContain('Effective Revenue (last 4 months): $110,000'); + expect(report).toContain('Effective Margin: $35,000'); + expect(report).toContain('Effective Marginality: 31.8%'); + }); +}); diff --git a/workers/main/src/workflows/weeklyFinancialReports/index.ts b/workers/main/src/workflows/weeklyFinancialReports/index.ts index efc556c..a8f51b8 100644 --- a/workers/main/src/workflows/weeklyFinancialReports/index.ts +++ b/workers/main/src/workflows/weeklyFinancialReports/index.ts @@ -1,6 +1,7 @@ import { proxyActivities } from '@temporalio/workflow'; import type * as activities from '../../activities/weeklyFinancialReports'; +import { FinancialData } from '../../activities/weeklyFinancialReports'; const { getProjectUnits, fetchFinancialData } = proxyActivities< typeof activities @@ -8,13 +9,11 @@ const { getProjectUnits, fetchFinancialData } = proxyActivities< startToCloseTimeout: '10 minutes', }); -export const weeklyFinancialReportsWorkflow = async (): Promise => { - try { - const reportTitle = 'Weekly Financial Report'; - const projectUnits = await getProjectUnits(); - - const data = await fetchFinancialData(); - const report = `Period: ${reportTitle} +export function generateReport( + reportTitle: string, + data: FinancialData, +): string { + return `Period: ${reportTitle} Contract Type: ${data.contractType} Revenue: $${data.revenue.toLocaleString()} COGS: $${data.cogs.toLocaleString()} @@ -22,10 +21,18 @@ Margin: $${data.margin.toLocaleString()} Marginality: ${data.marginality}%\n\nEffective Revenue (last 4 months): $${data.effectiveRevenue.toLocaleString()} Effective Margin: $${data.effectiveMargin.toLocaleString()} Effective Marginality: ${data.effectiveMarginality}%`; +} + +export async function weeklyFinancialReportsWorkflow(): Promise { + try { + const reportTitle = 'Weekly Financial Report'; + const projectUnits = await getProjectUnits(); + const data = await fetchFinancialData(); + const report = generateReport(reportTitle, data); return `${report}\n${JSON.stringify(projectUnits, null, 2)}`; } catch (error) { console.error('Weekly Financial Reports', error); throw error; } -}; +} diff --git a/workers/main/vitest.config.ts b/workers/main/vitest.config.ts index b51da2d..97243e2 100644 --- a/workers/main/vitest.config.ts +++ b/workers/main/vitest.config.ts @@ -13,7 +13,7 @@ export default defineConfig({ reporter: ['text', 'lcov'], all: true, // include: ['src/**/*.ts'], - exclude: ['src/dist/**'], + exclude: ['src/dist/**', 'eslint.config.js', 'vitest.config.ts'], }, }, }); From 6482a378563bc676d261047f1a572380ccb84511 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 29 May 2025 12:19:56 +0200 Subject: [PATCH 11/38] refactor(docker): update docker-compose and Vitest configuration - Added an `env_file` entry in `docker-compose.yml` to streamline environment variable management. - Updated the Vitest configuration to exclude additional test files, enhancing test coverage accuracy. - Refactored `Redmine.test.ts` to include a mock for `mysql2/promise` and reorganized test cases for better clarity and maintainability. These changes improve the development environment setup and enhance the organization of test files. --- docker-compose.yml | 2 + workers/main/src/common/Redmine.test.ts | 135 ++++++++++++++++++------ workers/main/vitest.config.ts | 3 +- 3 files changed, 105 insertions(+), 35 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 95552b7..f423a9e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -159,6 +159,8 @@ services: - ./workers/main:/app/main - ./workers/common:/app/common - /app/main/node_modules + env_file: + - .env networks: - app-network develop: diff --git a/workers/main/src/common/Redmine.test.ts b/workers/main/src/common/Redmine.test.ts index 20b7303..b78dfa1 100644 --- a/workers/main/src/common/Redmine.test.ts +++ b/workers/main/src/common/Redmine.test.ts @@ -3,8 +3,16 @@ import { TestWorkflowEnvironment, } from '@temporalio/testing'; import { DefaultLogger, LogEntry, Runtime } from '@temporalio/worker'; -import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; +import { afterAll, beforeAll, describe, expect, it, vi, afterEach } from 'vitest'; +// Mock mysql2/promise before importing Redmine +vi.mock('mysql2/promise', () => { + return { + createPool: vi.fn(), + }; +}); + +import * as mysql from 'mysql2/promise'; import { getProjectUnits } from '../activities'; import { Redmine } from './Redmine'; @@ -24,48 +32,109 @@ const mockProjectUnits = [ }, ]; -describe('Redmine Activities', () => { - let testEnv: TestWorkflowEnvironment; - let activityContext: MockActivityEnvironment; +// describe('Redmine Activities', () => { +// let testEnv: TestWorkflowEnvironment; +// let activityContext: MockActivityEnvironment; +// +// beforeAll(async () => { +// Runtime.install({ +// logger: new DefaultLogger('WARN', (entry: LogEntry) => +// // eslint-disable-next-line no-console +// console.log(`[${entry.level}]`, entry.message), +// ), +// }); +// +// testEnv = await TestWorkflowEnvironment.createTimeSkipping(); +// activityContext = new MockActivityEnvironment(); +// }); +// +// afterAll(async () => { +// await testEnv?.teardown(); +// }); +// +// afterEach(() => { +// vi.clearAllMocks(); +// }); +// +// it('getProjectUnits returns project units from Redmine', async () => { +// vi.spyOn(Redmine.prototype, 'getProjectUnits').mockResolvedValue( +// mockProjectUnits, +// ); +// +// const result = await activityContext.run(getProjectUnits); +// +// expect(result).toBeDefined(); +// }); +// +// it('getProjectUnits handles errors gracefully', async () => { +// const errorMessage = 'Database connection failed'; +// const mockError = new Error(errorMessage); +// +// const mockGetProjectUnits = vi +// .spyOn(Redmine.prototype, 'getProjectUnits') +// .mockRejectedValue(mockError); +// +// await expect(activityContext.run(getProjectUnits)).rejects.toThrow( +// errorMessage, +// ); +// +// expect(mockGetProjectUnits).toHaveBeenCalledTimes(1); +// }); +// }); - beforeAll(async () => { - Runtime.install({ - logger: new DefaultLogger('WARN', (entry: LogEntry) => - // eslint-disable-next-line no-console - console.log(`[${entry.level}]`, entry.message), - ), - }); +describe('Redmine internal', () => { + it('ensureConnection creates pool if missing', () => { + const credentials = { host: 'localhost', user: 'root', password: '', database: 'test' }; + const createPoolMock = mysql.createPool as unknown as ReturnType; + createPoolMock.mockClear(); + createPoolMock.mockReturnValue({} as any); - testEnv = await TestWorkflowEnvironment.createTimeSkipping(); - activityContext = new MockActivityEnvironment(); + const redmine = Object.create(Redmine.prototype) as Redmine; + (redmine as any).pool = undefined; + (redmine as any).credentials = credentials; + (redmine as any).ensureConnection(); + expect(createPoolMock).toHaveBeenCalledWith(credentials); }); - afterAll(async () => { - await testEnv?.teardown(); - }); + it('getProjectUnits returns correct data for different options', async () => { + const credentials = { host: 'localhost', user: 'root', password: '', database: 'test' }; + const fakeRows = [ + { group_id: 1, group_name: 'A', project_id: 2, project_name: 'B' }, + ]; + const fakePool = { execute: vi.fn().mockResolvedValue([fakeRows]) }; + const createPoolMock = mysql.createPool as unknown as ReturnType; + createPoolMock.mockClear(); + createPoolMock.mockReturnValue(fakePool); + const redmine = new Redmine(credentials); - it('getProjectUnits returns project units from Redmine', async () => { - vi.spyOn(Redmine.prototype, 'getProjectUnits').mockResolvedValue( - mockProjectUnits, - ); + // No options + let result = await redmine.getProjectUnits(); + expect(fakePool.execute).toHaveBeenCalled(); + expect(result).toEqual(fakeRows); - const result = await activityContext.run(getProjectUnits); + // With unitId + await redmine.getProjectUnits({ unitId: 123 }); + expect(fakePool.execute).toHaveBeenCalledWith( + expect.stringContaining('g.id = ?'), + expect.arrayContaining([123]) + ); - expect(result).toBeDefined(); + // With unitName + await redmine.getProjectUnits({ unitName: 'Test' }); + expect(fakePool.execute).toHaveBeenCalledWith( + expect.stringContaining('g.lastname = ?'), + expect.arrayContaining(['Test']) + ); }); it('getProjectUnits handles errors gracefully', async () => { - const errorMessage = 'Database connection failed'; - const mockError = new Error(errorMessage); - - const mockGetProjectUnits = vi - .spyOn(Redmine.prototype, 'getProjectUnits') - .mockRejectedValue(mockError); - - await expect(activityContext.run(getProjectUnits)).rejects.toThrow( - errorMessage, - ); + const credentials = { host: 'localhost', user: 'root', password: '', database: 'test' }; + const fakePool = { execute: vi.fn().mockRejectedValue(new Error('Database connection failed')) }; + const createPoolMock = mysql.createPool as unknown as ReturnType; + createPoolMock.mockClear(); + createPoolMock.mockReturnValue(fakePool); + const redmine = new Redmine(credentials); - expect(mockGetProjectUnits).toHaveBeenCalledTimes(1); + await expect(redmine.getProjectUnits()).rejects.toThrow('Database connection failed'); }); }); diff --git a/workers/main/vitest.config.ts b/workers/main/vitest.config.ts index 97243e2..380e7b7 100644 --- a/workers/main/vitest.config.ts +++ b/workers/main/vitest.config.ts @@ -12,8 +12,7 @@ export default defineConfig({ provider: 'v8', reporter: ['text', 'lcov'], all: true, - // include: ['src/**/*.ts'], - exclude: ['src/dist/**', 'eslint.config.js', 'vitest.config.ts'], + exclude: ['src/dist/**', 'src/**/*.test.ts', 'eslint.config.mjs', 'vitest.config.ts'], }, }, }); From 931ac1e9ac5c5fd8fc71ec697285f3d61cbeb318 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 29 May 2025 13:07:03 +0200 Subject: [PATCH 12/38] fix(sonar): update exclusion patterns in sonar-project.properties - Changed the exclusion pattern from `**/src/__tests__/**` to `**/*.test.ts` to better target test files for exclusion in Sonar analysis. - This update enhances the accuracy of code quality metrics by ensuring that only relevant files are included in the analysis. --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index dbeda9a..ec62924 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,4 +1,4 @@ sonar.projectKey=speedandfunction_automatization sonar.organization=speedandfunction sonar.javascript.lcov.reportPaths=workers/main/coverage/lcov.info -sonar.exclusions=**/src/__tests__/**,**/src/dist/** +sonar.exclusions=**/*.test.ts,**/src/dist/** From de878631f222d87aa2aff4cc86ff3a7f4a34fe06 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 29 May 2025 13:14:39 +0200 Subject: [PATCH 13/38] refactor(tests): improve test file organization and configuration - Updated the Vitest configuration to format the exclusion patterns for better readability. - Refactored `Redmine.test.ts` by removing unused mocks and reorganizing test cases for clarity. - Enhanced the structure of the test suite for `Redmine` activities, ensuring better maintainability and readability. These changes streamline the testing setup and improve the overall organization of test files. --- workers/main/src/common/Redmine.test.ts | 135 ++++++------------------ workers/main/vitest.config.ts | 7 +- 2 files changed, 39 insertions(+), 103 deletions(-) diff --git a/workers/main/src/common/Redmine.test.ts b/workers/main/src/common/Redmine.test.ts index b78dfa1..20b7303 100644 --- a/workers/main/src/common/Redmine.test.ts +++ b/workers/main/src/common/Redmine.test.ts @@ -3,16 +3,8 @@ import { TestWorkflowEnvironment, } from '@temporalio/testing'; import { DefaultLogger, LogEntry, Runtime } from '@temporalio/worker'; -import { afterAll, beforeAll, describe, expect, it, vi, afterEach } from 'vitest'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; -// Mock mysql2/promise before importing Redmine -vi.mock('mysql2/promise', () => { - return { - createPool: vi.fn(), - }; -}); - -import * as mysql from 'mysql2/promise'; import { getProjectUnits } from '../activities'; import { Redmine } from './Redmine'; @@ -32,109 +24,48 @@ const mockProjectUnits = [ }, ]; -// describe('Redmine Activities', () => { -// let testEnv: TestWorkflowEnvironment; -// let activityContext: MockActivityEnvironment; -// -// beforeAll(async () => { -// Runtime.install({ -// logger: new DefaultLogger('WARN', (entry: LogEntry) => -// // eslint-disable-next-line no-console -// console.log(`[${entry.level}]`, entry.message), -// ), -// }); -// -// testEnv = await TestWorkflowEnvironment.createTimeSkipping(); -// activityContext = new MockActivityEnvironment(); -// }); -// -// afterAll(async () => { -// await testEnv?.teardown(); -// }); -// -// afterEach(() => { -// vi.clearAllMocks(); -// }); -// -// it('getProjectUnits returns project units from Redmine', async () => { -// vi.spyOn(Redmine.prototype, 'getProjectUnits').mockResolvedValue( -// mockProjectUnits, -// ); -// -// const result = await activityContext.run(getProjectUnits); -// -// expect(result).toBeDefined(); -// }); -// -// it('getProjectUnits handles errors gracefully', async () => { -// const errorMessage = 'Database connection failed'; -// const mockError = new Error(errorMessage); -// -// const mockGetProjectUnits = vi -// .spyOn(Redmine.prototype, 'getProjectUnits') -// .mockRejectedValue(mockError); -// -// await expect(activityContext.run(getProjectUnits)).rejects.toThrow( -// errorMessage, -// ); -// -// expect(mockGetProjectUnits).toHaveBeenCalledTimes(1); -// }); -// }); +describe('Redmine Activities', () => { + let testEnv: TestWorkflowEnvironment; + let activityContext: MockActivityEnvironment; -describe('Redmine internal', () => { - it('ensureConnection creates pool if missing', () => { - const credentials = { host: 'localhost', user: 'root', password: '', database: 'test' }; - const createPoolMock = mysql.createPool as unknown as ReturnType; - createPoolMock.mockClear(); - createPoolMock.mockReturnValue({} as any); + beforeAll(async () => { + Runtime.install({ + logger: new DefaultLogger('WARN', (entry: LogEntry) => + // eslint-disable-next-line no-console + console.log(`[${entry.level}]`, entry.message), + ), + }); - const redmine = Object.create(Redmine.prototype) as Redmine; - (redmine as any).pool = undefined; - (redmine as any).credentials = credentials; - (redmine as any).ensureConnection(); - expect(createPoolMock).toHaveBeenCalledWith(credentials); + testEnv = await TestWorkflowEnvironment.createTimeSkipping(); + activityContext = new MockActivityEnvironment(); }); - it('getProjectUnits returns correct data for different options', async () => { - const credentials = { host: 'localhost', user: 'root', password: '', database: 'test' }; - const fakeRows = [ - { group_id: 1, group_name: 'A', project_id: 2, project_name: 'B' }, - ]; - const fakePool = { execute: vi.fn().mockResolvedValue([fakeRows]) }; - const createPoolMock = mysql.createPool as unknown as ReturnType; - createPoolMock.mockClear(); - createPoolMock.mockReturnValue(fakePool); - const redmine = new Redmine(credentials); - - // No options - let result = await redmine.getProjectUnits(); - expect(fakePool.execute).toHaveBeenCalled(); - expect(result).toEqual(fakeRows); + afterAll(async () => { + await testEnv?.teardown(); + }); - // With unitId - await redmine.getProjectUnits({ unitId: 123 }); - expect(fakePool.execute).toHaveBeenCalledWith( - expect.stringContaining('g.id = ?'), - expect.arrayContaining([123]) + it('getProjectUnits returns project units from Redmine', async () => { + vi.spyOn(Redmine.prototype, 'getProjectUnits').mockResolvedValue( + mockProjectUnits, ); - // With unitName - await redmine.getProjectUnits({ unitName: 'Test' }); - expect(fakePool.execute).toHaveBeenCalledWith( - expect.stringContaining('g.lastname = ?'), - expect.arrayContaining(['Test']) - ); + const result = await activityContext.run(getProjectUnits); + + expect(result).toBeDefined(); }); it('getProjectUnits handles errors gracefully', async () => { - const credentials = { host: 'localhost', user: 'root', password: '', database: 'test' }; - const fakePool = { execute: vi.fn().mockRejectedValue(new Error('Database connection failed')) }; - const createPoolMock = mysql.createPool as unknown as ReturnType; - createPoolMock.mockClear(); - createPoolMock.mockReturnValue(fakePool); - const redmine = new Redmine(credentials); + const errorMessage = 'Database connection failed'; + const mockError = new Error(errorMessage); + + const mockGetProjectUnits = vi + .spyOn(Redmine.prototype, 'getProjectUnits') + .mockRejectedValue(mockError); + + await expect(activityContext.run(getProjectUnits)).rejects.toThrow( + errorMessage, + ); - await expect(redmine.getProjectUnits()).rejects.toThrow('Database connection failed'); + expect(mockGetProjectUnits).toHaveBeenCalledTimes(1); }); }); diff --git a/workers/main/vitest.config.ts b/workers/main/vitest.config.ts index 380e7b7..4fccab1 100644 --- a/workers/main/vitest.config.ts +++ b/workers/main/vitest.config.ts @@ -12,7 +12,12 @@ export default defineConfig({ provider: 'v8', reporter: ['text', 'lcov'], all: true, - exclude: ['src/dist/**', 'src/**/*.test.ts', 'eslint.config.mjs', 'vitest.config.ts'], + exclude: [ + 'src/dist/**', + 'src/**/*.test.ts', + 'eslint.config.mjs', + 'vitest.config.ts', + ], }, }, }); From ed04ed56d95abb79ded8e559c5057d810d58dc86 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 29 May 2025 13:29:41 +0200 Subject: [PATCH 14/38] docs(README): update environment setup instructions and clarify .env file requirements - Revised the instructions for creating the `.env` file to emphasize its necessity for Docker Compose. - Added a note regarding the `.env.example` file, detailing its contents and the importance of editing the `.env` file for specific configurations. - Clarified the process for starting services after setting up the environment file. These changes enhance the documentation by providing clearer guidance on environment setup and ensuring users understand the importance of the `.env` file. --- README.md | 10 +++++++--- .../workflows/weeklyFinancialReports/index.ts | 20 +++++++++++-------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a45e707..1741fae 100644 --- a/README.md +++ b/README.md @@ -44,17 +44,21 @@ This project uses custom Docker images built from the following Dockerfiles: ### Create environment file -Create a `.env` file in the root directory of the project with your environment variables: +Before starting the services, you **must** create a `.env` file in the root directory of the project. This file is required for Docker Compose to provide all necessary environment variables to the containers. + +Copy the example file: ```bash cp .env.example .env ``` -Then edit the `.env` file to set your specific configuration values. +> **Note:** +> - The `.env.example` file contains all required variables for onboarding and local development. Edit the `.env` file to set your specific configuration values as needed. +> - If you do not create a `.env` file, `docker-compose` will fail to start some services due to missing environment variables. ### Starting the services -You can start the services in two ways, depending on your environment: +After creating and editing your `.env` file, you can start the services in two ways, depending on your environment: #### 1. Development diff --git a/workers/main/src/workflows/weeklyFinancialReports/index.ts b/workers/main/src/workflows/weeklyFinancialReports/index.ts index a8f51b8..bbb32fe 100644 --- a/workers/main/src/workflows/weeklyFinancialReports/index.ts +++ b/workers/main/src/workflows/weeklyFinancialReports/index.ts @@ -13,14 +13,18 @@ export function generateReport( reportTitle: string, data: FinancialData, ): string { - return `Period: ${reportTitle} -Contract Type: ${data.contractType} -Revenue: $${data.revenue.toLocaleString()} -COGS: $${data.cogs.toLocaleString()} -Margin: $${data.margin.toLocaleString()} -Marginality: ${data.marginality}%\n\nEffective Revenue (last 4 months): $${data.effectiveRevenue.toLocaleString()} -Effective Margin: $${data.effectiveMargin.toLocaleString()} -Effective Marginality: ${data.effectiveMarginality}%`; + return ( + `Period: ${reportTitle}\n` + + `Contract Type: ${data.contractType}\n` + + `Revenue: $${data.revenue.toLocaleString()}\n` + + `COGS: $${data.cogs.toLocaleString()}\n` + + `Margin: $${data.margin.toLocaleString()}\n` + + `Marginality: ${data.marginality}%\n` + + `\n` + + `Effective Revenue (last 4 months): $${data.effectiveRevenue.toLocaleString()}\n` + + `Effective Margin: $${data.effectiveMargin.toLocaleString()}\n` + + `Effective Marginality: ${data.effectiveMarginality}%\n` + ); } export async function weeklyFinancialReportsWorkflow(): Promise { From 8daa545665e36d7a7ca72c5136a75b5cd8c6a226 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 29 May 2025 13:38:04 +0200 Subject: [PATCH 15/38] fix(sonar): update exclusion patterns in sonar-project.properties - Modified the exclusion patterns in `sonar-project.properties` to include `docker-compose.yml` alongside existing patterns. This change ensures that the specified files are excluded from Sonar analysis, improving the accuracy of code quality metrics. This update enhances the configuration for Sonar analysis by refining the exclusion criteria. --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index ec62924..94582fa 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,4 +1,4 @@ sonar.projectKey=speedandfunction_automatization sonar.organization=speedandfunction sonar.javascript.lcov.reportPaths=workers/main/coverage/lcov.info -sonar.exclusions=**/*.test.ts,**/src/dist/** +sonar.exclusions=docker-compose.yml,**/*.test.ts,**/src/dist/** From 59148c83d8560df343a2d164b534a413916f5d73 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 29 May 2025 13:57:23 +0200 Subject: [PATCH 16/38] refactor(redmine): reorganize ProjectUnit interface and improve Redmine class structure - Moved the `ProjectUnit` interface to a new `types.ts` file for better organization and separation of concerns. - Updated the `Redmine` class to import the `ProjectUnit` interface from the new location, enhancing code clarity. - Refactored the `getProjectUnits` function to streamline its implementation. - Improved error handling in tests for `getProjectUnits`, ensuring robust validation of results. These changes enhance the maintainability and readability of the Redmine-related code, promoting a cleaner architecture. --- .../weeklyFinancialReports/redmine.ts | 7 ++++--- .../weeklyFinancialReports/redmine.types.ts | 7 ------- workers/main/src/common/Redmine.test.ts | 11 +++++++++- workers/main/src/common/Redmine.ts | 21 ++++++++++++------- workers/main/src/common/types.ts | 6 ++++++ .../weeklyFinancialReports/index.test.ts | 4 ++-- 6 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 workers/main/src/common/types.ts diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.ts index fcf5a9a..2e86a26 100644 --- a/workers/main/src/activities/weeklyFinancialReports/redmine.ts +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.ts @@ -1,10 +1,11 @@ import { Redmine } from '../../common/Redmine'; +import type { ProjectUnit } from '../../common/types'; import { redmineDatabaseConfig } from '../../configs/redmineDatabase'; -import type { FinancialData, ProjectUnit } from './redmine.types'; +import type { FinancialData } from './redmine.types'; -export const getProjectUnits = async (): Promise => { - const redmine = new Redmine(redmineDatabaseConfig); +const redmine = new Redmine(redmineDatabaseConfig); +export const getProjectUnits = async (): Promise => { return redmine.getProjectUnits(); }; diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.types.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.types.ts index 7dcd3f9..cef72aa 100644 --- a/workers/main/src/activities/weeklyFinancialReports/redmine.types.ts +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.types.ts @@ -1,10 +1,3 @@ -export interface ProjectUnit { - group_id: number; - group_name: string; - project_id: number; - project_name: string; -} - export interface FinancialData { period: string; contractType: string; diff --git a/workers/main/src/common/Redmine.test.ts b/workers/main/src/common/Redmine.test.ts index 20b7303..4d0d87d 100644 --- a/workers/main/src/common/Redmine.test.ts +++ b/workers/main/src/common/Redmine.test.ts @@ -7,9 +7,10 @@ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { getProjectUnits } from '../activities'; import { Redmine } from './Redmine'; +import { ProjectUnit } from './types'; // Mock data -const mockProjectUnits = [ +const mockProjectUnits: ProjectUnit[] = [ { group_id: 1, group_name: 'Engineering', @@ -52,6 +53,14 @@ describe('Redmine Activities', () => { const result = await activityContext.run(getProjectUnits); expect(result).toBeDefined(); + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(mockProjectUnits.length); + expect(result).toEqual(mockProjectUnits); + // Optionally, check key properties of the first item + expect(result[0]).toHaveProperty('group_id', 1); + expect(result[0]).toHaveProperty('group_name', 'Engineering'); + expect(result[0]).toHaveProperty('project_id', 101); + expect(result[0]).toHaveProperty('project_name', 'Project Alpha'); }); it('getProjectUnits handles errors gracefully', async () => { diff --git a/workers/main/src/common/Redmine.ts b/workers/main/src/common/Redmine.ts index 72644e2..a6025bf 100644 --- a/workers/main/src/common/Redmine.ts +++ b/workers/main/src/common/Redmine.ts @@ -1,16 +1,12 @@ import * as mysql from 'mysql2/promise'; import { Pool, PoolOptions, RowDataPacket } from 'mysql2/promise'; -export interface ProjectUnit { - group_id: number; - group_name: string; - project_id: number; - project_name: string; -} +import { ProjectUnit } from './types'; export class Redmine { private pool: Pool; private credentials: PoolOptions; + private poolEnded = false; constructor(credentials: PoolOptions) { this.credentials = credentials; @@ -18,7 +14,18 @@ export class Redmine { } private ensureConnection() { - if (!this?.pool) this.pool = mysql.createPool(this.credentials); + if (!this.pool || this.poolEnded) { + this.pool = mysql.createPool(this.credentials); + this.poolEnded = false; + } + } + + /** Call this when you want to end the pool */ + async endPool() { + if (this.pool && !this.poolEnded) { + await this.pool.end(); + this.poolEnded = true; + } } async getProjectUnits(options?: { diff --git a/workers/main/src/common/types.ts b/workers/main/src/common/types.ts new file mode 100644 index 0000000..97df49c --- /dev/null +++ b/workers/main/src/common/types.ts @@ -0,0 +1,6 @@ +export interface ProjectUnit { + group_id: number; + group_name: string; + project_id: number; + project_name: string; +} diff --git a/workers/main/src/workflows/weeklyFinancialReports/index.test.ts b/workers/main/src/workflows/weeklyFinancialReports/index.test.ts index 327486c..60d4c42 100644 --- a/workers/main/src/workflows/weeklyFinancialReports/index.test.ts +++ b/workers/main/src/workflows/weeklyFinancialReports/index.test.ts @@ -3,11 +3,11 @@ import { DefaultLogger, LogEntry, Runtime, Worker } from '@temporalio/worker'; import { v4 as uuidv4 } from 'uuid'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import type { FinancialData, ProjectUnit } from '../../activities'; +import type { FinancialData } from '../../activities'; import { weeklyFinancialReportsWorkflow } from '..'; import { generateReport } from './index'; -const mockProjectUnits: ProjectUnit[] = [ +const mockProjectUnits = [ { group_id: 1, group_name: 'Engineering', From a4010f64ba2d686c1f77e38c241746b6548d9376 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 29 May 2025 16:46:20 +0200 Subject: [PATCH 17/38] refactor(redmine): enhance getProjectUnits method and improve test coverage - Refactored the `getProjectUnits` method in the `Redmine` class to separate query construction into a private method `getProjectUnitsQuery`, improving code clarity and maintainability. - Added comprehensive tests for the `getProjectUnitsQuery` method, ensuring correct query generation and parameter handling for various input scenarios. - Enhanced the test suite for the `Redmine` class, verifying pool initialization and connection management, which strengthens the overall reliability of the Redmine integration. These changes contribute to a more organized code structure and improved test coverage for the Redmine functionalities. --- workers/main/src/common/Redmine.test.ts | 77 +++++++++++++++++++++++++ workers/main/src/common/Redmine.ts | 16 +++-- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/workers/main/src/common/Redmine.test.ts b/workers/main/src/common/Redmine.test.ts index 4d0d87d..93f4789 100644 --- a/workers/main/src/common/Redmine.test.ts +++ b/workers/main/src/common/Redmine.test.ts @@ -78,3 +78,80 @@ describe('Redmine Activities', () => { expect(mockGetProjectUnits).toHaveBeenCalledTimes(1); }); }); + +describe('Redmine.getProjectUnitsQuery (private method)', () => { + // Use a dummy credentials object for instantiation + const dummyCredentials = { host: 'localhost', user: 'root', database: 'test', password: 'test' }; + const redmine = new Redmine(dummyCredentials); + + // Helper to access private method + function callGetProjectUnitsQuery(options?: { unitName?: string; unitId?: number }) { + // @ts-expect-error: Accessing private method for test purposes + return redmine.getProjectUnitsQuery(options); + } + + it('returns correct query and params with no options', () => { + const { query, params } = callGetProjectUnitsQuery(); + expect(query).toContain("g.type = 'Group'"); + expect(params).toEqual([]); + }); + + it('returns correct query and params with unitId', () => { + const { query, params } = callGetProjectUnitsQuery({ unitId: 42 }); + expect(query).toContain('g.id = ?'); + expect(params).toEqual([42]); + }); + + it('returns correct query and params with unitName', () => { + const { query, params } = callGetProjectUnitsQuery({ unitName: 'QA' }); + expect(query).toContain('g.lastname = ?'); + expect(params).toEqual(['QA']); + }); +}); + +describe('Redmine class internals', () => { + const credentials = { host: 'localhost', user: 'root', database: 'test', password: 'test' }; + let redmine: Redmine; + + beforeEach(() => { + redmine = new Redmine(credentials); + }); + + it('should initialize pool in constructor', () => { + expect(redmine['pool']).toBeDefined(); + }); + + it('should re-initialize pool if poolEnded is true', () => { + const oldPool = redmine['pool']; + redmine['poolEnded'] = true; + redmine['ensureConnection'](); + expect(redmine['pool']).not.toBe(oldPool); + expect(redmine['poolEnded']).toBe(false); + }); + + it('should re-initialize pool if pool is undefined', () => { + redmine['pool'] = undefined as any; + redmine['poolEnded'] = false; + redmine['ensureConnection'](); + expect(redmine['pool']).toBeDefined(); + expect(redmine['poolEnded']).toBe(false); + }); + + it('should end the pool and set poolEnded to true', async () => { + const endSpy = vi.spyOn(redmine['pool'], 'end').mockResolvedValue(undefined); + await redmine.endPool(); + expect(endSpy).toHaveBeenCalled(); + expect(redmine['poolEnded']).toBe(true); + }); + + it('should not call end if pool is already ended', async () => { + redmine['poolEnded'] = true; + const endSpy = vi.spyOn(redmine['pool'], 'end'); + await redmine.endPool(); + expect(endSpy).not.toHaveBeenCalled(); + }); + + it('should return the pool instance from connection getter', () => { + expect(redmine.connection).toBe(redmine['pool']); + }); +}); diff --git a/workers/main/src/common/Redmine.ts b/workers/main/src/common/Redmine.ts index a6025bf..b2d58d4 100644 --- a/workers/main/src/common/Redmine.ts +++ b/workers/main/src/common/Redmine.ts @@ -28,12 +28,10 @@ export class Redmine { } } - async getProjectUnits(options?: { + private getProjectUnitsQuery(options?: { unitName?: string; unitId?: number; - }): Promise { - this.ensureConnection(); - + }): { query: string; params: (string | number)[] } { let whereClause = "g.type = 'Group'"; const params: (string | number)[] = []; @@ -55,6 +53,16 @@ export class Redmine { JOIN projects AS p ON p.id = m.project_id WHERE ${whereClause}`; + return { query, params }; + } + + async getProjectUnits(options?: { + unitName?: string; + unitId?: number; + }): Promise { + this.ensureConnection(); + + const { query, params } = this.getProjectUnitsQuery(options); const [rows] = await this.pool.execute(query, params); return rows as ProjectUnit[]; From 6661d24320bb3b8eac874792e76456d5c979c6a2 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 29 May 2025 18:57:50 +0200 Subject: [PATCH 18/38] refactor(tests): enhance Redmine test suite structure and readability - Added `beforeEach` to the test suite for better test isolation and consistency. - Reformatted the `dummyCredentials` and `callGetProjectUnitsQuery` function for improved readability. - Removed redundant property checks in tests, streamlining the assertions for clarity. - Enhanced the organization of test cases within the `Redmine` class internals, ensuring better maintainability. These changes contribute to a more structured and readable test suite for the Redmine functionalities, improving overall test quality. --- workers/main/src/common/Redmine.test.ts | 49 +++++++++++++++++++------ 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/workers/main/src/common/Redmine.test.ts b/workers/main/src/common/Redmine.test.ts index 93f4789..d041729 100644 --- a/workers/main/src/common/Redmine.test.ts +++ b/workers/main/src/common/Redmine.test.ts @@ -3,7 +3,15 @@ import { TestWorkflowEnvironment, } from '@temporalio/testing'; import { DefaultLogger, LogEntry, Runtime } from '@temporalio/worker'; -import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; +import { + afterAll, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from 'vitest'; import { getProjectUnits } from '../activities'; import { Redmine } from './Redmine'; @@ -56,11 +64,6 @@ describe('Redmine Activities', () => { expect(Array.isArray(result)).toBe(true); expect(result).toHaveLength(mockProjectUnits.length); expect(result).toEqual(mockProjectUnits); - // Optionally, check key properties of the first item - expect(result[0]).toHaveProperty('group_id', 1); - expect(result[0]).toHaveProperty('group_name', 'Engineering'); - expect(result[0]).toHaveProperty('project_id', 101); - expect(result[0]).toHaveProperty('project_name', 'Project Alpha'); }); it('getProjectUnits handles errors gracefully', async () => { @@ -81,36 +84,52 @@ describe('Redmine Activities', () => { describe('Redmine.getProjectUnitsQuery (private method)', () => { // Use a dummy credentials object for instantiation - const dummyCredentials = { host: 'localhost', user: 'root', database: 'test', password: 'test' }; + const dummyCredentials = { + host: 'localhost', + user: 'root', + database: 'test', + password: 'test', + }; const redmine = new Redmine(dummyCredentials); // Helper to access private method - function callGetProjectUnitsQuery(options?: { unitName?: string; unitId?: number }) { + function callGetProjectUnitsQuery(options?: { + unitName?: string; + unitId?: number; + }) { // @ts-expect-error: Accessing private method for test purposes return redmine.getProjectUnitsQuery(options); } it('returns correct query and params with no options', () => { const { query, params } = callGetProjectUnitsQuery(); + expect(query).toContain("g.type = 'Group'"); expect(params).toEqual([]); }); it('returns correct query and params with unitId', () => { const { query, params } = callGetProjectUnitsQuery({ unitId: 42 }); + expect(query).toContain('g.id = ?'); expect(params).toEqual([42]); }); it('returns correct query and params with unitName', () => { const { query, params } = callGetProjectUnitsQuery({ unitName: 'QA' }); + expect(query).toContain('g.lastname = ?'); expect(params).toEqual(['QA']); }); }); describe('Redmine class internals', () => { - const credentials = { host: 'localhost', user: 'root', database: 'test', password: 'test' }; + const credentials = { + host: 'localhost', + user: 'root', + database: 'test', + password: 'test', + }; let redmine: Redmine; beforeEach(() => { @@ -123,6 +142,7 @@ describe('Redmine class internals', () => { it('should re-initialize pool if poolEnded is true', () => { const oldPool = redmine['pool']; + redmine['poolEnded'] = true; redmine['ensureConnection'](); expect(redmine['pool']).not.toBe(oldPool); @@ -130,7 +150,10 @@ describe('Redmine class internals', () => { }); it('should re-initialize pool if pool is undefined', () => { - redmine['pool'] = undefined as any; + Object.defineProperty(redmine, 'pool', { + value: undefined, + writable: true, + }); redmine['poolEnded'] = false; redmine['ensureConnection'](); expect(redmine['pool']).toBeDefined(); @@ -138,7 +161,10 @@ describe('Redmine class internals', () => { }); it('should end the pool and set poolEnded to true', async () => { - const endSpy = vi.spyOn(redmine['pool'], 'end').mockResolvedValue(undefined); + const endSpy = vi + .spyOn(redmine['pool'], 'end') + .mockResolvedValue(undefined); + await redmine.endPool(); expect(endSpy).toHaveBeenCalled(); expect(redmine['poolEnded']).toBe(true); @@ -147,6 +173,7 @@ describe('Redmine class internals', () => { it('should not call end if pool is already ended', async () => { redmine['poolEnded'] = true; const endSpy = vi.spyOn(redmine['pool'], 'end'); + await redmine.endPool(); expect(endSpy).not.toHaveBeenCalled(); }); From 34ff7528d1f1f1b60a829ecbbcac0ebc8bd1cb28 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 29 May 2025 19:01:00 +0200 Subject: [PATCH 19/38] refactor(tests): update dummy credentials in Redmine test suite - Changed the user property in the dummy credentials from 'root' to 'test' for consistency across test cases. - This update enhances the clarity of the test setup and ensures that the credentials used in tests are more representative of a typical user scenario. These changes contribute to a more coherent and maintainable test suite for the Redmine functionalities. --- workers/main/src/common/Redmine.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/workers/main/src/common/Redmine.test.ts b/workers/main/src/common/Redmine.test.ts index d041729..beef417 100644 --- a/workers/main/src/common/Redmine.test.ts +++ b/workers/main/src/common/Redmine.test.ts @@ -83,10 +83,9 @@ describe('Redmine Activities', () => { }); describe('Redmine.getProjectUnitsQuery (private method)', () => { - // Use a dummy credentials object for instantiation const dummyCredentials = { host: 'localhost', - user: 'root', + user: 'test', database: 'test', password: 'test', }; @@ -126,7 +125,7 @@ describe('Redmine.getProjectUnitsQuery (private method)', () => { describe('Redmine class internals', () => { const credentials = { host: 'localhost', - user: 'root', + user: 'test', database: 'test', password: 'test', }; From 04b11ebb7a4b051e7dddff4a02f6bab16f166666 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 29 May 2025 19:13:55 +0200 Subject: [PATCH 20/38] refactor(tests): enhance Redmine test suite with MySQL pool initialization tests - Introduced a mock for the MySQL connection pool in the Redmine test suite to verify pool initialization in the constructor. - Added assertions to ensure the `createPool` method is called with the correct credentials, improving test coverage for database interactions. - This update strengthens the reliability of the Redmine integration tests by validating the connection pool setup. --- workers/main/src/common/Redmine.test.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/workers/main/src/common/Redmine.test.ts b/workers/main/src/common/Redmine.test.ts index beef417..89f2cc7 100644 --- a/workers/main/src/common/Redmine.test.ts +++ b/workers/main/src/common/Redmine.test.ts @@ -3,6 +3,7 @@ import { TestWorkflowEnvironment, } from '@temporalio/testing'; import { DefaultLogger, LogEntry, Runtime } from '@temporalio/worker'; +import * as mysql from 'mysql2/promise'; import { afterAll, beforeAll, @@ -136,7 +137,21 @@ describe('Redmine class internals', () => { }); it('should initialize pool in constructor', () => { - expect(redmine['pool']).toBeDefined(); + const mockPool = { end: vi.fn() } as unknown as mysql.Pool; + const createPoolMock = vi + .spyOn(mysql, 'createPool') + .mockReturnValue(mockPool); + const testCredentials = { + host: 'localhost', + user: 'test', + database: 'test', + password: 'test', + }; + const testRedmine = new Redmine(testCredentials); + + expect(createPoolMock).toHaveBeenCalledWith(testCredentials); + expect(testRedmine['pool']).toBeDefined(); + createPoolMock.mockRestore(); }); it('should re-initialize pool if poolEnded is true', () => { From 87a42013817d5ce67d89ce69fa9b9a2fa008d73f Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 29 May 2025 19:21:44 +0200 Subject: [PATCH 21/38] refactor(tests): enhance Redmine test suite with MySQL mocking - Introduced a mock for the MySQL connection pool in the Redmine test suite to improve the testing of pool initialization. - Updated the `beforeEach` hook to be asynchronous, ensuring proper setup before each test. - Enhanced assertions to verify that the `createPool` method is called with the correct credentials, further improving test coverage for database interactions. These changes contribute to a more reliable and maintainable test suite for the Redmine functionalities. --- docker-compose.override.yml | 15 +++++++++++++++ workers/main/src/common/Redmine.test.ts | 20 +++++++++++++------- 2 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 docker-compose.override.yml diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 0000000..8466960 --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,15 @@ +services: + redmine-tunnel: + container_name: redmine-tunnel + image: alpine:latest + command: > + sh -c "apk add --no-cache openssh && + ssh -v -o StrictHostKeyChecking=no -i /root/.ssh/id_rsa ubuntu@staging.forecasting-v2.gluzdov.com -N -L 0.0.0.0:3306:redmine-pr-rds-db-read.c1kaki1qbk4o.us-east-1.rds.amazonaws.com:3306" + volumes: + - ~/.ssh:/root/.ssh:ro + ports: + - "3306:3306" + networks: + - app-network + environment: + - SSH_KEY=/root/.ssh/id_rsa \ No newline at end of file diff --git a/workers/main/src/common/Redmine.test.ts b/workers/main/src/common/Redmine.test.ts index 89f2cc7..f7a21ee 100644 --- a/workers/main/src/common/Redmine.test.ts +++ b/workers/main/src/common/Redmine.test.ts @@ -4,6 +4,7 @@ import { } from '@temporalio/testing'; import { DefaultLogger, LogEntry, Runtime } from '@temporalio/worker'; import * as mysql from 'mysql2/promise'; +import type { Mock } from 'vitest'; import { afterAll, beforeAll, @@ -34,6 +35,16 @@ const mockProjectUnits: ProjectUnit[] = [ }, ]; +vi.mock('mysql2/promise', async () => { + const actual = + await vi.importActual('mysql2/promise'); + + return { + ...actual, + createPool: vi.fn(() => ({ end: vi.fn() })), + }; +}); + describe('Redmine Activities', () => { let testEnv: TestWorkflowEnvironment; let activityContext: MockActivityEnvironment; @@ -132,15 +143,11 @@ describe('Redmine class internals', () => { }; let redmine: Redmine; - beforeEach(() => { + beforeEach(async () => { redmine = new Redmine(credentials); }); it('should initialize pool in constructor', () => { - const mockPool = { end: vi.fn() } as unknown as mysql.Pool; - const createPoolMock = vi - .spyOn(mysql, 'createPool') - .mockReturnValue(mockPool); const testCredentials = { host: 'localhost', user: 'test', @@ -149,9 +156,8 @@ describe('Redmine class internals', () => { }; const testRedmine = new Redmine(testCredentials); - expect(createPoolMock).toHaveBeenCalledWith(testCredentials); + expect(mysql.createPool as Mock).toHaveBeenCalledWith(testCredentials); expect(testRedmine['pool']).toBeDefined(); - createPoolMock.mockRestore(); }); it('should re-initialize pool if poolEnded is true', () => { From ebecf18e1893e805a2f596ead03564f98c446cb7 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Thu, 29 May 2025 19:26:09 +0200 Subject: [PATCH 22/38] refactor(tests): enhance Redmine test for pool re-initialization - Added a mock clear call to reset the MySQL connection pool before testing re-initialization. - Included an assertion to verify that the `createPool` method is called with the correct credentials during the pool re-initialization process. - These changes improve the reliability of the Redmine test suite by ensuring proper handling of the connection pool state. This update contributes to a more robust testing framework for the Redmine functionalities. --- workers/main/src/common/Redmine.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/workers/main/src/common/Redmine.test.ts b/workers/main/src/common/Redmine.test.ts index f7a21ee..4cbf0be 100644 --- a/workers/main/src/common/Redmine.test.ts +++ b/workers/main/src/common/Redmine.test.ts @@ -170,12 +170,15 @@ describe('Redmine class internals', () => { }); it('should re-initialize pool if pool is undefined', () => { + // Reset the mock to clear previous calls + (mysql.createPool as Mock).mockClear(); Object.defineProperty(redmine, 'pool', { value: undefined, writable: true, }); redmine['poolEnded'] = false; redmine['ensureConnection'](); + expect(mysql.createPool as Mock).toHaveBeenCalledWith(credentials); expect(redmine['pool']).toBeDefined(); expect(redmine['poolEnded']).toBe(false); }); From 7edaa6c9ba2b5182f79f3571af01b99209b9d1e0 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Fri, 30 May 2025 12:50:42 +0200 Subject: [PATCH 23/38] refactor(redmine): simplify getProjectUnitsQuery method and enhance tests - Refactored the `getProjectUnitsQuery` method in the `Redmine` class to remove options and streamline the SQL query construction, improving clarity and maintainability. - Updated the corresponding test in `Redmine.test.ts` to reflect the changes, ensuring it verifies the correct query string is returned. - Enhanced assertions in the test to check for specific SQL components, improving test coverage and reliability. These changes contribute to a more efficient and understandable implementation of the Redmine functionalities, strengthening the overall test suite. --- workers/main/src/common/Redmine.test.ts | 32 ++++-------- workers/main/src/common/Redmine.ts | 65 ++++++++++++------------- 2 files changed, 41 insertions(+), 56 deletions(-) diff --git a/workers/main/src/common/Redmine.test.ts b/workers/main/src/common/Redmine.test.ts index 4cbf0be..13ecbca 100644 --- a/workers/main/src/common/Redmine.test.ts +++ b/workers/main/src/common/Redmine.test.ts @@ -104,33 +104,19 @@ describe('Redmine.getProjectUnitsQuery (private method)', () => { const redmine = new Redmine(dummyCredentials); // Helper to access private method - function callGetProjectUnitsQuery(options?: { - unitName?: string; - unitId?: number; - }) { + function callGetProjectUnitsQuery() { // @ts-expect-error: Accessing private method for test purposes - return redmine.getProjectUnitsQuery(options); + return redmine.getProjectUnitsQuery(); } - it('returns correct query and params with no options', () => { - const { query, params } = callGetProjectUnitsQuery(); + it('returns correct query string', () => { + const query = callGetProjectUnitsQuery(); - expect(query).toContain("g.type = 'Group'"); - expect(params).toEqual([]); - }); - - it('returns correct query and params with unitId', () => { - const { query, params } = callGetProjectUnitsQuery({ unitId: 42 }); - - expect(query).toContain('g.id = ?'); - expect(params).toEqual([42]); - }); - - it('returns correct query and params with unitName', () => { - const { query, params } = callGetProjectUnitsQuery({ unitName: 'QA' }); - - expect(query).toContain('g.lastname = ?'); - expect(params).toEqual(['QA']); + expect(typeof query).toBe('string'); + expect(query).toContain('SELECT'); + expect(query).toContain('FROM users AS g'); + expect(query).toContain('JOIN members AS m ON m.user_id = g.id'); + expect(query).toContain('JOIN projects AS p ON p.id = m.project_id'); }); }); diff --git a/workers/main/src/common/Redmine.ts b/workers/main/src/common/Redmine.ts index b2d58d4..6dcd5de 100644 --- a/workers/main/src/common/Redmine.ts +++ b/workers/main/src/common/Redmine.ts @@ -28,47 +28,46 @@ export class Redmine { } } - private getProjectUnitsQuery(options?: { - unitName?: string; - unitId?: number; - }): { query: string; params: (string | number)[] } { - let whereClause = "g.type = 'Group'"; - const params: (string | number)[] = []; - - if (options?.unitId) { - whereClause += ' AND g.id = ?'; - params.push(options.unitId); - } else if (options?.unitName) { - whereClause += ' AND g.lastname = ?'; - params.push(options.unitName); - } - - const query = `SELECT - g.id AS group_id, - g.lastname AS group_name, - p.id AS project_id, - p.name AS project_name - FROM users AS g - JOIN members AS m ON m.user_id = g.id - JOIN projects AS p ON p.id = m.project_id - WHERE ${whereClause}`; - - return { query, params }; + private getProjectUnitsQuery() { + return `SELECT + group_id, + group_name, + project_id, + project_name, + user_id, + username, + spent_on, + SUM(total_hours) AS total_hours +FROM ( + SELECT + g.id AS group_id, + g.lastname AS group_name, + p.id AS project_id, + p.name AS project_name, + te.user_id AS user_id, + CONCAT(u.firstname, ' ', u.lastname) AS username, + te.spent_on AS spent_on, + te.hours AS total_hours + FROM users AS g + JOIN members AS m ON m.user_id = g.id + JOIN projects AS p ON p.id = m.project_id + JOIN time_entries te ON te.project_id = p.id + JOIN users AS u ON u.id = te.user_id + WHERE te.spent_on >= CURDATE() - INTERVAL 7 DAY +) t +GROUP BY group_id, group_name, project_id, project_name, user_id, username, spent_on +ORDER BY group_name ASC, project_name ASC, username ASC, spent_on ASC`; } - async getProjectUnits(options?: { - unitName?: string; - unitId?: number; - }): Promise { + async getProjectUnits(): Promise { this.ensureConnection(); - const { query, params } = this.getProjectUnitsQuery(options); - const [rows] = await this.pool.execute(query, params); + const query = this.getProjectUnitsQuery(); + const [rows] = await this.pool.execute(query); return rows as ProjectUnit[]; } - /** Expose the Pool Connection */ get connection() { return this.pool; } From 50a0e131fc678870c9773ee6bb6117c89ba22d8a Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Fri, 30 May 2025 13:07:00 +0200 Subject: [PATCH 24/38] refactor(redmine): restructure Redmine class and introduce RedminePool - Removed the `Redmine` class and replaced it with `RedmineRepository` to better encapsulate database interactions. - Introduced a new `RedminePool` class to manage MySQL connection pooling, enhancing connection management and reliability. - Updated the `getProjectUnits` method to utilize the new repository structure, improving code clarity and maintainability. - Deleted the obsolete `docker-compose.override.yml` file, streamlining the project configuration. These changes contribute to a more organized and efficient implementation of Redmine functionalities, improving overall code structure and maintainability. --- .cursor/rules/mysql-redmine-tables.mdc | 116 ++++++++++++++++++ docker-compose.override.yml | 15 --- .../weeklyFinancialReports/redmine.ts | 11 +- workers/main/src/common/Redmine.test.ts | 62 +++++----- workers/main/src/common/Redmine.ts | 36 +----- workers/main/src/common/RedminePool.ts | 33 +++++ 6 files changed, 193 insertions(+), 80 deletions(-) create mode 100644 .cursor/rules/mysql-redmine-tables.mdc delete mode 100644 docker-compose.override.yml create mode 100644 workers/main/src/common/RedminePool.ts diff --git a/.cursor/rules/mysql-redmine-tables.mdc b/.cursor/rules/mysql-redmine-tables.mdc new file mode 100644 index 0000000..0c85330 --- /dev/null +++ b/.cursor/rules/mysql-redmine-tables.mdc @@ -0,0 +1,116 @@ +--- +description: +globs: +alwaysApply: false +--- +``` +-- redmine_pr_rds.users definition + +CREATE TABLE `users` ( + `id` int NOT NULL AUTO_INCREMENT, + `login` varchar(255) NOT NULL DEFAULT '', + `hashed_password` varchar(40) NOT NULL DEFAULT '', + `firstname` varchar(30) NOT NULL DEFAULT '', + `lastname` varchar(255) NOT NULL DEFAULT '', + `admin` tinyint(1) NOT NULL DEFAULT '0', + `status` int NOT NULL DEFAULT '1', + `last_login_on` datetime DEFAULT NULL, + `language` varchar(5) DEFAULT '', + `auth_source_id` int DEFAULT NULL, + `created_on` datetime DEFAULT NULL, + `updated_on` datetime DEFAULT NULL, + `type` varchar(255) DEFAULT NULL, + `mail_notification` varchar(255) NOT NULL DEFAULT '', + `salt` varchar(64) DEFAULT NULL, + `must_change_passwd` tinyint(1) NOT NULL DEFAULT '0', + `passwd_changed_on` datetime DEFAULT NULL, + `twofa_scheme` varchar(255) DEFAULT NULL, + `twofa_totp_key` varchar(255) DEFAULT NULL, + `twofa_totp_last_used_at` int DEFAULT NULL, + `twofa_required` tinyint(1) DEFAULT '0', + PRIMARY KEY (`id`), + KEY `index_users_on_id_and_type` (`id`,`type`), + KEY `index_users_on_auth_source_id` (`auth_source_id`), + KEY `index_users_on_type` (`type`) +) ENGINE=InnoDB AUTO_INCREMENT=1109 DEFAULT CHARSET=utf8mb3; +``` + +``` +CREATE TABLE `projects` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL DEFAULT '', + `description` text, + `homepage` varchar(255) DEFAULT '', + `is_public` tinyint(1) NOT NULL DEFAULT '1', + `parent_id` int DEFAULT NULL, + `created_on` datetime DEFAULT NULL, + `updated_on` datetime DEFAULT NULL, + `identifier` varchar(255) DEFAULT NULL, + `status` int NOT NULL DEFAULT '1', + `lft` int DEFAULT NULL, + `rgt` int DEFAULT NULL, + `inherit_members` tinyint(1) NOT NULL DEFAULT '0', + `default_version_id` int DEFAULT NULL, + `default_assigned_to_id` int DEFAULT NULL, + `default_issue_query_id` int DEFAULT NULL, + `easy_baseline_for_id` bigint DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `index_projects_on_lft` (`lft`), + KEY `index_projects_on_rgt` (`rgt`), + KEY `index_projects_on_easy_baseline_for_id` (`easy_baseline_for_id`) +) ENGINE=InnoDB AUTO_INCREMENT=1325 DEFAULT CHARSET=utf8mb3; +``` + + +``` +CREATE TABLE `projects` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL DEFAULT '', + `description` text, + `homepage` varchar(255) DEFAULT '', + `is_public` tinyint(1) NOT NULL DEFAULT '1', + `parent_id` int DEFAULT NULL, + `created_on` datetime DEFAULT NULL, + `updated_on` datetime DEFAULT NULL, + `identifier` varchar(255) DEFAULT NULL, + `status` int NOT NULL DEFAULT '1', + `lft` int DEFAULT NULL, + `rgt` int DEFAULT NULL, + `inherit_members` tinyint(1) NOT NULL DEFAULT '0', + `default_version_id` int DEFAULT NULL, + `default_assigned_to_id` int DEFAULT NULL, + `default_issue_query_id` int DEFAULT NULL, + `easy_baseline_for_id` bigint DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `index_projects_on_lft` (`lft`), + KEY `index_projects_on_rgt` (`rgt`), + KEY `index_projects_on_easy_baseline_for_id` (`easy_baseline_for_id`) +) ENGINE=InnoDB AUTO_INCREMENT=1325 DEFAULT CHARSET=utf8mb3; +``` + +``` +-- redmine_pr_rds.time_entries definition + +CREATE TABLE `time_entries` ( + `id` int NOT NULL AUTO_INCREMENT, + `project_id` int NOT NULL, + `author_id` int DEFAULT NULL, + `user_id` int NOT NULL, + `issue_id` int DEFAULT NULL, + `hours` float NOT NULL, + `comments` varchar(1024) DEFAULT NULL, + `activity_id` int NOT NULL, + `spent_on` date NOT NULL, + `tyear` int NOT NULL, + `tmonth` int NOT NULL, + `tweek` int NOT NULL, + `created_on` datetime NOT NULL, + `updated_on` datetime NOT NULL, + PRIMARY KEY (`id`), + KEY `time_entries_project_id` (`project_id`), + KEY `time_entries_issue_id` (`issue_id`), + KEY `index_time_entries_on_activity_id` (`activity_id`), + KEY `index_time_entries_on_user_id` (`user_id`), + KEY `index_time_entries_on_created_on` (`created_on`) +) ENGINE=InnoDB AUTO_INCREMENT=650391 DEFAULT CHARSET=utf8mb3; +``` \ No newline at end of file diff --git a/docker-compose.override.yml b/docker-compose.override.yml deleted file mode 100644 index 8466960..0000000 --- a/docker-compose.override.yml +++ /dev/null @@ -1,15 +0,0 @@ -services: - redmine-tunnel: - container_name: redmine-tunnel - image: alpine:latest - command: > - sh -c "apk add --no-cache openssh && - ssh -v -o StrictHostKeyChecking=no -i /root/.ssh/id_rsa ubuntu@staging.forecasting-v2.gluzdov.com -N -L 0.0.0.0:3306:redmine-pr-rds-db-read.c1kaki1qbk4o.us-east-1.rds.amazonaws.com:3306" - volumes: - - ~/.ssh:/root/.ssh:ro - ports: - - "3306:3306" - networks: - - app-network - environment: - - SSH_KEY=/root/.ssh/id_rsa \ No newline at end of file diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.ts index 2e86a26..dfd2e11 100644 --- a/workers/main/src/activities/weeklyFinancialReports/redmine.ts +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.ts @@ -1,13 +1,14 @@ -import { Redmine } from '../../common/Redmine'; +import { RedmineRepository } from '../../common/Redmine'; +import { RedminePool } from '../../common/RedminePool'; import type { ProjectUnit } from '../../common/types'; import { redmineDatabaseConfig } from '../../configs/redmineDatabase'; import type { FinancialData } from './redmine.types'; -const redmine = new Redmine(redmineDatabaseConfig); +const redminePool = new RedminePool(redmineDatabaseConfig); +const redmineRepo = new RedmineRepository(redminePool); -export const getProjectUnits = async (): Promise => { - return redmine.getProjectUnits(); -}; +export const getProjectUnits = async (): Promise => + redmineRepo.getProjectUnits(); export async function fetchFinancialData( period: string = 'current', diff --git a/workers/main/src/common/Redmine.test.ts b/workers/main/src/common/Redmine.test.ts index 13ecbca..ccb4981 100644 --- a/workers/main/src/common/Redmine.test.ts +++ b/workers/main/src/common/Redmine.test.ts @@ -16,7 +16,8 @@ import { } from 'vitest'; import { getProjectUnits } from '../activities'; -import { Redmine } from './Redmine'; +import { RedmineRepository } from './Redmine'; +import { RedminePool } from './RedminePool'; import { ProjectUnit } from './types'; // Mock data @@ -66,7 +67,7 @@ describe('Redmine Activities', () => { }); it('getProjectUnits returns project units from Redmine', async () => { - vi.spyOn(Redmine.prototype, 'getProjectUnits').mockResolvedValue( + vi.spyOn(RedmineRepository.prototype, 'getProjectUnits').mockResolvedValue( mockProjectUnits, ); @@ -83,7 +84,7 @@ describe('Redmine Activities', () => { const mockError = new Error(errorMessage); const mockGetProjectUnits = vi - .spyOn(Redmine.prototype, 'getProjectUnits') + .spyOn(RedmineRepository.prototype, 'getProjectUnits') .mockRejectedValue(mockError); await expect(activityContext.run(getProjectUnits)).rejects.toThrow( @@ -94,19 +95,20 @@ describe('Redmine Activities', () => { }); }); -describe('Redmine.getProjectUnitsQuery (private method)', () => { +describe('RedmineRepository.getProjectUnitsQuery (private method)', () => { const dummyCredentials = { host: 'localhost', user: 'test', database: 'test', password: 'test', }; - const redmine = new Redmine(dummyCredentials); + const redminePool = new RedminePool(dummyCredentials); + const redmineRepo = new RedmineRepository(redminePool); // Helper to access private method function callGetProjectUnitsQuery() { // @ts-expect-error: Accessing private method for test purposes - return redmine.getProjectUnitsQuery(); + return redmineRepo.getProjectUnitsQuery(); } it('returns correct query string', () => { @@ -120,74 +122,74 @@ describe('Redmine.getProjectUnitsQuery (private method)', () => { }); }); -describe('Redmine class internals', () => { +describe('RedmineRepository class internals', () => { const credentials = { host: 'localhost', user: 'test', database: 'test', password: 'test', }; - let redmine: Redmine; + let redminePool: RedminePool; beforeEach(async () => { - redmine = new Redmine(credentials); + redminePool = new RedminePool(credentials); }); - it('should initialize pool in constructor', () => { + it('should initialize pool in RedminePool constructor', () => { const testCredentials = { host: 'localhost', user: 'test', database: 'test', password: 'test', }; - const testRedmine = new Redmine(testCredentials); + const testRedminePool = new RedminePool(testCredentials); expect(mysql.createPool as Mock).toHaveBeenCalledWith(testCredentials); - expect(testRedmine['pool']).toBeDefined(); + expect(testRedminePool['pool']).toBeDefined(); }); it('should re-initialize pool if poolEnded is true', () => { - const oldPool = redmine['pool']; + const oldPool = redminePool['pool']; - redmine['poolEnded'] = true; - redmine['ensureConnection'](); - expect(redmine['pool']).not.toBe(oldPool); - expect(redmine['poolEnded']).toBe(false); + redminePool['poolEnded'] = true; + redminePool['getPool'](); + expect(redminePool['pool']).not.toBe(oldPool); + expect(redminePool['poolEnded']).toBe(false); }); it('should re-initialize pool if pool is undefined', () => { // Reset the mock to clear previous calls (mysql.createPool as Mock).mockClear(); - Object.defineProperty(redmine, 'pool', { + Object.defineProperty(redminePool, 'pool', { value: undefined, writable: true, }); - redmine['poolEnded'] = false; - redmine['ensureConnection'](); + redminePool['poolEnded'] = false; + redminePool['getPool'](); expect(mysql.createPool as Mock).toHaveBeenCalledWith(credentials); - expect(redmine['pool']).toBeDefined(); - expect(redmine['poolEnded']).toBe(false); + expect(redminePool['pool']).toBeDefined(); + expect(redminePool['poolEnded']).toBe(false); }); it('should end the pool and set poolEnded to true', async () => { const endSpy = vi - .spyOn(redmine['pool'], 'end') + .spyOn(redminePool['pool'], 'end') .mockResolvedValue(undefined); - await redmine.endPool(); + await redminePool.endPool(); expect(endSpy).toHaveBeenCalled(); - expect(redmine['poolEnded']).toBe(true); + expect(redminePool['poolEnded']).toBe(true); }); it('should not call end if pool is already ended', async () => { - redmine['poolEnded'] = true; - const endSpy = vi.spyOn(redmine['pool'], 'end'); + redminePool['poolEnded'] = true; + const endSpy = vi.spyOn(redminePool['pool'], 'end'); - await redmine.endPool(); + await redminePool.endPool(); expect(endSpy).not.toHaveBeenCalled(); }); - it('should return the pool instance from connection getter', () => { - expect(redmine.connection).toBe(redmine['pool']); + it('should return the pool instance from getPool', () => { + expect(redminePool.getPool()).toBe(redminePool['pool']); }); }); diff --git a/workers/main/src/common/Redmine.ts b/workers/main/src/common/Redmine.ts index 6dcd5de..2d9c9a0 100644 --- a/workers/main/src/common/Redmine.ts +++ b/workers/main/src/common/Redmine.ts @@ -1,31 +1,13 @@ -import * as mysql from 'mysql2/promise'; -import { Pool, PoolOptions, RowDataPacket } from 'mysql2/promise'; +import { Pool } from 'mysql2/promise'; +import { RedminePool } from './RedminePool'; import { ProjectUnit } from './types'; -export class Redmine { +export class RedmineRepository { private pool: Pool; - private credentials: PoolOptions; - private poolEnded = false; - constructor(credentials: PoolOptions) { - this.credentials = credentials; - this.pool = mysql.createPool(this.credentials); - } - - private ensureConnection() { - if (!this.pool || this.poolEnded) { - this.pool = mysql.createPool(this.credentials); - this.poolEnded = false; - } - } - - /** Call this when you want to end the pool */ - async endPool() { - if (this.pool && !this.poolEnded) { - await this.pool.end(); - this.poolEnded = true; - } + constructor(redminePool: RedminePool) { + this.pool = redminePool.getPool(); } private getProjectUnitsQuery() { @@ -60,15 +42,9 @@ ORDER BY group_name ASC, project_name ASC, username ASC, spent_on ASC`; } async getProjectUnits(): Promise { - this.ensureConnection(); - const query = this.getProjectUnitsQuery(); - const [rows] = await this.pool.execute(query); + const [rows] = await this.pool.execute(query); return rows as ProjectUnit[]; } - - get connection() { - return this.pool; - } } diff --git a/workers/main/src/common/RedminePool.ts b/workers/main/src/common/RedminePool.ts new file mode 100644 index 0000000..b56b9e4 --- /dev/null +++ b/workers/main/src/common/RedminePool.ts @@ -0,0 +1,33 @@ +import * as mysql from 'mysql2/promise'; +import { Pool, PoolOptions } from 'mysql2/promise'; + +export class RedminePool { + private pool: Pool | null = null; + private credentials: PoolOptions; + private poolEnded = false; + + constructor(credentials: PoolOptions) { + this.credentials = credentials; + this.createPool(); + } + + private createPool() { + this.pool = mysql.createPool(this.credentials); + this.poolEnded = false; + } + + public getPool(): Pool { + if (!this.pool || this.poolEnded) { + this.createPool(); + } + + return this.pool!; + } + + async endPool() { + if (this.pool && !this.poolEnded) { + await this.pool.end(); + this.poolEnded = true; + } + } +} From f79a6515123f10c57ed6171f86b4cce9e9b3d9b1 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Fri, 30 May 2025 13:35:02 +0200 Subject: [PATCH 25/38] test(redmine): add tests for getProjectUnits function and enhance fetchFinancialData tests - Introduced a new test suite for the `getProjectUnits` function, verifying that it correctly calls the method from the `redmineModule`. - Enhanced the existing tests for `fetchFinancialData` to ensure comprehensive coverage of its functionality. - Updated imports to include necessary modules for testing, improving the overall test structure. These changes contribute to a more robust and reliable test suite for the Redmine functionalities, ensuring better validation of the implemented features. --- .../weeklyFinancialReports/redmine.test.ts | 15 ++++++++++++++- workers/main/src/common/Redmine.test.ts | 1 - 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts index 1b3e9eb..62edb9e 100644 --- a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts @@ -1,5 +1,6 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; +import * as redmineModule from './redmine'; import { fetchFinancialData } from './redmine'; describe('fetchFinancialData', () => { @@ -19,3 +20,15 @@ describe('fetchFinancialData', () => { expect(data.period).toBe('previous'); }); }); + +describe('getProjectUnits', () => { + it('calls redmineRepo.getProjectUnits', async () => { + const spy = vi + .spyOn(redmineModule, 'getProjectUnits') + .mockResolvedValue([]); + + await redmineModule.getProjectUnits(); + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); +}); diff --git a/workers/main/src/common/Redmine.test.ts b/workers/main/src/common/Redmine.test.ts index ccb4981..e684454 100644 --- a/workers/main/src/common/Redmine.test.ts +++ b/workers/main/src/common/Redmine.test.ts @@ -20,7 +20,6 @@ import { RedmineRepository } from './Redmine'; import { RedminePool } from './RedminePool'; import { ProjectUnit } from './types'; -// Mock data const mockProjectUnits: ProjectUnit[] = [ { group_id: 1, From 5ec18e134aba8d6e09c47bb6f3937a378d8790e4 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Fri, 30 May 2025 14:07:28 +0200 Subject: [PATCH 26/38] refactor(redmine): remove fetchFinancialData function and related tests - Deleted the `fetchFinancialData` function from the `redmine.ts` file, simplifying the codebase by removing unused functionality. - Removed associated test cases from `redmine.test.ts` and `index.test.ts`, ensuring the test suite reflects the current implementation. - Updated the `weeklyFinancialReportsWorkflow` to directly return project units without financial data, streamlining the workflow logic. These changes contribute to a cleaner and more maintainable codebase by eliminating unnecessary components and focusing on essential functionalities. --- .cursor/rules/mysql-redmine-tables.mdc | 116 ------------------ .../weeklyFinancialReports/redmine.test.ts | 19 --- .../weeklyFinancialReports/redmine.ts | 17 --- workers/main/src/common/Redmine.test.ts | 13 +- .../weeklyFinancialReports/index.test.ts | 40 +----- .../workflows/weeklyFinancialReports/index.ts | 27 +--- 6 files changed, 12 insertions(+), 220 deletions(-) delete mode 100644 .cursor/rules/mysql-redmine-tables.mdc diff --git a/.cursor/rules/mysql-redmine-tables.mdc b/.cursor/rules/mysql-redmine-tables.mdc deleted file mode 100644 index 0c85330..0000000 --- a/.cursor/rules/mysql-redmine-tables.mdc +++ /dev/null @@ -1,116 +0,0 @@ ---- -description: -globs: -alwaysApply: false ---- -``` --- redmine_pr_rds.users definition - -CREATE TABLE `users` ( - `id` int NOT NULL AUTO_INCREMENT, - `login` varchar(255) NOT NULL DEFAULT '', - `hashed_password` varchar(40) NOT NULL DEFAULT '', - `firstname` varchar(30) NOT NULL DEFAULT '', - `lastname` varchar(255) NOT NULL DEFAULT '', - `admin` tinyint(1) NOT NULL DEFAULT '0', - `status` int NOT NULL DEFAULT '1', - `last_login_on` datetime DEFAULT NULL, - `language` varchar(5) DEFAULT '', - `auth_source_id` int DEFAULT NULL, - `created_on` datetime DEFAULT NULL, - `updated_on` datetime DEFAULT NULL, - `type` varchar(255) DEFAULT NULL, - `mail_notification` varchar(255) NOT NULL DEFAULT '', - `salt` varchar(64) DEFAULT NULL, - `must_change_passwd` tinyint(1) NOT NULL DEFAULT '0', - `passwd_changed_on` datetime DEFAULT NULL, - `twofa_scheme` varchar(255) DEFAULT NULL, - `twofa_totp_key` varchar(255) DEFAULT NULL, - `twofa_totp_last_used_at` int DEFAULT NULL, - `twofa_required` tinyint(1) DEFAULT '0', - PRIMARY KEY (`id`), - KEY `index_users_on_id_and_type` (`id`,`type`), - KEY `index_users_on_auth_source_id` (`auth_source_id`), - KEY `index_users_on_type` (`type`) -) ENGINE=InnoDB AUTO_INCREMENT=1109 DEFAULT CHARSET=utf8mb3; -``` - -``` -CREATE TABLE `projects` ( - `id` int NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL DEFAULT '', - `description` text, - `homepage` varchar(255) DEFAULT '', - `is_public` tinyint(1) NOT NULL DEFAULT '1', - `parent_id` int DEFAULT NULL, - `created_on` datetime DEFAULT NULL, - `updated_on` datetime DEFAULT NULL, - `identifier` varchar(255) DEFAULT NULL, - `status` int NOT NULL DEFAULT '1', - `lft` int DEFAULT NULL, - `rgt` int DEFAULT NULL, - `inherit_members` tinyint(1) NOT NULL DEFAULT '0', - `default_version_id` int DEFAULT NULL, - `default_assigned_to_id` int DEFAULT NULL, - `default_issue_query_id` int DEFAULT NULL, - `easy_baseline_for_id` bigint DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `index_projects_on_lft` (`lft`), - KEY `index_projects_on_rgt` (`rgt`), - KEY `index_projects_on_easy_baseline_for_id` (`easy_baseline_for_id`) -) ENGINE=InnoDB AUTO_INCREMENT=1325 DEFAULT CHARSET=utf8mb3; -``` - - -``` -CREATE TABLE `projects` ( - `id` int NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL DEFAULT '', - `description` text, - `homepage` varchar(255) DEFAULT '', - `is_public` tinyint(1) NOT NULL DEFAULT '1', - `parent_id` int DEFAULT NULL, - `created_on` datetime DEFAULT NULL, - `updated_on` datetime DEFAULT NULL, - `identifier` varchar(255) DEFAULT NULL, - `status` int NOT NULL DEFAULT '1', - `lft` int DEFAULT NULL, - `rgt` int DEFAULT NULL, - `inherit_members` tinyint(1) NOT NULL DEFAULT '0', - `default_version_id` int DEFAULT NULL, - `default_assigned_to_id` int DEFAULT NULL, - `default_issue_query_id` int DEFAULT NULL, - `easy_baseline_for_id` bigint DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `index_projects_on_lft` (`lft`), - KEY `index_projects_on_rgt` (`rgt`), - KEY `index_projects_on_easy_baseline_for_id` (`easy_baseline_for_id`) -) ENGINE=InnoDB AUTO_INCREMENT=1325 DEFAULT CHARSET=utf8mb3; -``` - -``` --- redmine_pr_rds.time_entries definition - -CREATE TABLE `time_entries` ( - `id` int NOT NULL AUTO_INCREMENT, - `project_id` int NOT NULL, - `author_id` int DEFAULT NULL, - `user_id` int NOT NULL, - `issue_id` int DEFAULT NULL, - `hours` float NOT NULL, - `comments` varchar(1024) DEFAULT NULL, - `activity_id` int NOT NULL, - `spent_on` date NOT NULL, - `tyear` int NOT NULL, - `tmonth` int NOT NULL, - `tweek` int NOT NULL, - `created_on` datetime NOT NULL, - `updated_on` datetime NOT NULL, - PRIMARY KEY (`id`), - KEY `time_entries_project_id` (`project_id`), - KEY `time_entries_issue_id` (`issue_id`), - KEY `index_time_entries_on_activity_id` (`activity_id`), - KEY `index_time_entries_on_user_id` (`user_id`), - KEY `index_time_entries_on_created_on` (`created_on`) -) ENGINE=InnoDB AUTO_INCREMENT=650391 DEFAULT CHARSET=utf8mb3; -``` \ No newline at end of file diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts index 62edb9e..67a4cbd 100644 --- a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts @@ -1,25 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; import * as redmineModule from './redmine'; -import { fetchFinancialData } from './redmine'; - -describe('fetchFinancialData', () => { - it('returns expected mock data for default period', async () => { - const data = await fetchFinancialData(); - - expect(data).toBeDefined(); - expect(data.period).toBe('current'); - expect(data.contractType).toBe('T&M'); - expect(data.revenue).toBe(120000); - }); - - it('returns expected mock data for custom period', async () => { - const data = await fetchFinancialData('previous'); - - expect(data).toBeDefined(); - expect(data.period).toBe('previous'); - }); -}); describe('getProjectUnits', () => { it('calls redmineRepo.getProjectUnits', async () => { diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.ts index dfd2e11..d5d48c4 100644 --- a/workers/main/src/activities/weeklyFinancialReports/redmine.ts +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.ts @@ -2,26 +2,9 @@ import { RedmineRepository } from '../../common/Redmine'; import { RedminePool } from '../../common/RedminePool'; import type { ProjectUnit } from '../../common/types'; import { redmineDatabaseConfig } from '../../configs/redmineDatabase'; -import type { FinancialData } from './redmine.types'; const redminePool = new RedminePool(redmineDatabaseConfig); const redmineRepo = new RedmineRepository(redminePool); export const getProjectUnits = async (): Promise => redmineRepo.getProjectUnits(); - -export async function fetchFinancialData( - period: string = 'current', -): Promise { - return { - period: period, - contractType: 'T&M', - revenue: 120000, - cogs: 80000, - margin: 40000, - marginality: 33.3, - effectiveRevenue: 110000, - effectiveMargin: 35000, - effectiveMarginality: 31.8, - }; -} diff --git a/workers/main/src/common/Redmine.test.ts b/workers/main/src/common/Redmine.test.ts index e684454..63276ba 100644 --- a/workers/main/src/common/Redmine.test.ts +++ b/workers/main/src/common/Redmine.test.ts @@ -3,6 +3,7 @@ import { TestWorkflowEnvironment, } from '@temporalio/testing'; import { DefaultLogger, LogEntry, Runtime } from '@temporalio/worker'; +import type { Pool } from 'mysql2/promise'; import * as mysql from 'mysql2/promise'; import type { Mock } from 'vitest'; import { @@ -41,7 +42,12 @@ vi.mock('mysql2/promise', async () => { return { ...actual, - createPool: vi.fn(() => ({ end: vi.fn() })), + createPool: vi.fn( + () => + ({ + end: vi.fn(), + }) as unknown as Pool, + ), }; }); @@ -157,7 +163,6 @@ describe('RedmineRepository class internals', () => { }); it('should re-initialize pool if pool is undefined', () => { - // Reset the mock to clear previous calls (mysql.createPool as Mock).mockClear(); Object.defineProperty(redminePool, 'pool', { value: undefined, @@ -172,7 +177,7 @@ describe('RedmineRepository class internals', () => { it('should end the pool and set poolEnded to true', async () => { const endSpy = vi - .spyOn(redminePool['pool'], 'end') + .spyOn(redminePool['pool'] as Pool, 'end') .mockResolvedValue(undefined); await redminePool.endPool(); @@ -182,7 +187,7 @@ describe('RedmineRepository class internals', () => { it('should not call end if pool is already ended', async () => { redminePool['poolEnded'] = true; - const endSpy = vi.spyOn(redminePool['pool'], 'end'); + const endSpy = vi.spyOn(redminePool['pool'] as Pool, 'end'); await redminePool.endPool(); expect(endSpy).not.toHaveBeenCalled(); diff --git a/workers/main/src/workflows/weeklyFinancialReports/index.test.ts b/workers/main/src/workflows/weeklyFinancialReports/index.test.ts index 60d4c42..223b119 100644 --- a/workers/main/src/workflows/weeklyFinancialReports/index.test.ts +++ b/workers/main/src/workflows/weeklyFinancialReports/index.test.ts @@ -2,10 +2,9 @@ import { TestWorkflowEnvironment } from '@temporalio/testing'; import { DefaultLogger, LogEntry, Runtime, Worker } from '@temporalio/worker'; import { v4 as uuidv4 } from 'uuid'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { vi } from 'vitest'; -import type { FinancialData } from '../../activities'; import { weeklyFinancialReportsWorkflow } from '..'; -import { generateReport } from './index'; const mockProjectUnits = [ { @@ -22,18 +21,6 @@ const mockProjectUnits = [ }, ]; -const mockFinancialData: FinancialData = { - period: 'current', - contractType: 'T&M', - revenue: 120000, - cogs: 80000, - margin: 40000, - marginality: 33.3, - effectiveRevenue: 110000, - effectiveMargin: 35000, - effectiveMarginality: 31.8, -}; - describe('weeklyFinancialReportsWorkflow', () => { let testEnv: TestWorkflowEnvironment; @@ -56,7 +43,6 @@ describe('weeklyFinancialReportsWorkflow', () => { const { client, nativeConnection } = testEnv; const mockActivities = { getProjectUnits: async () => mockProjectUnits, - fetchFinancialData: async () => mockFinancialData, }; const taskQueue = `test-${uuidv4()}`; @@ -78,29 +64,5 @@ describe('weeklyFinancialReportsWorkflow', () => { expect(result).toContain('Weekly Financial Report'); expect(result).toContain('Engineering'); expect(result).toContain('Project Alpha'); - expect(result).toContain('Revenue: $120,000'); - expect(result).toContain('COGS: $80,000'); - expect(result).toContain('Margin: $40,000'); - expect(result).toContain('Marginality: 33.3%'); - expect(result).toContain('Effective Revenue (last 4 months): $110,000'); - expect(result).toContain('Effective Margin: $35,000'); - expect(result).toContain('Effective Marginality: 31.8%'); - }); -}); - -describe('generateReport', () => { - it('formats the report string as expected', () => { - const reportTitle = 'Test Report'; - const report = generateReport(reportTitle, mockFinancialData); - - expect(report).toContain('Period: Test Report'); - expect(report).toContain('Contract Type: T&M'); - expect(report).toContain('Revenue: $120,000'); - expect(report).toContain('COGS: $80,000'); - expect(report).toContain('Margin: $40,000'); - expect(report).toContain('Marginality: 33.3%'); - expect(report).toContain('Effective Revenue (last 4 months): $110,000'); - expect(report).toContain('Effective Margin: $35,000'); - expect(report).toContain('Effective Marginality: 31.8%'); }); }); diff --git a/workers/main/src/workflows/weeklyFinancialReports/index.ts b/workers/main/src/workflows/weeklyFinancialReports/index.ts index bbb32fe..9669aea 100644 --- a/workers/main/src/workflows/weeklyFinancialReports/index.ts +++ b/workers/main/src/workflows/weeklyFinancialReports/index.ts @@ -1,40 +1,17 @@ import { proxyActivities } from '@temporalio/workflow'; import type * as activities from '../../activities/weeklyFinancialReports'; -import { FinancialData } from '../../activities/weeklyFinancialReports'; -const { getProjectUnits, fetchFinancialData } = proxyActivities< - typeof activities ->({ +const { getProjectUnits } = proxyActivities({ startToCloseTimeout: '10 minutes', }); -export function generateReport( - reportTitle: string, - data: FinancialData, -): string { - return ( - `Period: ${reportTitle}\n` + - `Contract Type: ${data.contractType}\n` + - `Revenue: $${data.revenue.toLocaleString()}\n` + - `COGS: $${data.cogs.toLocaleString()}\n` + - `Margin: $${data.margin.toLocaleString()}\n` + - `Marginality: ${data.marginality}%\n` + - `\n` + - `Effective Revenue (last 4 months): $${data.effectiveRevenue.toLocaleString()}\n` + - `Effective Margin: $${data.effectiveMargin.toLocaleString()}\n` + - `Effective Marginality: ${data.effectiveMarginality}%\n` - ); -} - export async function weeklyFinancialReportsWorkflow(): Promise { try { const reportTitle = 'Weekly Financial Report'; const projectUnits = await getProjectUnits(); - const data = await fetchFinancialData(); - const report = generateReport(reportTitle, data); - return `${report}\n${JSON.stringify(projectUnits, null, 2)}`; + return `${reportTitle}\n${JSON.stringify(projectUnits, null, 2)}`; } catch (error) { console.error('Weekly Financial Reports', error); throw error; From 935bcc760e0dc21362efa8f54a22833743cff6de Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Fri, 30 May 2025 17:02:49 +0200 Subject: [PATCH 27/38] refactor(financial): restructure financial services and repository integration - Introduced `ProjectUnitRepository` and `FinancialReportService` to encapsulate financial data handling and improve code organization. - Implemented `RedmineRepository` and `RedmineService` for better management of Redmine-related data retrieval. - Added new activities and workflows for generating weekly financial reports, enhancing the overall functionality of the financial module. - Removed obsolete test files and added new tests for the financial services, ensuring comprehensive coverage of the new implementations. These changes contribute to a more maintainable and efficient financial reporting system, improving the overall structure and reliability of the codebase. --- workers/main/package-lock.json | 241 ++++++++---------- workers/main/package.json | 7 +- workers/main/src/__tests__/utils.test.ts | 58 ----- .../src/activities/financial/factory.test.ts | 22 ++ .../main/src/activities/financial/factory.ts | 10 + .../financial/getProjectUnits.activity.ts | 4 + .../main/src/activities/financial/index.ts | 1 + .../weeklyFinancialReports/redmine.test.ts | 27 ++ .../weeklyFinancialReports/redmine.ts | 11 +- workers/main/src/common/Redmine.test.ts | 199 --------------- workers/main/src/common/RedminePool.test.ts | 59 +++++ workers/main/src/index.test.ts | 1 - .../financial/IProjectUnitRepository.ts | 5 + .../financial/ProjectUnitRepository.ts | 49 ++++ .../financial/FinancialReportService.ts | 8 + .../services/redmine/IRedmineRepository.ts | 5 + .../redmine/RedmineRepository.ts} | 7 +- .../src/services/redmine/RedmineService.ts | 9 + .../index.test.ts | 3 +- workers/main/src/workflows/financial/index.ts | 1 + .../weeklyFinancialReports.workflow.ts | 14 + workers/main/src/workflows/index.ts | 2 +- .../workflows/weeklyFinancialReports/index.ts | 19 -- 23 files changed, 339 insertions(+), 423 deletions(-) delete mode 100644 workers/main/src/__tests__/utils.test.ts create mode 100644 workers/main/src/activities/financial/factory.test.ts create mode 100644 workers/main/src/activities/financial/factory.ts create mode 100644 workers/main/src/activities/financial/getProjectUnits.activity.ts create mode 100644 workers/main/src/activities/financial/index.ts delete mode 100644 workers/main/src/common/Redmine.test.ts create mode 100644 workers/main/src/common/RedminePool.test.ts create mode 100644 workers/main/src/repositories/financial/IProjectUnitRepository.ts create mode 100644 workers/main/src/repositories/financial/ProjectUnitRepository.ts create mode 100644 workers/main/src/services/financial/FinancialReportService.ts create mode 100644 workers/main/src/services/redmine/IRedmineRepository.ts rename workers/main/src/{common/Redmine.ts => services/redmine/RedmineRepository.ts} (83%) create mode 100644 workers/main/src/services/redmine/RedmineService.ts rename workers/main/src/workflows/{weeklyFinancialReports => financial}/index.test.ts (95%) create mode 100644 workers/main/src/workflows/financial/index.ts create mode 100644 workers/main/src/workflows/financial/weeklyFinancialReports.workflow.ts delete mode 100644 workers/main/src/workflows/weeklyFinancialReports/index.ts diff --git a/workers/main/package-lock.json b/workers/main/package-lock.json index 80767f6..34ae283 100644 --- a/workers/main/package-lock.json +++ b/workers/main/package-lock.json @@ -16,7 +16,7 @@ "zod": "3.25.17" }, "devDependencies": { - "@eslint/js": "9.27.0", + "@eslint/js": "8.57.1", "@temporalio/testing": "1.11.8", "@types/node": "22.15.21", "@vitest/coverage-v8": "3.1.3", @@ -32,7 +32,7 @@ "source-map-support": "^0.5.21", "ts-node": "10.9.1", "typescript": "5.8.3", - "typescript-eslint": "8.32.1", + "typescript-eslint": "8.33.0", "uuid": "11.1.0", "vite": "6.3.5", "vitest": "3.1.3" @@ -639,7 +639,6 @@ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", @@ -654,7 +653,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -665,7 +663,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -678,7 +675,6 @@ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -688,7 +684,6 @@ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -701,7 +696,6 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -725,7 +719,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -736,7 +729,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -745,16 +737,12 @@ } }, "node_modules/@eslint/js": { - "version": "9.27.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", - "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, - "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@eslint/object-schema": { @@ -762,7 +750,6 @@ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -772,7 +759,6 @@ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.14.0", "levn": "^0.4.1" @@ -815,7 +801,6 @@ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=18.18.0" } @@ -825,7 +810,6 @@ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" @@ -839,7 +823,6 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -867,7 +850,6 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -1038,7 +1020,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1052,7 +1033,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } @@ -1062,7 +1042,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1894,17 +1873,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz", - "integrity": "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz", + "integrity": "sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.32.1", - "@typescript-eslint/type-utils": "8.32.1", - "@typescript-eslint/utils": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1", + "@typescript-eslint/scope-manager": "8.33.0", + "@typescript-eslint/type-utils": "8.33.0", + "@typescript-eslint/utils": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -1918,7 +1896,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "@typescript-eslint/parser": "^8.33.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -1928,22 +1906,20 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz", - "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.0.tgz", + "integrity": "sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.32.1", - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/typescript-estree": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1", + "@typescript-eslint/scope-manager": "8.33.0", + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/typescript-estree": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0", "debug": "^4.3.4" }, "engines": { @@ -1958,15 +1934,32 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.0.tgz", + "integrity": "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==", + "dev": true, + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.33.0", + "@typescript-eslint/types": "^8.33.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", - "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz", + "integrity": "sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1" + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1976,15 +1969,30 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz", + "integrity": "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz", - "integrity": "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz", + "integrity": "sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.32.1", - "@typescript-eslint/utils": "8.32.1", + "@typescript-eslint/typescript-estree": "8.33.0", + "@typescript-eslint/utils": "8.33.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -2001,11 +2009,10 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", - "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz", + "integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2015,14 +2022,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz", - "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz", + "integrity": "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1", + "@typescript-eslint/project-service": "8.33.0", + "@typescript-eslint/tsconfig-utils": "8.33.0", + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2042,16 +2050,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz", - "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.0.tgz", + "integrity": "sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.32.1", - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/typescript-estree": "8.32.1" + "@typescript-eslint/scope-manager": "8.33.0", + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/typescript-estree": "8.33.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2066,13 +2073,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz", - "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz", + "integrity": "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/types": "8.33.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2622,7 +2628,6 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -2645,7 +2650,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2730,8 +2734,7 @@ "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" + "dev": true }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", @@ -2919,7 +2922,6 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -3062,7 +3064,6 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -3742,7 +3743,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -4020,7 +4020,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -4045,6 +4044,18 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4074,7 +4085,6 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", @@ -4185,7 +4195,6 @@ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -4202,7 +4211,6 @@ "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" }, @@ -4214,8 +4222,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -4244,7 +4251,6 @@ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, - "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -4269,7 +4275,6 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, - "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" }, @@ -4282,7 +4287,6 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, - "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -4312,7 +4316,6 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, - "license": "MIT", "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -4325,8 +4328,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/for-each": { "version": "0.3.5", @@ -4552,7 +4554,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -4599,8 +4600,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/has-bigints": { "version": "1.1.0", @@ -4734,7 +4734,6 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } @@ -4744,7 +4743,6 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, - "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -5010,7 +5008,6 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -5298,7 +5295,6 @@ "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" }, @@ -5310,8 +5306,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -5322,8 +5317,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -5350,7 +5344,6 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -5521,7 +5514,6 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } @@ -5531,7 +5523,6 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, - "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -5545,7 +5536,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, - "license": "MIT", "engines": { "node": ">=8.6" }, @@ -5881,7 +5871,6 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -6086,7 +6075,6 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -6109,8 +6097,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/randombytes": { "version": "2.1.0", @@ -6207,7 +6194,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -6226,7 +6212,6 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, - "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -6291,7 +6276,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -6885,7 +6869,6 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" }, @@ -7097,7 +7080,6 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -7125,7 +7107,6 @@ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=18.12" }, @@ -7300,15 +7281,14 @@ } }, "node_modules/typescript-eslint": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.32.1.tgz", - "integrity": "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.0.tgz", + "integrity": "sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.32.1", - "@typescript-eslint/parser": "8.32.1", - "@typescript-eslint/utils": "8.32.1" + "@typescript-eslint/eslint-plugin": "8.33.0", + "@typescript-eslint/parser": "8.33.0", + "@typescript-eslint/utils": "8.33.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7420,7 +7400,6 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } diff --git a/workers/main/package.json b/workers/main/package.json index 3da92b8..4fd6471 100644 --- a/workers/main/package.json +++ b/workers/main/package.json @@ -5,10 +5,11 @@ "scripts": { "test": "vitest run", "coverage": "vitest run --coverage", - "eslint": "eslint . --ext .ts" + "eslint": "eslint . --ext .ts", + "build": "tsc" }, "devDependencies": { - "@eslint/js": "9.27.0", + "@eslint/js": "8.57.1", "@types/node": "22.15.21", "@temporalio/testing": "1.11.8", "@vitest/coverage-v8": "3.1.3", @@ -24,7 +25,7 @@ "source-map-support": "^0.5.21", "ts-node": "10.9.1", "typescript": "5.8.3", - "typescript-eslint": "8.32.1", + "typescript-eslint": "8.33.0", "uuid": "11.1.0", "vite": "6.3.5", "vitest": "3.1.3" diff --git a/workers/main/src/__tests__/utils.test.ts b/workers/main/src/__tests__/utils.test.ts deleted file mode 100644 index 2a56244..0000000 --- a/workers/main/src/__tests__/utils.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; - -vi.mock('../common/../configs', () => ({ - validationResult: { success: true }, -})); - -import * as configs from '../common/../configs'; -import { validateEnv } from '../common/utils'; - -type ValidationResult = { - success: boolean; - error?: { issues: { path: unknown[]; message: string }[] }; -}; -function setValidationResult(result: ValidationResult) { - (configs as { validationResult: ValidationResult }).validationResult = result; -} - -describe('validateEnv', () => { - let errorSpy: ReturnType; - let exitSpy: ReturnType; - - beforeEach(() => { - errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => { - throw new Error('exit'); - }) as unknown as ReturnType; - }); - - afterEach(() => { - errorSpy.mockRestore(); - exitSpy.mockRestore(); - }); - - it('does nothing if validationResult.success is true', () => { - setValidationResult({ success: true }); - expect(() => validateEnv()).not.toThrow(); - expect(errorSpy).not.toHaveBeenCalled(); - expect(exitSpy).not.toHaveBeenCalled(); - }); - - it('logs error and exits if validationResult.success is false', () => { - setValidationResult({ - success: false, - error: { - issues: [ - { path: ['FOO'], message: 'is required' }, - { path: [], message: 'unknown' }, - ], - }, - }); - expect(() => validateEnv()).toThrow('exit'); - expect(errorSpy).toHaveBeenCalledWith( - 'Missing or invalid environment variable: FOO (is required)\n' + - 'Missing or invalid environment variable: (unknown variable) (unknown)', - ); - expect(exitSpy).toHaveBeenCalledWith(1); - }); -}); diff --git a/workers/main/src/activities/financial/factory.test.ts b/workers/main/src/activities/financial/factory.test.ts new file mode 100644 index 0000000..51975f1 --- /dev/null +++ b/workers/main/src/activities/financial/factory.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest'; + +import { ProjectUnitRepository } from '../../repositories/financial/ProjectUnitRepository'; +import { FinancialReportService } from '../../services/financial/FinancialReportService'; +import { + createFinancialReportService, + createProjectUnitRepository, +} from './factory'; + +describe('factory', () => { + it('createProjectUnitRepository returns ProjectUnitRepository instance', () => { + const repo = createProjectUnitRepository(); + + expect(repo).toBeInstanceOf(ProjectUnitRepository); + }); + + it('createFinancialReportService returns FinancialReportService instance', () => { + const service = createFinancialReportService(); + + expect(service).toBeInstanceOf(FinancialReportService); + }); +}); diff --git a/workers/main/src/activities/financial/factory.ts b/workers/main/src/activities/financial/factory.ts new file mode 100644 index 0000000..7c88584 --- /dev/null +++ b/workers/main/src/activities/financial/factory.ts @@ -0,0 +1,10 @@ +import { ProjectUnitRepository } from '../../repositories/financial/ProjectUnitRepository'; +import { FinancialReportService } from '../../services/financial/FinancialReportService'; + +export function createProjectUnitRepository() { + return new ProjectUnitRepository(); +} + +export function createFinancialReportService() { + return new FinancialReportService(createProjectUnitRepository()); +} diff --git a/workers/main/src/activities/financial/getProjectUnits.activity.ts b/workers/main/src/activities/financial/getProjectUnits.activity.ts new file mode 100644 index 0000000..f98c990 --- /dev/null +++ b/workers/main/src/activities/financial/getProjectUnits.activity.ts @@ -0,0 +1,4 @@ +import { createFinancialReportService } from './factory'; + +export const getProjectUnits = async () => + createFinancialReportService().getWeeklyReport(); diff --git a/workers/main/src/activities/financial/index.ts b/workers/main/src/activities/financial/index.ts new file mode 100644 index 0000000..963c7ab --- /dev/null +++ b/workers/main/src/activities/financial/index.ts @@ -0,0 +1 @@ +export * from './getProjectUnits.activity'; diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts index 67a4cbd..fe63124 100644 --- a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts @@ -1,5 +1,7 @@ import { describe, expect, it, vi } from 'vitest'; +import { RedmineRepository } from '../../services/redmine/RedmineRepository'; +import { RedmineService } from '../../services/redmine/RedmineService'; import * as redmineModule from './redmine'; describe('getProjectUnits', () => { @@ -13,3 +15,28 @@ describe('getProjectUnits', () => { spy.mockRestore(); }); }); + +describe('getProjectUnits activity', () => { + it('calls RedmineService.getProjectUnits', async () => { + const serviceSpy = vi + .spyOn(RedmineService.prototype, 'getProjectUnits') + .mockResolvedValue([]); + + await redmineModule.getProjectUnits(); + expect(serviceSpy).toHaveBeenCalled(); + serviceSpy.mockRestore(); + }); +}); + +describe('RedmineService', () => { + it('delegates to repository', async () => { + const repo = { + getProjectUnits: vi.fn().mockResolvedValue(['unit']), + } as unknown as RedmineRepository; + const service = new RedmineService(repo); + const result = await service.getProjectUnits(); + + expect(result).toEqual(['unit']); + expect(repo.getProjectUnits).toHaveBeenCalled(); + }); +}); diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.ts index d5d48c4..b99dee3 100644 --- a/workers/main/src/activities/weeklyFinancialReports/redmine.ts +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.ts @@ -1,10 +1,9 @@ -import { RedmineRepository } from '../../common/Redmine'; import { RedminePool } from '../../common/RedminePool'; -import type { ProjectUnit } from '../../common/types'; import { redmineDatabaseConfig } from '../../configs/redmineDatabase'; +import { RedmineRepository } from '../../services/redmine/RedmineRepository'; +import { RedmineService } from '../../services/redmine/RedmineService'; -const redminePool = new RedminePool(redmineDatabaseConfig); -const redmineRepo = new RedmineRepository(redminePool); +const repo = new RedmineRepository(new RedminePool(redmineDatabaseConfig)); +const service = new RedmineService(repo); -export const getProjectUnits = async (): Promise => - redmineRepo.getProjectUnits(); +export const getProjectUnits = async () => service.getProjectUnits(); diff --git a/workers/main/src/common/Redmine.test.ts b/workers/main/src/common/Redmine.test.ts deleted file mode 100644 index 63276ba..0000000 --- a/workers/main/src/common/Redmine.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { - MockActivityEnvironment, - TestWorkflowEnvironment, -} from '@temporalio/testing'; -import { DefaultLogger, LogEntry, Runtime } from '@temporalio/worker'; -import type { Pool } from 'mysql2/promise'; -import * as mysql from 'mysql2/promise'; -import type { Mock } from 'vitest'; -import { - afterAll, - beforeAll, - beforeEach, - describe, - expect, - it, - vi, -} from 'vitest'; - -import { getProjectUnits } from '../activities'; -import { RedmineRepository } from './Redmine'; -import { RedminePool } from './RedminePool'; -import { ProjectUnit } from './types'; - -const mockProjectUnits: ProjectUnit[] = [ - { - group_id: 1, - group_name: 'Engineering', - project_id: 101, - project_name: 'Project Alpha', - }, - { - group_id: 2, - group_name: 'QA', - project_id: 102, - project_name: 'Project Beta', - }, -]; - -vi.mock('mysql2/promise', async () => { - const actual = - await vi.importActual('mysql2/promise'); - - return { - ...actual, - createPool: vi.fn( - () => - ({ - end: vi.fn(), - }) as unknown as Pool, - ), - }; -}); - -describe('Redmine Activities', () => { - let testEnv: TestWorkflowEnvironment; - let activityContext: MockActivityEnvironment; - - beforeAll(async () => { - Runtime.install({ - logger: new DefaultLogger('WARN', (entry: LogEntry) => - // eslint-disable-next-line no-console - console.log(`[${entry.level}]`, entry.message), - ), - }); - - testEnv = await TestWorkflowEnvironment.createTimeSkipping(); - activityContext = new MockActivityEnvironment(); - }); - - afterAll(async () => { - await testEnv?.teardown(); - }); - - it('getProjectUnits returns project units from Redmine', async () => { - vi.spyOn(RedmineRepository.prototype, 'getProjectUnits').mockResolvedValue( - mockProjectUnits, - ); - - const result = await activityContext.run(getProjectUnits); - - expect(result).toBeDefined(); - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(mockProjectUnits.length); - expect(result).toEqual(mockProjectUnits); - }); - - it('getProjectUnits handles errors gracefully', async () => { - const errorMessage = 'Database connection failed'; - const mockError = new Error(errorMessage); - - const mockGetProjectUnits = vi - .spyOn(RedmineRepository.prototype, 'getProjectUnits') - .mockRejectedValue(mockError); - - await expect(activityContext.run(getProjectUnits)).rejects.toThrow( - errorMessage, - ); - - expect(mockGetProjectUnits).toHaveBeenCalledTimes(1); - }); -}); - -describe('RedmineRepository.getProjectUnitsQuery (private method)', () => { - const dummyCredentials = { - host: 'localhost', - user: 'test', - database: 'test', - password: 'test', - }; - const redminePool = new RedminePool(dummyCredentials); - const redmineRepo = new RedmineRepository(redminePool); - - // Helper to access private method - function callGetProjectUnitsQuery() { - // @ts-expect-error: Accessing private method for test purposes - return redmineRepo.getProjectUnitsQuery(); - } - - it('returns correct query string', () => { - const query = callGetProjectUnitsQuery(); - - expect(typeof query).toBe('string'); - expect(query).toContain('SELECT'); - expect(query).toContain('FROM users AS g'); - expect(query).toContain('JOIN members AS m ON m.user_id = g.id'); - expect(query).toContain('JOIN projects AS p ON p.id = m.project_id'); - }); -}); - -describe('RedmineRepository class internals', () => { - const credentials = { - host: 'localhost', - user: 'test', - database: 'test', - password: 'test', - }; - let redminePool: RedminePool; - - beforeEach(async () => { - redminePool = new RedminePool(credentials); - }); - - it('should initialize pool in RedminePool constructor', () => { - const testCredentials = { - host: 'localhost', - user: 'test', - database: 'test', - password: 'test', - }; - const testRedminePool = new RedminePool(testCredentials); - - expect(mysql.createPool as Mock).toHaveBeenCalledWith(testCredentials); - expect(testRedminePool['pool']).toBeDefined(); - }); - - it('should re-initialize pool if poolEnded is true', () => { - const oldPool = redminePool['pool']; - - redminePool['poolEnded'] = true; - redminePool['getPool'](); - expect(redminePool['pool']).not.toBe(oldPool); - expect(redminePool['poolEnded']).toBe(false); - }); - - it('should re-initialize pool if pool is undefined', () => { - (mysql.createPool as Mock).mockClear(); - Object.defineProperty(redminePool, 'pool', { - value: undefined, - writable: true, - }); - redminePool['poolEnded'] = false; - redminePool['getPool'](); - expect(mysql.createPool as Mock).toHaveBeenCalledWith(credentials); - expect(redminePool['pool']).toBeDefined(); - expect(redminePool['poolEnded']).toBe(false); - }); - - it('should end the pool and set poolEnded to true', async () => { - const endSpy = vi - .spyOn(redminePool['pool'] as Pool, 'end') - .mockResolvedValue(undefined); - - await redminePool.endPool(); - expect(endSpy).toHaveBeenCalled(); - expect(redminePool['poolEnded']).toBe(true); - }); - - it('should not call end if pool is already ended', async () => { - redminePool['poolEnded'] = true; - const endSpy = vi.spyOn(redminePool['pool'] as Pool, 'end'); - - await redminePool.endPool(); - expect(endSpy).not.toHaveBeenCalled(); - }); - - it('should return the pool instance from getPool', () => { - expect(redminePool.getPool()).toBe(redminePool['pool']); - }); -}); diff --git a/workers/main/src/common/RedminePool.test.ts b/workers/main/src/common/RedminePool.test.ts new file mode 100644 index 0000000..f6c990b --- /dev/null +++ b/workers/main/src/common/RedminePool.test.ts @@ -0,0 +1,59 @@ +import type { Pool } from 'mysql2/promise'; +import * as mysql from 'mysql2/promise'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { RedminePool } from './RedminePool'; + +const mockPool: Partial = { + end: vi.fn().mockResolvedValue(undefined), +}; + +// Mock mysql2/promise globally +vi.mock('mysql2/promise', () => ({ + createPool: vi.fn(), +})); + +describe('RedminePool', () => { + const credentials = { + host: 'localhost', + user: 'user', + password: 'pass', + database: 'db', + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('creates pool on construction', () => { + (mysql.createPool as unknown as ReturnType).mockReturnValue(mockPool as Pool); + new RedminePool(credentials); + expect(mysql.createPool).toHaveBeenCalledWith(credentials); + }); + + it('getPool returns the pool', () => { + (mysql.createPool as unknown as ReturnType).mockReturnValue(mockPool as Pool); + const pool = new RedminePool(credentials); + expect(pool.getPool()).toBe(mockPool); + }); + + it('endPool ends the pool and sets poolEnded', async () => { + (mysql.createPool as unknown as ReturnType).mockReturnValue(mockPool as Pool); + const pool = new RedminePool(credentials); + await pool.endPool(); + expect(mockPool.end).toHaveBeenCalled(); + // После завершения poolEnded = true, getPool создаёт новый пул + (mysql.createPool as unknown as ReturnType).mockReturnValue(mockPool as Pool); + pool.getPool(); + expect(mysql.createPool).toHaveBeenCalledTimes(2); // первый раз в конструкторе, второй раз после endPool + }); + + it('getPool recreates pool if ended', async () => { + (mysql.createPool as unknown as ReturnType).mockReturnValue(mockPool as Pool); + const pool = new RedminePool(credentials); + await pool.endPool(); + (mysql.createPool as unknown as ReturnType).mockReturnValue(mockPool as Pool); + pool.getPool(); + expect(mysql.createPool).toHaveBeenCalledTimes(2); + }); +}); diff --git a/workers/main/src/index.test.ts b/workers/main/src/index.test.ts index 3d5fdd8..3c6022e 100644 --- a/workers/main/src/index.test.ts +++ b/workers/main/src/index.test.ts @@ -26,7 +26,6 @@ describe('run', () => { describe('handleRunError', () => { it('should log the error and throw the error', () => { const error = new Error('test error'); - // Spy on logger.error const logSpy = vi.spyOn(logger, 'error').mockImplementation(() => {}); expect(() => handleRunError(error)).toThrow(error); diff --git a/workers/main/src/repositories/financial/IProjectUnitRepository.ts b/workers/main/src/repositories/financial/IProjectUnitRepository.ts new file mode 100644 index 0000000..fee22ef --- /dev/null +++ b/workers/main/src/repositories/financial/IProjectUnitRepository.ts @@ -0,0 +1,5 @@ +import type { ProjectUnit } from '../../../common/types'; + +export interface IProjectUnitRepository { + getProjectUnits(): Promise; +} diff --git a/workers/main/src/repositories/financial/ProjectUnitRepository.ts b/workers/main/src/repositories/financial/ProjectUnitRepository.ts new file mode 100644 index 0000000..fa83001 --- /dev/null +++ b/workers/main/src/repositories/financial/ProjectUnitRepository.ts @@ -0,0 +1,49 @@ +import { Pool } from 'mysql2/promise'; + +import { RedminePool } from '../../common/RedminePool'; +import { ProjectUnit } from '../../common/types'; +import { redmineDatabaseConfig } from '../../configs/redmineDatabase'; +import { IProjectUnitRepository } from './IProjectUnitRepository'; + +export class ProjectUnitRepository implements IProjectUnitRepository { + private pool: Pool; + constructor() { + this.pool = new RedminePool(redmineDatabaseConfig).getPool(); + } + private getProjectUnitsQuery() { + return `SELECT + group_id, + group_name, + project_id, + project_name, + user_id, + username, + spent_on, + SUM(total_hours) AS total_hours +FROM ( + SELECT + g.id AS group_id, + g.lastname AS group_name, + p.id AS project_id, + p.name AS project_name, + te.user_id AS user_id, + CONCAT(u.firstname, ' ', u.lastname) AS username, + te.spent_on AS spent_on, + te.hours AS total_hours + FROM users AS g + JOIN members AS m ON m.user_id = g.id + JOIN projects AS p ON p.id = m.project_id + JOIN time_entries te ON te.project_id = p.id + JOIN users AS u ON u.id = te.user_id + WHERE te.spent_on >= CURDATE() - INTERVAL 7 DAY +) t +GROUP BY group_id, group_name, project_id, project_name, user_id, username, spent_on +ORDER BY group_name ASC, project_name ASC, username ASC, spent_on ASC`; + } + async getProjectUnits(): Promise { + const query = this.getProjectUnitsQuery(); + const [rows] = await this.pool.execute(query); + + return rows as ProjectUnit[]; + } +} diff --git a/workers/main/src/services/financial/FinancialReportService.ts b/workers/main/src/services/financial/FinancialReportService.ts new file mode 100644 index 0000000..f2087c5 --- /dev/null +++ b/workers/main/src/services/financial/FinancialReportService.ts @@ -0,0 +1,8 @@ +import { IProjectUnitRepository } from '../../repositories/financial/IProjectUnitRepository'; + +export class FinancialReportService { + constructor(private repo: IProjectUnitRepository) {} + async getWeeklyReport() { + return this.repo.getProjectUnits(); + } +} diff --git a/workers/main/src/services/redmine/IRedmineRepository.ts b/workers/main/src/services/redmine/IRedmineRepository.ts new file mode 100644 index 0000000..4507360 --- /dev/null +++ b/workers/main/src/services/redmine/IRedmineRepository.ts @@ -0,0 +1,5 @@ +import type { ProjectUnit } from '../../common/types'; + +export interface IRedmineRepository { + getProjectUnits(): Promise; +} diff --git a/workers/main/src/common/Redmine.ts b/workers/main/src/services/redmine/RedmineRepository.ts similarity index 83% rename from workers/main/src/common/Redmine.ts rename to workers/main/src/services/redmine/RedmineRepository.ts index 2d9c9a0..36dc5f8 100644 --- a/workers/main/src/common/Redmine.ts +++ b/workers/main/src/services/redmine/RedmineRepository.ts @@ -1,9 +1,10 @@ import { Pool } from 'mysql2/promise'; -import { RedminePool } from './RedminePool'; -import { ProjectUnit } from './types'; +import { RedminePool } from '../../common/RedminePool'; +import { ProjectUnit } from '../../common/types'; +import { IRedmineRepository } from './IRedmineRepository'; -export class RedmineRepository { +export class RedmineRepository implements IRedmineRepository { private pool: Pool; constructor(redminePool: RedminePool) { diff --git a/workers/main/src/services/redmine/RedmineService.ts b/workers/main/src/services/redmine/RedmineService.ts new file mode 100644 index 0000000..3b8f1d2 --- /dev/null +++ b/workers/main/src/services/redmine/RedmineService.ts @@ -0,0 +1,9 @@ +import { IRedmineRepository } from './IRedmineRepository'; + +export class RedmineService { + constructor(private repo: IRedmineRepository) {} + + async getProjectUnits() { + return this.repo.getProjectUnits(); + } +} diff --git a/workers/main/src/workflows/weeklyFinancialReports/index.test.ts b/workers/main/src/workflows/financial/index.test.ts similarity index 95% rename from workers/main/src/workflows/weeklyFinancialReports/index.test.ts rename to workers/main/src/workflows/financial/index.test.ts index 223b119..c842930 100644 --- a/workers/main/src/workflows/weeklyFinancialReports/index.test.ts +++ b/workers/main/src/workflows/financial/index.test.ts @@ -2,9 +2,8 @@ import { TestWorkflowEnvironment } from '@temporalio/testing'; import { DefaultLogger, LogEntry, Runtime, Worker } from '@temporalio/worker'; import { v4 as uuidv4 } from 'uuid'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { vi } from 'vitest'; -import { weeklyFinancialReportsWorkflow } from '..'; +import { weeklyFinancialReportsWorkflow } from './weeklyFinancialReports.workflow'; const mockProjectUnits = [ { diff --git a/workers/main/src/workflows/financial/index.ts b/workers/main/src/workflows/financial/index.ts new file mode 100644 index 0000000..c3fd4e2 --- /dev/null +++ b/workers/main/src/workflows/financial/index.ts @@ -0,0 +1 @@ +export * from './weeklyFinancialReports.workflow'; diff --git a/workers/main/src/workflows/financial/weeklyFinancialReports.workflow.ts b/workers/main/src/workflows/financial/weeklyFinancialReports.workflow.ts new file mode 100644 index 0000000..0db94af --- /dev/null +++ b/workers/main/src/workflows/financial/weeklyFinancialReports.workflow.ts @@ -0,0 +1,14 @@ +import { proxyActivities } from '@temporalio/workflow'; + +import type * as activities from '../../activities/financial'; + +const { getProjectUnits } = proxyActivities({ + startToCloseTimeout: '10 minutes', +}); + +export async function weeklyFinancialReportsWorkflow(): Promise { + const reportTitle = 'Weekly Financial Report'; + const projectUnits = await getProjectUnits(); + + return `${reportTitle}\n${JSON.stringify(projectUnits, null, 2)}`; +} diff --git a/workers/main/src/workflows/index.ts b/workers/main/src/workflows/index.ts index 2309cad..b8d3a2e 100644 --- a/workers/main/src/workflows/index.ts +++ b/workers/main/src/workflows/index.ts @@ -1 +1 @@ -export { weeklyFinancialReportsWorkflow } from './weeklyFinancialReports'; +export { weeklyFinancialReportsWorkflow } from './financial/index'; diff --git a/workers/main/src/workflows/weeklyFinancialReports/index.ts b/workers/main/src/workflows/weeklyFinancialReports/index.ts deleted file mode 100644 index 9669aea..0000000 --- a/workers/main/src/workflows/weeklyFinancialReports/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { proxyActivities } from '@temporalio/workflow'; - -import type * as activities from '../../activities/weeklyFinancialReports'; - -const { getProjectUnits } = proxyActivities({ - startToCloseTimeout: '10 minutes', -}); - -export async function weeklyFinancialReportsWorkflow(): Promise { - try { - const reportTitle = 'Weekly Financial Report'; - const projectUnits = await getProjectUnits(); - - return `${reportTitle}\n${JSON.stringify(projectUnits, null, 2)}`; - } catch (error) { - console.error('Weekly Financial Reports', error); - throw error; - } -} From f8042e689fc33bd99460a563da2c389fdf5f80b4 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Fri, 30 May 2025 17:14:51 +0200 Subject: [PATCH 28/38] test(redmine): enhance tests for getProjectUnits and RedminePool - Updated the test for `getProjectUnits` in `redmine.test.ts` to use a spy on the service method, ensuring that the method is called correctly. - Refactored tests in `RedminePool.test.ts` to improve readability by formatting mock pool creation and ensuring consistent behavior across tests. - Enhanced assertions to verify the correct number of pool creations after the pool is ended, improving test reliability. These changes contribute to a more robust and maintainable test suite for Redmine functionalities, ensuring accurate validation of service interactions and connection pooling behavior. --- .../weeklyFinancialReports/redmine.test.ts | 7 ++++- workers/main/src/common/RedminePool.test.ts | 27 ++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts index fe63124..a92c2e4 100644 --- a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts @@ -34,9 +34,14 @@ describe('RedmineService', () => { getProjectUnits: vi.fn().mockResolvedValue(['unit']), } as unknown as RedmineRepository; const service = new RedmineService(repo); + + const serviceSpy = vi.spyOn(service, 'getProjectUnits'); + const result = await service.getProjectUnits(); expect(result).toEqual(['unit']); - expect(repo.getProjectUnits).toHaveBeenCalled(); + expect(serviceSpy).toHaveBeenCalled(); + + serviceSpy.mockRestore(); }); }); diff --git a/workers/main/src/common/RedminePool.test.ts b/workers/main/src/common/RedminePool.test.ts index f6c990b..854b5e0 100644 --- a/workers/main/src/common/RedminePool.test.ts +++ b/workers/main/src/common/RedminePool.test.ts @@ -26,33 +26,48 @@ describe('RedminePool', () => { }); it('creates pool on construction', () => { - (mysql.createPool as unknown as ReturnType).mockReturnValue(mockPool as Pool); + (mysql.createPool as unknown as ReturnType).mockReturnValue( + mockPool as Pool, + ); new RedminePool(credentials); expect(mysql.createPool).toHaveBeenCalledWith(credentials); }); it('getPool returns the pool', () => { - (mysql.createPool as unknown as ReturnType).mockReturnValue(mockPool as Pool); + (mysql.createPool as unknown as ReturnType).mockReturnValue( + mockPool as Pool, + ); const pool = new RedminePool(credentials); + expect(pool.getPool()).toBe(mockPool); }); it('endPool ends the pool and sets poolEnded', async () => { - (mysql.createPool as unknown as ReturnType).mockReturnValue(mockPool as Pool); + (mysql.createPool as unknown as ReturnType).mockReturnValue( + mockPool as Pool, + ); const pool = new RedminePool(credentials); + await pool.endPool(); expect(mockPool.end).toHaveBeenCalled(); // После завершения poolEnded = true, getPool создаёт новый пул - (mysql.createPool as unknown as ReturnType).mockReturnValue(mockPool as Pool); + (mysql.createPool as unknown as ReturnType).mockReturnValue( + mockPool as Pool, + ); pool.getPool(); expect(mysql.createPool).toHaveBeenCalledTimes(2); // первый раз в конструкторе, второй раз после endPool }); it('getPool recreates pool if ended', async () => { - (mysql.createPool as unknown as ReturnType).mockReturnValue(mockPool as Pool); + (mysql.createPool as unknown as ReturnType).mockReturnValue( + mockPool as Pool, + ); const pool = new RedminePool(credentials); + await pool.endPool(); - (mysql.createPool as unknown as ReturnType).mockReturnValue(mockPool as Pool); + (mysql.createPool as unknown as ReturnType).mockReturnValue( + mockPool as Pool, + ); pool.getPool(); expect(mysql.createPool).toHaveBeenCalledTimes(2); }); From 7329ae1367497cb17eca4a2d31fb553be1654639 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Fri, 30 May 2025 17:24:12 +0200 Subject: [PATCH 29/38] test(financial): add comprehensive tests for FinancialReportService and ProjectUnitRepository - Introduced new test files for `FinancialReportService` and `ProjectUnitRepository`, ensuring thorough coverage of their functionalities. - Added tests for `getWeeklyReport` in `FinancialReportService`, verifying correct data retrieval from the repository. - Implemented tests for `getProjectUnits` in `ProjectUnitRepository`, checking for expected behavior with both valid and empty responses. - Enhanced existing tests in `factory.test.ts` and `redmine.test.ts` to include additional cases for `getProjectUnits`, improving overall test reliability. These changes contribute to a more robust and maintainable test suite for financial services, ensuring accurate validation of data handling and service interactions. --- .../src/activities/financial/factory.test.ts | 12 +++++ .../getProjectUnits.activity.test.ts | 48 +++++++++++++++++ .../weeklyFinancialReports/redmine.test.ts | 40 ++++++++++++++ .../financial/ProjectUnitRepository.test.ts | 52 +++++++++++++++++++ .../financial/FinancialReportService.test.ts | 41 +++++++++++++++ 5 files changed, 193 insertions(+) create mode 100644 workers/main/src/activities/financial/getProjectUnits.activity.test.ts create mode 100644 workers/main/src/repositories/financial/ProjectUnitRepository.test.ts create mode 100644 workers/main/src/services/financial/FinancialReportService.test.ts diff --git a/workers/main/src/activities/financial/factory.test.ts b/workers/main/src/activities/financial/factory.test.ts index 51975f1..b362a6b 100644 --- a/workers/main/src/activities/financial/factory.test.ts +++ b/workers/main/src/activities/financial/factory.test.ts @@ -19,4 +19,16 @@ describe('factory', () => { expect(service).toBeInstanceOf(FinancialReportService); }); + + it('ProjectUnitRepository instance has getProjectUnits method', () => { + const repo = createProjectUnitRepository(); + + expect(typeof repo.getProjectUnits).toBe('function'); + }); + + it('FinancialReportService instance has getWeeklyReport method', () => { + const service = createFinancialReportService(); + + expect(typeof service.getWeeklyReport).toBe('function'); + }); }); diff --git a/workers/main/src/activities/financial/getProjectUnits.activity.test.ts b/workers/main/src/activities/financial/getProjectUnits.activity.test.ts new file mode 100644 index 0000000..36c643e --- /dev/null +++ b/workers/main/src/activities/financial/getProjectUnits.activity.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { FinancialReportService } from '../../services/financial/FinancialReportService'; +import * as factory from './factory'; +import { getProjectUnits } from './getProjectUnits.activity'; + +describe('getProjectUnits activity', () => { + it('returns weekly report from service', async () => { + const mockReport = [ + { group_id: 1, group_name: 'A', project_id: 2, project_name: 'B' }, + ]; + + const mockService: Partial = { + getWeeklyReport: vi.fn().mockResolvedValue(mockReport), + }; + + vi.spyOn(factory, 'createFinancialReportService').mockReturnValue( + mockService as FinancialReportService, + ); + const result = await getProjectUnits(); + + expect(result).toEqual(mockReport); + }); + + it('returns empty array if no data', async () => { + const mockService: Partial = { + getWeeklyReport: vi.fn().mockResolvedValue([]), + }; + + vi.spyOn(factory, 'createFinancialReportService').mockReturnValue( + mockService as FinancialReportService, + ); + const result = await getProjectUnits(); + + expect(result).toEqual([]); + }); + + it('throws if service fails', async () => { + const mockService: Partial = { + getWeeklyReport: vi.fn().mockRejectedValue(new Error('fail')), + }; + + vi.spyOn(factory, 'createFinancialReportService').mockReturnValue( + mockService as FinancialReportService, + ); + await expect(getProjectUnits()).rejects.toThrow('fail'); + }); +}); diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts index a92c2e4..2c719e9 100644 --- a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts @@ -45,3 +45,43 @@ describe('RedmineService', () => { serviceSpy.mockRestore(); }); }); + +describe('getProjectUnits additional cases', () => { + it('returns correct structure for project units', async () => { + const mockUnits = [ + { + group_id: 1, + group_name: 'Group A', + project_id: 10, + project_name: 'Project X', + }, + ]; + + vi.spyOn(RedmineService.prototype, 'getProjectUnits').mockResolvedValueOnce( + mockUnits, + ); + const result = await redmineModule.getProjectUnits(); + + expect(result).toEqual(mockUnits); + expect(result[0]).toHaveProperty('group_id'); + expect(result[0]).toHaveProperty('group_name'); + expect(result[0]).toHaveProperty('project_id'); + expect(result[0]).toHaveProperty('project_name'); + }); + + it('returns empty array if no units found', async () => { + vi.spyOn(RedmineService.prototype, 'getProjectUnits').mockResolvedValueOnce( + [], + ); + const result = await redmineModule.getProjectUnits(); + + expect(result).toEqual([]); + }); + + it('throws if RedmineService.getProjectUnits fails', async () => { + vi.spyOn(RedmineService.prototype, 'getProjectUnits').mockRejectedValueOnce( + new Error('DB error'), + ); + await expect(redmineModule.getProjectUnits()).rejects.toThrow('DB error'); + }); +}); diff --git a/workers/main/src/repositories/financial/ProjectUnitRepository.test.ts b/workers/main/src/repositories/financial/ProjectUnitRepository.test.ts new file mode 100644 index 0000000..94e7ffc --- /dev/null +++ b/workers/main/src/repositories/financial/ProjectUnitRepository.test.ts @@ -0,0 +1,52 @@ +import type { Pool } from 'mysql2/promise'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { RedminePool } from '../../common/RedminePool'; +import { ProjectUnit } from '../../common/types'; +import { ProjectUnitRepository } from './ProjectUnitRepository'; + +vi.mock('../../common/RedminePool'); + +const mockExecute = vi.fn(); + +beforeEach(() => { + vi.clearAllMocks(); + vi.spyOn(RedminePool.prototype, 'getPool').mockReturnValue({ + execute: mockExecute, + } as unknown as Pool); +}); + +describe('ProjectUnitRepository', () => { + it('returns project units from db', async () => { + const mockRows: ProjectUnit[] = [ + { + group_id: 1, + group_name: 'Group', + project_id: 2, + project_name: 'Project', + }, + ]; + + mockExecute.mockResolvedValueOnce([mockRows]); + const repo = new ProjectUnitRepository(); + const result = await repo.getProjectUnits(); + + expect(result).toEqual(mockRows); + expect(mockExecute).toHaveBeenCalled(); + }); + + it('returns empty array if no data', async () => { + mockExecute.mockResolvedValueOnce([[]]); + const repo = new ProjectUnitRepository(); + const result = await repo.getProjectUnits(); + + expect(result).toEqual([]); + }); + + it('throws if db fails', async () => { + mockExecute.mockRejectedValueOnce(new Error('fail')); + const repo = new ProjectUnitRepository(); + + await expect(repo.getProjectUnits()).rejects.toThrow('fail'); + }); +}); diff --git a/workers/main/src/services/financial/FinancialReportService.test.ts b/workers/main/src/services/financial/FinancialReportService.test.ts new file mode 100644 index 0000000..415e392 --- /dev/null +++ b/workers/main/src/services/financial/FinancialReportService.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { IProjectUnitRepository } from '../../repositories/financial/IProjectUnitRepository'; +import { FinancialReportService } from './FinancialReportService'; + +describe('FinancialReportService', () => { + it('returns weekly report from repo', async () => { + const repo: Partial = { + getProjectUnits: vi + .fn() + .mockResolvedValue([ + { group_id: 1, group_name: 'A', project_id: 2, project_name: 'B' }, + ]), + }; + const service = new FinancialReportService(repo as IProjectUnitRepository); + const result = await service.getWeeklyReport(); + + expect(result).toEqual([ + { group_id: 1, group_name: 'A', project_id: 2, project_name: 'B' }, + ]); + }); + + it('returns empty array if repo returns none', async () => { + const repo: Partial = { + getProjectUnits: vi.fn().mockResolvedValue([]), + }; + const service = new FinancialReportService(repo as IProjectUnitRepository); + const result = await service.getWeeklyReport(); + + expect(result).toEqual([]); + }); + + it('throws if repo fails', async () => { + const repo: Partial = { + getProjectUnits: vi.fn().mockRejectedValue(new Error('fail')), + }; + const service = new FinancialReportService(repo as IProjectUnitRepository); + + await expect(service.getWeeklyReport()).rejects.toThrow('fail'); + }); +}); From 5ae0d34177e17fe8d32e251887af0030951e4e99 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Fri, 30 May 2025 18:27:40 +0200 Subject: [PATCH 30/38] feat(financial): implement financial report formatting and enhance project unit retrieval - Added `FinancialReportFormatter` to format weekly financial reports based on project units. - Introduced tests for `formatFinancialReport` to ensure correct output structure and content. - Updated `weeklyFinancialReportsWorkflow` to utilize the new formatter, improving code clarity and maintainability. - Created a new test file for `RedmineRepository` to validate the behavior of `getProjectUnits` and ensure accurate data retrieval. These changes enhance the financial reporting capabilities and improve the overall structure of the financial module. --- .../financial/IProjectUnitRepository.ts | 2 +- .../redmine/RedmineRepository.test.ts | 53 +++++++++++++++++++ .../financial/FinancialReportFormatter.ts | 7 +++ .../weeklyFinancialReports.workflow.test.ts | 46 ++++++++++++++++ .../weeklyFinancialReports.workflow.ts | 4 +- 5 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 workers/main/src/services/redmine/RedmineRepository.test.ts create mode 100644 workers/main/src/workflows/financial/FinancialReportFormatter.ts create mode 100644 workers/main/src/workflows/financial/weeklyFinancialReports.workflow.test.ts diff --git a/workers/main/src/repositories/financial/IProjectUnitRepository.ts b/workers/main/src/repositories/financial/IProjectUnitRepository.ts index fee22ef..122f65c 100644 --- a/workers/main/src/repositories/financial/IProjectUnitRepository.ts +++ b/workers/main/src/repositories/financial/IProjectUnitRepository.ts @@ -1,4 +1,4 @@ -import type { ProjectUnit } from '../../../common/types'; +import { ProjectUnit } from '../../common/types'; export interface IProjectUnitRepository { getProjectUnits(): Promise; diff --git a/workers/main/src/services/redmine/RedmineRepository.test.ts b/workers/main/src/services/redmine/RedmineRepository.test.ts new file mode 100644 index 0000000..ad77ec6 --- /dev/null +++ b/workers/main/src/services/redmine/RedmineRepository.test.ts @@ -0,0 +1,53 @@ +import type { Pool } from 'mysql2/promise'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { RedminePool } from '../../common/RedminePool'; +import { RedmineRepository } from './RedmineRepository'; + +// Mock RedminePool and Pool +const mockExecute = vi.fn(); +const mockPool: Partial = { + execute: mockExecute, +}; + +const mockRedminePool: Partial = { + getPool: () => mockPool as Pool, +}; + +describe('RedmineRepository', () => { + let repo: RedmineRepository; + + beforeEach(() => { + vi.clearAllMocks(); + repo = new RedmineRepository(mockRedminePool as RedminePool); + }); + + it('getProjectUnitsQuery returns correct SQL', () => { + // @ts-expect-error: access private method for test + const query = repo.getProjectUnitsQuery(); + + expect(query).toContain('SELECT'); + expect(query).toContain('group_id'); + expect(query).toContain('SUM(total_hours) AS total_hours'); + expect(query).toContain('ORDER BY group_name ASC'); + }); + + it('getProjectUnits returns rows from pool', async () => { + const mockRows = [ + { group_id: 1, group_name: 'A', project_id: 2, project_name: 'B' }, + ]; + + mockExecute.mockResolvedValueOnce([mockRows]); + const result = await repo.getProjectUnits(); + + expect(result).toEqual(mockRows); + expect(mockExecute).toHaveBeenCalled(); + }); + + it('getProjectUnits returns empty array if no rows', async () => { + mockExecute.mockResolvedValueOnce([[]]); + const result = await repo.getProjectUnits(); + + expect(result).toEqual([]); + }); +}); diff --git a/workers/main/src/workflows/financial/FinancialReportFormatter.ts b/workers/main/src/workflows/financial/FinancialReportFormatter.ts new file mode 100644 index 0000000..7b933d9 --- /dev/null +++ b/workers/main/src/workflows/financial/FinancialReportFormatter.ts @@ -0,0 +1,7 @@ +import { ProjectUnit } from '../../common/types'; + +export function formatFinancialReport(units: ProjectUnit[]) { + const reportTitle = 'Weekly Financial Report'; + + return `${reportTitle}\n${JSON.stringify(units, null, 2)}`; +} diff --git a/workers/main/src/workflows/financial/weeklyFinancialReports.workflow.test.ts b/workers/main/src/workflows/financial/weeklyFinancialReports.workflow.test.ts new file mode 100644 index 0000000..8b99b5b --- /dev/null +++ b/workers/main/src/workflows/financial/weeklyFinancialReports.workflow.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from 'vitest'; +import { vi } from 'vitest'; + +import { formatFinancialReport } from './FinancialReportFormatter'; + +describe('formatFinancialReport', () => { + it('should format report with given project units', () => { + const result = formatFinancialReport([ + { + group_id: 1, + group_name: 'Engineering', + project_id: 101, + project_name: 'Project Alpha', + }, + ]); + + expect(result).toContain('Weekly Financial Report'); + expect(result).toContain('Engineering'); + expect(result).toContain('Project Alpha'); + }); +}); + +vi.mock('@temporalio/workflow', () => ({ + proxyActivities: () => ({ + getProjectUnits: vi.fn().mockResolvedValue([ + { + group_id: 1, + group_name: 'Engineering', + project_id: 101, + project_name: 'Project Alpha', + }, + ]), + }), +})); + +import { weeklyFinancialReportsWorkflow } from './weeklyFinancialReports.workflow'; + +describe('weeklyFinancialReportsWorkflow', () => { + it('should return formatted report from workflow', async () => { + const result = await weeklyFinancialReportsWorkflow(); + + expect(result).toContain('Weekly Financial Report'); + expect(result).toContain('Engineering'); + expect(result).toContain('Project Alpha'); + }); +}); diff --git a/workers/main/src/workflows/financial/weeklyFinancialReports.workflow.ts b/workers/main/src/workflows/financial/weeklyFinancialReports.workflow.ts index 0db94af..704b160 100644 --- a/workers/main/src/workflows/financial/weeklyFinancialReports.workflow.ts +++ b/workers/main/src/workflows/financial/weeklyFinancialReports.workflow.ts @@ -1,14 +1,14 @@ import { proxyActivities } from '@temporalio/workflow'; import type * as activities from '../../activities/financial'; +import { formatFinancialReport } from './FinancialReportFormatter'; const { getProjectUnits } = proxyActivities({ startToCloseTimeout: '10 minutes', }); export async function weeklyFinancialReportsWorkflow(): Promise { - const reportTitle = 'Weekly Financial Report'; const projectUnits = await getProjectUnits(); - return `${reportTitle}\n${JSON.stringify(projectUnits, null, 2)}`; + return formatFinancialReport(projectUnits); } From 4fff7903a2087b9e6132750055d030a013c69286 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Fri, 30 May 2025 20:03:39 +0200 Subject: [PATCH 31/38] test(financial): enhance factory tests for ProjectUnitRepository and FinancialReportService - Added comprehensive tests for `ProjectUnitRepository.getProjectUnits` to verify correct data retrieval, handling of empty responses, and error scenarios. - Introduced tests for `FinancialReportService.getWeeklyReport` to ensure accurate reporting of project units under various conditions. - Utilized mocking to simulate database interactions, improving test reliability and isolation. These changes strengthen the test coverage for financial services, ensuring robust validation of data handling and service interactions. --- .../src/activities/financial/factory.test.ts | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/workers/main/src/activities/financial/factory.test.ts b/workers/main/src/activities/financial/factory.test.ts index b362a6b..36d79df 100644 --- a/workers/main/src/activities/financial/factory.test.ts +++ b/workers/main/src/activities/financial/factory.test.ts @@ -1,5 +1,8 @@ +import type { Pool } from 'mysql2/promise'; import { describe, expect, it } from 'vitest'; +import { beforeEach, vi } from 'vitest'; +import { RedminePool } from '../../common/RedminePool'; import { ProjectUnitRepository } from '../../repositories/financial/ProjectUnitRepository'; import { FinancialReportService } from '../../services/financial/FinancialReportService'; import { @@ -7,6 +10,17 @@ import { createProjectUnitRepository, } from './factory'; +vi.mock('../../common/RedminePool'); + +const mockExecute = vi.fn(); + +beforeEach(() => { + vi.clearAllMocks(); + vi.spyOn(RedminePool.prototype, 'getPool').mockReturnValue({ + execute: mockExecute, + } as unknown as Pool); +}); + describe('factory', () => { it('createProjectUnitRepository returns ProjectUnitRepository instance', () => { const repo = createProjectUnitRepository(); @@ -32,3 +46,60 @@ describe('factory', () => { expect(typeof service.getWeeklyReport).toBe('function'); }); }); + +describe('factory (behavioral)', () => { + it('ProjectUnitRepository.getProjectUnits returns project units', async () => { + const mockRows = [ + { group_id: 1, group_name: 'A', project_id: 2, project_name: 'B' }, + ]; + + mockExecute.mockResolvedValueOnce([mockRows]); + const repo = createProjectUnitRepository(); + const result = await repo.getProjectUnits(); + + expect(result).toEqual(mockRows); + expect(mockExecute).toHaveBeenCalled(); + }); + + it('ProjectUnitRepository.getProjectUnits returns empty array if no data', async () => { + mockExecute.mockResolvedValueOnce([[]]); + const repo = createProjectUnitRepository(); + const result = await repo.getProjectUnits(); + + expect(result).toEqual([]); + }); + + it('ProjectUnitRepository.getProjectUnits throws if db fails', async () => { + mockExecute.mockRejectedValueOnce(new Error('fail')); + const repo = createProjectUnitRepository(); + + await expect(repo.getProjectUnits()).rejects.toThrow('fail'); + }); + + it('FinancialReportService.getWeeklyReport returns project units', async () => { + const mockRows = [ + { group_id: 1, group_name: 'A', project_id: 2, project_name: 'B' }, + ]; + + mockExecute.mockResolvedValueOnce([mockRows]); + const service = createFinancialReportService(); + const result = await service.getWeeklyReport(); + + expect(result).toEqual(mockRows); + }); + + it('FinancialReportService.getWeeklyReport returns empty array if no data', async () => { + mockExecute.mockResolvedValueOnce([[]]); + const service = createFinancialReportService(); + const result = await service.getWeeklyReport(); + + expect(result).toEqual([]); + }); + + it('FinancialReportService.getWeeklyReport throws if repo fails', async () => { + mockExecute.mockRejectedValueOnce(new Error('fail')); + const service = createFinancialReportService(); + + await expect(service.getWeeklyReport()).rejects.toThrow('fail'); + }); +}); From 18bddcf59dac6b544b13e6c225a9f7c7430fa6da Mon Sep 17 00:00:00 2001 From: anatolyshipitz <30830673+anatolyshipitz@users.noreply.github.com> Date: Fri, 30 May 2025 20:05:23 +0200 Subject: [PATCH 32/38] Update workers/main/src/services/financial/FinancialReportService.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- workers/main/src/services/financial/FinancialReportService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workers/main/src/services/financial/FinancialReportService.ts b/workers/main/src/services/financial/FinancialReportService.ts index f2087c5..ad22f29 100644 --- a/workers/main/src/services/financial/FinancialReportService.ts +++ b/workers/main/src/services/financial/FinancialReportService.ts @@ -2,7 +2,7 @@ import { IProjectUnitRepository } from '../../repositories/financial/IProjectUni export class FinancialReportService { constructor(private repo: IProjectUnitRepository) {} - async getWeeklyReport() { + async getWeeklyReport(): Promise { return this.repo.getProjectUnits(); } } From f6333addb798db51ababa090d5b3552adf470501 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Fri, 30 May 2025 20:12:38 +0200 Subject: [PATCH 33/38] fix(redmine): enhance error handling in getProjectUnits method - Added error handling in the `getProjectUnits` method of `RedmineRepository` to ensure that the method correctly throws an error if the query result is not an array. - Improved the overall reliability of the method by catching potential errors during database execution and providing a clear error message. These changes enhance the robustness of the data retrieval process in the Redmine functionalities, ensuring better error management and user feedback. --- .../services/financial/FinancialReportService.ts | 1 + .../src/services/redmine/RedmineRepository.ts | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/workers/main/src/services/financial/FinancialReportService.ts b/workers/main/src/services/financial/FinancialReportService.ts index ad22f29..7f0246a 100644 --- a/workers/main/src/services/financial/FinancialReportService.ts +++ b/workers/main/src/services/financial/FinancialReportService.ts @@ -1,3 +1,4 @@ +import type { ProjectUnit } from '../../common/types'; import { IProjectUnitRepository } from '../../repositories/financial/IProjectUnitRepository'; export class FinancialReportService { diff --git a/workers/main/src/services/redmine/RedmineRepository.ts b/workers/main/src/services/redmine/RedmineRepository.ts index 36dc5f8..e405cf1 100644 --- a/workers/main/src/services/redmine/RedmineRepository.ts +++ b/workers/main/src/services/redmine/RedmineRepository.ts @@ -44,8 +44,19 @@ ORDER BY group_name ASC, project_name ASC, username ASC, spent_on ASC`; async getProjectUnits(): Promise { const query = this.getProjectUnitsQuery(); - const [rows] = await this.pool.execute(query); - return rows as ProjectUnit[]; + try { + const [rows] = await this.pool.execute(query); + + if (!Array.isArray(rows)) { + throw new Error('Query did not return an array'); + } + + return rows as ProjectUnit[]; + } catch (error) { + const err = error as Error; + + throw new Error(`Failed to fetch project units: ${err.message}`); + } } } From f9af48a21c0568782d7c5cf216bd7824d008c417 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Fri, 30 May 2025 20:16:12 +0200 Subject: [PATCH 34/38] refactor(redmine): improve data mapping in getProjectUnits method - Updated the `getProjectUnits` method in `RedmineRepository` to enhance data mapping by converting database rows into a more structured format. - Each row is now transformed into an object with explicit types for `group_id`, `group_name`, `project_id`, and `project_name`, improving data integrity and usability. These changes contribute to a clearer and more reliable data retrieval process within the Redmine functionalities. --- workers/main/src/services/redmine/RedmineRepository.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/workers/main/src/services/redmine/RedmineRepository.ts b/workers/main/src/services/redmine/RedmineRepository.ts index e405cf1..d055ed8 100644 --- a/workers/main/src/services/redmine/RedmineRepository.ts +++ b/workers/main/src/services/redmine/RedmineRepository.ts @@ -52,7 +52,12 @@ ORDER BY group_name ASC, project_name ASC, username ASC, spent_on ASC`; throw new Error('Query did not return an array'); } - return rows as ProjectUnit[]; + return (rows as Record[]).map((row) => ({ + group_id: Number(row.group_id), + group_name: String(row.group_name), + project_id: Number(row.project_id), + project_name: String(row.project_name), + })); } catch (error) { const err = error as Error; From 26d36000f8519cf5e02116731eb1bdc4b78c1121 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Fri, 30 May 2025 20:36:55 +0200 Subject: [PATCH 35/38] feat(redmine): extend ProjectUnit interface and enhance database query handling - Added new properties `user_id`, `username`, `spent_on`, and `total_hours` to the `ProjectUnit` interface in `types.ts` to improve data representation. - Refactored the `RedmineRepository` to utilize a new `IPoolProvider` interface, enhancing the flexibility of database interactions. - Updated the SQL query in `getProjectUnits` to include the new fields, ensuring comprehensive data retrieval from the database. - Modified tests in `RedmineRepository.test.ts` to validate the new structure and ensure correct handling of the extended data model. These changes improve the data structure and retrieval process within the Redmine functionalities, enhancing overall usability and maintainability. --- workers/main/src/common/types.ts | 4 ++ .../redmine/RedmineRepository.test.ts | 60 ++++++++++++------- .../src/services/redmine/RedmineRepository.ts | 49 +++++++++------ 3 files changed, 74 insertions(+), 39 deletions(-) diff --git a/workers/main/src/common/types.ts b/workers/main/src/common/types.ts index 97df49c..9fbdb1c 100644 --- a/workers/main/src/common/types.ts +++ b/workers/main/src/common/types.ts @@ -3,4 +3,8 @@ export interface ProjectUnit { group_name: string; project_id: number; project_name: string; + user_id: number; + username: string; + spent_on: string; + total_hours: number; } diff --git a/workers/main/src/services/redmine/RedmineRepository.test.ts b/workers/main/src/services/redmine/RedmineRepository.test.ts index ad77ec6..981a053 100644 --- a/workers/main/src/services/redmine/RedmineRepository.test.ts +++ b/workers/main/src/services/redmine/RedmineRepository.test.ts @@ -1,16 +1,15 @@ import type { Pool } from 'mysql2/promise'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { RedminePool } from '../../common/RedminePool'; -import { RedmineRepository } from './RedmineRepository'; +import { IPoolProvider, RedmineRepository } from './RedmineRepository'; -// Mock RedminePool and Pool -const mockExecute = vi.fn(); +// Mock Pool and IPoolProvider +const mockQuery = vi.fn(); const mockPool: Partial = { - execute: mockExecute, + query: mockQuery, }; -const mockRedminePool: Partial = { +const mockPoolProvider: IPoolProvider = { getPool: () => mockPool as Pool, }; @@ -19,35 +18,52 @@ describe('RedmineRepository', () => { beforeEach(() => { vi.clearAllMocks(); - repo = new RedmineRepository(mockRedminePool as RedminePool); - }); - - it('getProjectUnitsQuery returns correct SQL', () => { - // @ts-expect-error: access private method for test - const query = repo.getProjectUnitsQuery(); - - expect(query).toContain('SELECT'); - expect(query).toContain('group_id'); - expect(query).toContain('SUM(total_hours) AS total_hours'); - expect(query).toContain('ORDER BY group_name ASC'); + repo = new RedmineRepository(mockPoolProvider); }); it('getProjectUnits returns rows from pool', async () => { const mockRows = [ - { group_id: 1, group_name: 'A', project_id: 2, project_name: 'B' }, + { + group_id: 1, + group_name: 'A', + project_id: 2, + project_name: 'B', + user_id: 3, + username: 'User X', + spent_on: '2024-06-01', + total_hours: 5, + }, ]; - mockExecute.mockResolvedValueOnce([mockRows]); + mockQuery.mockResolvedValueOnce([mockRows]); const result = await repo.getProjectUnits(); - expect(result).toEqual(mockRows); - expect(mockExecute).toHaveBeenCalled(); + expect(result).toEqual([ + { + group_id: 1, + group_name: 'A', + project_id: 2, + project_name: 'B', + user_id: 3, + username: 'User X', + spent_on: '2024-06-01', + total_hours: 5, + }, + ]); + expect(mockQuery).toHaveBeenCalled(); }); it('getProjectUnits returns empty array if no rows', async () => { - mockExecute.mockResolvedValueOnce([[]]); + mockQuery.mockResolvedValueOnce([[]]); const result = await repo.getProjectUnits(); expect(result).toEqual([]); }); + + it('getProjectUnits throws error if query fails', async () => { + mockQuery.mockRejectedValueOnce(new Error('DB error')); + await expect(repo.getProjectUnits()).rejects.toThrow( + 'RedmineRepository.getProjectUnits failed: DB error', + ); + }); }); diff --git a/workers/main/src/services/redmine/RedmineRepository.ts b/workers/main/src/services/redmine/RedmineRepository.ts index d055ed8..05f17a3 100644 --- a/workers/main/src/services/redmine/RedmineRepository.ts +++ b/workers/main/src/services/redmine/RedmineRepository.ts @@ -1,18 +1,20 @@ -import { Pool } from 'mysql2/promise'; +import { Pool, RowDataPacket } from 'mysql2/promise'; -import { RedminePool } from '../../common/RedminePool'; import { ProjectUnit } from '../../common/types'; import { IRedmineRepository } from './IRedmineRepository'; -export class RedmineRepository implements IRedmineRepository { - private pool: Pool; - - constructor(redminePool: RedminePool) { - this.pool = redminePool.getPool(); - } +interface ProjectUnitRow extends RowDataPacket { + group_id: number; + group_name: string; + project_id: number; + project_name: string; + user_id: number; + username: string; + spent_on: string; + total_hours: number; +} - private getProjectUnitsQuery() { - return `SELECT +const PROJECT_UNITS_QUERY = `SELECT group_id, group_name, project_id, @@ -40,28 +42,41 @@ FROM ( ) t GROUP BY group_id, group_name, project_id, project_name, user_id, username, spent_on ORDER BY group_name ASC, project_name ASC, username ASC, spent_on ASC`; + +export interface IPoolProvider { + getPool(): Pool; +} + +export class RedmineRepository implements IRedmineRepository { + private readonly pool: Pool; + + constructor(poolProvider: IPoolProvider) { + this.pool = poolProvider.getPool(); } async getProjectUnits(): Promise { - const query = this.getProjectUnitsQuery(); - try { - const [rows] = await this.pool.execute(query); + const [rows] = + await this.pool.query(PROJECT_UNITS_QUERY); if (!Array.isArray(rows)) { throw new Error('Query did not return an array'); } - return (rows as Record[]).map((row) => ({ + return rows.map((row) => ({ group_id: Number(row.group_id), group_name: String(row.group_name), project_id: Number(row.project_id), project_name: String(row.project_name), + user_id: Number(row.user_id), + username: String(row.username), + spent_on: String(row.spent_on), + total_hours: Number(row.total_hours), })); } catch (error) { - const err = error as Error; - - throw new Error(`Failed to fetch project units: ${err.message}`); + throw new Error( + `RedmineRepository.getProjectUnits failed: ${(error as Error).message}`, + ); } } } From 5505684fff3396ab175439de2eb5fc33b666bbcc Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Fri, 30 May 2025 20:48:00 +0200 Subject: [PATCH 36/38] test(redmine): enhance tests for getProjectUnits with additional properties - Updated the test cases in `redmine.test.ts` to include new properties `user_id`, `username`, `spent_on`, and `total_hours` for the `getProjectUnits` method. - Added assertions to verify the presence of these properties in the returned results, ensuring comprehensive validation of the extended data model. These changes improve the test coverage for the `getProjectUnits` method, ensuring accurate validation of the new data structure and enhancing overall test reliability. --- .../src/activities/weeklyFinancialReports/redmine.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts index 2c719e9..8216a70 100644 --- a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts @@ -54,6 +54,10 @@ describe('getProjectUnits additional cases', () => { group_name: 'Group A', project_id: 10, project_name: 'Project X', + user_id: 100, + username: 'John Doe', + spent_on: '2024-06-01', + total_hours: 8, }, ]; @@ -67,6 +71,10 @@ describe('getProjectUnits additional cases', () => { expect(result[0]).toHaveProperty('group_name'); expect(result[0]).toHaveProperty('project_id'); expect(result[0]).toHaveProperty('project_name'); + expect(result[0]).toHaveProperty('user_id'); + expect(result[0]).toHaveProperty('username'); + expect(result[0]).toHaveProperty('spent_on'); + expect(result[0]).toHaveProperty('total_hours'); }); it('returns empty array if no units found', async () => { From 1784a5c5b2c1633742555f42a17ae4fa5552db87 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Fri, 30 May 2025 20:50:15 +0200 Subject: [PATCH 37/38] test(redmine): update getProjectUnits test to improve validation - Refactored the test for `getProjectUnits` in `redmine.test.ts` to use a spy on the `RedmineService` method, ensuring the method is called correctly. - Added an assertion to verify the returned result is an empty array, enhancing the test's reliability and coverage. These changes improve the accuracy of the test for the `getProjectUnits` method, ensuring it correctly validates the expected behavior of the service interaction. --- .../weeklyFinancialReports/redmine.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts index 8216a70..3bafc7e 100644 --- a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts @@ -6,13 +6,15 @@ import * as redmineModule from './redmine'; describe('getProjectUnits', () => { it('calls redmineRepo.getProjectUnits', async () => { - const spy = vi - .spyOn(redmineModule, 'getProjectUnits') + const serviceSpy = vi + .spyOn(RedmineService.prototype, 'getProjectUnits') .mockResolvedValue([]); - await redmineModule.getProjectUnits(); - expect(spy).toHaveBeenCalled(); - spy.mockRestore(); + const result = await redmineModule.getProjectUnits(); + + expect(serviceSpy).toHaveBeenCalled(); + expect(result).toEqual([]); + serviceSpy.mockRestore(); }); }); From eb641c6b0ba2eafc7e69c737e47369537ab44887 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Fri, 30 May 2025 21:14:51 +0200 Subject: [PATCH 38/38] refactor(redmine): streamline Redmine service and testing structure - Removed the `redmine.types.ts` file as it is no longer needed, simplifying the codebase. - Refactored the `getProjectUnits` function to accept a `RedmineService` instance, enhancing flexibility in testing and usage. - Introduced a new `mocks/redmine.ts` file to provide mock implementations for testing, improving test isolation and reliability. - Updated tests in `redmine.test.ts` to utilize the new mocking structure, ensuring accurate validation of the `getProjectUnits` method. These changes enhance the maintainability and clarity of the Redmine functionalities, while also improving the testing framework. --- .../weeklyFinancialReports/index.ts | 1 - .../weeklyFinancialReports/mocks/redmine.ts | 31 +++++ .../weeklyFinancialReports/redmine.test.ts | 109 ++++++++---------- .../weeklyFinancialReports/redmine.ts | 10 +- .../weeklyFinancialReports/redmine.types.ts | 11 -- .../src/services/redmine/RedmineService.ts | 3 +- 6 files changed, 85 insertions(+), 80 deletions(-) create mode 100644 workers/main/src/activities/weeklyFinancialReports/mocks/redmine.ts delete mode 100644 workers/main/src/activities/weeklyFinancialReports/redmine.types.ts diff --git a/workers/main/src/activities/weeklyFinancialReports/index.ts b/workers/main/src/activities/weeklyFinancialReports/index.ts index 8c1f12b..aca17e3 100644 --- a/workers/main/src/activities/weeklyFinancialReports/index.ts +++ b/workers/main/src/activities/weeklyFinancialReports/index.ts @@ -1,2 +1 @@ export * from './redmine'; -export * from './redmine.types'; diff --git a/workers/main/src/activities/weeklyFinancialReports/mocks/redmine.ts b/workers/main/src/activities/weeklyFinancialReports/mocks/redmine.ts new file mode 100644 index 0000000..4756c48 --- /dev/null +++ b/workers/main/src/activities/weeklyFinancialReports/mocks/redmine.ts @@ -0,0 +1,31 @@ +import { vi } from 'vitest'; + +import type { ProjectUnit } from '../../../common/types'; +import type { RedmineRepository } from '../../../services/redmine/RedmineRepository'; + +export function createMockProjectUnit( + overrides: Partial = {}, +): ProjectUnit { + return { + group_id: 1, + group_name: 'Group A', + project_id: 10, + project_name: 'Project X', + user_id: 100, + username: 'John Doe', + spent_on: '2024-06-01', + total_hours: 8, + ...overrides, + }; +} + +export function createMockRedmineRepository( + units: ProjectUnit[] = [], + error?: Error, +): RedmineRepository { + return { + getProjectUnits: error + ? vi.fn().mockRejectedValue(error) + : vi.fn().mockResolvedValue(units), + } as unknown as RedmineRepository; +} diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts index 3bafc7e..8df1623 100644 --- a/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.test.ts @@ -1,72 +1,47 @@ -import { describe, expect, it, vi } from 'vitest'; - -import { RedmineRepository } from '../../services/redmine/RedmineRepository'; +import { + afterEach, + beforeEach, + describe, + expect, + it, + type Mock, + vi, +} from 'vitest'; + +import type { ProjectUnit } from '../../common/types'; import { RedmineService } from '../../services/redmine/RedmineService'; -import * as redmineModule from './redmine'; +import { + createMockProjectUnit, + createMockRedmineRepository, +} from './mocks/redmine'; +import { createRedmineService, getProjectUnits } from './redmine'; describe('getProjectUnits', () => { - it('calls redmineRepo.getProjectUnits', async () => { - const serviceSpy = vi - .spyOn(RedmineService.prototype, 'getProjectUnits') - .mockResolvedValue([]); - - const result = await redmineModule.getProjectUnits(); + let service: RedmineService; + let repo: ReturnType; - expect(serviceSpy).toHaveBeenCalled(); - expect(result).toEqual([]); - serviceSpy.mockRestore(); + beforeEach(() => { + repo = createMockRedmineRepository(); + service = new RedmineService(repo); }); -}); -describe('getProjectUnits activity', () => { - it('calls RedmineService.getProjectUnits', async () => { - const serviceSpy = vi - .spyOn(RedmineService.prototype, 'getProjectUnits') - .mockResolvedValue([]); - - await redmineModule.getProjectUnits(); - expect(serviceSpy).toHaveBeenCalled(); - serviceSpy.mockRestore(); + afterEach(() => { + vi.restoreAllMocks(); }); -}); - -describe('RedmineService', () => { - it('delegates to repository', async () => { - const repo = { - getProjectUnits: vi.fn().mockResolvedValue(['unit']), - } as unknown as RedmineRepository; - const service = new RedmineService(repo); - - const serviceSpy = vi.spyOn(service, 'getProjectUnits'); - const result = await service.getProjectUnits(); - - expect(result).toEqual(['unit']); - expect(serviceSpy).toHaveBeenCalled(); + it('calls RedmineService.getProjectUnits', async () => { + const spy = vi.spyOn(service, 'getProjectUnits' as const); + const result = await getProjectUnits(service); - serviceSpy.mockRestore(); + expect(spy).toHaveBeenCalled(); + expect(result).toEqual([]); }); -}); -describe('getProjectUnits additional cases', () => { it('returns correct structure for project units', async () => { - const mockUnits = [ - { - group_id: 1, - group_name: 'Group A', - project_id: 10, - project_name: 'Project X', - user_id: 100, - username: 'John Doe', - spent_on: '2024-06-01', - total_hours: 8, - }, - ]; + const mockUnits: ProjectUnit[] = [createMockProjectUnit()]; - vi.spyOn(RedmineService.prototype, 'getProjectUnits').mockResolvedValueOnce( - mockUnits, - ); - const result = await redmineModule.getProjectUnits(); + (repo.getProjectUnits as Mock).mockResolvedValueOnce(mockUnits); + const result = await getProjectUnits(service); expect(result).toEqual(mockUnits); expect(result[0]).toHaveProperty('group_id'); @@ -80,18 +55,24 @@ describe('getProjectUnits additional cases', () => { }); it('returns empty array if no units found', async () => { - vi.spyOn(RedmineService.prototype, 'getProjectUnits').mockResolvedValueOnce( - [], - ); - const result = await redmineModule.getProjectUnits(); + (repo.getProjectUnits as Mock).mockResolvedValueOnce([]); + const result = await getProjectUnits(service); expect(result).toEqual([]); }); it('throws if RedmineService.getProjectUnits fails', async () => { - vi.spyOn(RedmineService.prototype, 'getProjectUnits').mockRejectedValueOnce( - new Error('DB error'), - ); - await expect(redmineModule.getProjectUnits()).rejects.toThrow('DB error'); + const error = new Error('DB error'); + + (repo.getProjectUnits as Mock).mockRejectedValueOnce(error); + await expect(getProjectUnits(service)).rejects.toThrow('DB error'); + }); +}); + +describe('createRedmineService', () => { + it('creates a RedmineService instance', () => { + const service = createRedmineService(); + + expect(service).toBeInstanceOf(RedmineService); }); }); diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.ts index b99dee3..97a30b8 100644 --- a/workers/main/src/activities/weeklyFinancialReports/redmine.ts +++ b/workers/main/src/activities/weeklyFinancialReports/redmine.ts @@ -3,7 +3,11 @@ import { redmineDatabaseConfig } from '../../configs/redmineDatabase'; import { RedmineRepository } from '../../services/redmine/RedmineRepository'; import { RedmineService } from '../../services/redmine/RedmineService'; -const repo = new RedmineRepository(new RedminePool(redmineDatabaseConfig)); -const service = new RedmineService(repo); +export function createRedmineService() { + const repo = new RedmineRepository(new RedminePool(redmineDatabaseConfig)); -export const getProjectUnits = async () => service.getProjectUnits(); + return new RedmineService(repo); +} + +export const getProjectUnits = async (service: RedmineService) => + service.getProjectUnits(); diff --git a/workers/main/src/activities/weeklyFinancialReports/redmine.types.ts b/workers/main/src/activities/weeklyFinancialReports/redmine.types.ts deleted file mode 100644 index cef72aa..0000000 --- a/workers/main/src/activities/weeklyFinancialReports/redmine.types.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface FinancialData { - period: string; - contractType: string; - revenue: number; - cogs: number; - margin: number; - marginality: number; - effectiveRevenue: number; - effectiveMargin: number; - effectiveMarginality: number; -} diff --git a/workers/main/src/services/redmine/RedmineService.ts b/workers/main/src/services/redmine/RedmineService.ts index 3b8f1d2..7c693b7 100644 --- a/workers/main/src/services/redmine/RedmineService.ts +++ b/workers/main/src/services/redmine/RedmineService.ts @@ -1,9 +1,10 @@ +import type { ProjectUnit } from '../../common/types'; import { IRedmineRepository } from './IRedmineRepository'; export class RedmineService { constructor(private repo: IRedmineRepository) {} - async getProjectUnits() { + async getProjectUnits(): Promise { return this.repo.getProjectUnits(); } }