From ba5111fa61d841a20eded19188031d9ebd82ef1d Mon Sep 17 00:00:00 2001 From: Ute Weiss Date: Wed, 25 Aug 2021 08:53:41 +0200 Subject: [PATCH 01/13] refactor: remove typeorm - WIP Signed-off-by: Ute Weiss --- package-lock.json | 438 ++++++------------ package.json | 7 +- src/connect.ts | 45 +- src/index.ts | 1 - src/isConnected.ts | 14 - src/job/check.ts | 2 - src/job/clear.ts | 4 +- src/job/define.ts | 6 +- src/job/keepLatest.ts | 2 +- src/job/list.ts | 2 - src/repository/ExecutionsEntity.ts | 27 +- src/repository/ExecutionsRepository.ts | 25 +- src/repository/JobEntity.ts | 44 +- src/repository/JobRepository.ts | 12 +- src/repository/Repository.ts | 34 ++ src/repository/getRepository.ts | 16 +- src/schedule/Schedule.ts | 4 +- src/schedule/SchedulePing.ts | 2 +- test/connect.integration.spec.ts | 8 +- test/isConnected.spec.ts | 15 - test/job/clear.integration.spec.ts | 2 +- test/job/define.spec.ts | 2 +- test/job/findLatest.spec.ts | 3 +- test/job/keepLatest.spec.ts | 12 +- .../ExecutionsRepository.integration.spec.ts | 2 +- .../JobRepository.integration.spec.ts | 2 +- test/schedule/MongoSchedule.spec.ts | 2 +- .../MongoScheduleBuilder.integration.spec.ts | 2 +- test/schedule/SchedulePing.spec.ts | 2 +- test/schedule/momo.integration.spec.ts | 10 +- test/utils/createJobEntity.ts | 3 +- test/utils/mockRepositories.ts | 26 +- 32 files changed, 300 insertions(+), 476 deletions(-) create mode 100644 src/repository/Repository.ts delete mode 100644 test/isConnected.spec.ts diff --git a/package-lock.json b/package-lock.json index f5a3c5e2..f2a02d28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1049,11 +1049,6 @@ "@sinonjs/commons": "^1.7.0" } }, - "@sqltools/formatter": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.2.tgz", - "integrity": "sha512-/5O7Fq6Vnv8L6ucmPjaWbVG1XkP4FO+w5glqfkIsq3Xw4oyNAdJddbnYodNDAfjVUvo/rrSCTom4kAND7T1o5Q==" - }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -1125,15 +1120,6 @@ "@babel/types": "^7.3.0" } }, - "@types/bson": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.4.tgz", - "integrity": "sha512-awqorHvQS0DqxkHQ/FxcPX9E+H7Du51Qw/2F+5TBMSaE3G0hm+8D3eXJ6MAzFw75nE8V7xF0QvzUSdxIjJb/GA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -1210,21 +1196,10 @@ "@types/unist": "*" } }, - "@types/mongodb": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", - "integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==", - "dev": true, - "requires": { - "@types/bson": "*", - "@types/node": "*" - } - }, "@types/node": { "version": "16.3.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.3.2.tgz", - "integrity": "sha512-jJs9ErFLP403I+hMLGnqDRWT0RYKSvArxuBVh2veudHV7ifEC1WAmjJADacZ7mRbA2nWgHtn8xyECMAot0SkAw==", - "dev": true + "integrity": "sha512-jJs9ErFLP403I+hMLGnqDRWT0RYKSvArxuBVh2veudHV7ifEC1WAmjJADacZ7mRbA2nWgHtn8xyECMAot0SkAw==" }, "@types/pino": { "version": "6.3.9", @@ -1295,6 +1270,20 @@ "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", "dev": true }, + "@types/webidl-conversions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", + "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" + }, + "@types/whatwg-url": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", + "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==", + "requires": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "@types/yargs": { "version": "15.0.14", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", @@ -1583,21 +1572,18 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true }, "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, "requires": { "color-convert": "^2.0.1" } }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" - }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -1608,11 +1594,6 @@ "picomatch": "^2.0.4" } }, - "app-root-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", - "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==" - }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -1623,6 +1604,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -1790,7 +1772,8 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "base64-js": { "version": "1.5.1", @@ -1801,6 +1784,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "dev": true, "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -1810,6 +1794,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1862,9 +1847,12 @@ } }, "bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.4.1.tgz", + "integrity": "sha512-Uu4OCZa0jouQJCKOk1EmmyqtdWAP5HVLru4lQxTwzJzxT+sJ13lVpEZU/MATDxtHiekWMAL84oQY3Xn1LpJVSg==", + "requires": { + "buffer": "^5.6.0" + } }, "buffer": { "version": "5.7.1", @@ -1919,6 +1907,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1960,23 +1949,11 @@ "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "dev": true }, - "cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "requires": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - } - }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -1999,6 +1976,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -2006,7 +1984,8 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "colorette": { "version": "1.2.2", @@ -2038,7 +2017,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "convert-source-map": { "version": "1.8.0", @@ -2060,7 +2040,8 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "create-require": { "version": "1.1.1", @@ -2117,6 +2098,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { "ms": "2.1.2" } @@ -2218,11 +2200,6 @@ } } }, - "dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" - }, "electron-to-chromium": { "version": "1.3.778", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.778.tgz", @@ -2238,7 +2215,8 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "end-of-stream": { "version": "1.4.4", @@ -2305,12 +2283,14 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, "escodegen": { "version": "2.0.0", @@ -2653,7 +2633,8 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true }, "esquery": { "version": "1.4.0", @@ -2851,11 +2832,6 @@ "pend": "~1.2.0" } }, - "figlet": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.0.tgz", - "integrity": "sha512-ZQJM4aifMpz6H19AW1VqvZ7l4pOE9p7i/3LyxgO2kp+PO/VcDYNqIHEMtkccqIhTXMKci4kjueJr/iCQEaT/Ww==" - }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -2996,7 +2972,8 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "fsevents": { "version": "2.3.2", @@ -3026,7 +3003,8 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true }, "get-intrinsic": { "version": "1.1.1", @@ -3061,6 +3039,7 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3117,21 +3096,6 @@ "function-bind": "^1.1.1" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - } - } - }, "has-bigints": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", @@ -3141,7 +3105,8 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "has-symbols": { "version": "1.0.2", @@ -3149,11 +3114,6 @@ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "dev": true }, - "highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==" - }, "hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -3320,6 +3280,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -3328,7 +3289,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "is-alphabetical": { "version": "1.0.4", @@ -3412,7 +3374,8 @@ "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "is-generator-fn": { "version": "2.1.0", @@ -3499,7 +3462,8 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "isexe": { "version": "2.0.0", @@ -4808,6 +4772,7 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -5102,6 +5067,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5115,21 +5081,29 @@ "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true }, "mongodb": { - "version": "3.6.10", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.10.tgz", - "integrity": "sha512-fvIBQBF7KwCJnDZUnFFy4WqEFP8ibdXeFANnylW19+vOwdjOAvqIzPdsNCEMT6VKTHnYu4K64AWRih0mkFms6Q==", - "requires": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.0.3", - "safe-buffer": "^5.1.2", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.0.tgz", + "integrity": "sha512-Gx9U9MsFWgJ3E0v4oHAdWvYTGBznNYPCkhmD/3i/kPTY/URnPfHD5/6VoKUFrdgQTK3icFiM9976hVbqCRBO9Q==", + "requires": { + "bson": "^4.4.0", + "denque": "^1.5.0", + "mongodb-connection-string-url": "^1.0.1", "saslprep": "^1.0.0" } }, + "mongodb-connection-string-url": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-1.1.2.tgz", + "integrity": "sha512-mp5lv4guWuykOpkwNNqQ0tKKytuJUjL/aC/bu/DqoJVWL5NSh4j/u+gJ+EiOdweLujHyq6JZZqcTVipHhL5xRg==", + "requires": { + "@types/whatwg-url": "^8.0.0", + "whatwg-url": "^8.4.0" + } + }, "mongodb-memory-server": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-7.2.1.tgz", @@ -5173,12 +5147,32 @@ "yauzl": "^2.10.0" }, "dependencies": { + "bson": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", + "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", + "dev": true + }, "camelcase": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", "dev": true }, + "mongodb": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.11.tgz", + "integrity": "sha512-4Y4lTFHDHZZdgMaHmojtNAlqkvddX2QQBEN0K//GzxhGwlI9tZ9R0vhbjr1Decw+TF7qK0ZLjQT292XgHRRQgw==", + "dev": true, + "requires": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "optional-require": "^1.0.3", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + }, "tslib": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", @@ -5190,17 +5184,8 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "requires": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "natural-compare": { "version": "1.4.0", @@ -5299,11 +5284,6 @@ "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", "dev": true }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, "object-inspect": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", @@ -5343,6 +5323,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -5357,9 +5338,13 @@ } }, "optional-require": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", - "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==" + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.7.tgz", + "integrity": "sha512-cIeRZocXsZnZYn+SevbtSqNlLbeoS4mLzuNn4fvXRMDRNhTGg0sxuKXl0FnZCtnew85LorNxIbZp5OeliILhMw==", + "dev": true, + "requires": { + "require-at": "^1.0.6" + } }, "optionator": { "version": "0.9.1", @@ -5414,11 +5399,6 @@ "callsites": "^3.0.0" } }, - "parent-require": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz", - "integrity": "sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=" - }, "parse-entities": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", @@ -5443,26 +5423,6 @@ "json-parse-better-errors": "^1.0.1" } }, - "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" - }, - "parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "requires": { - "parse5": "^6.0.1" - }, - "dependencies": { - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - } - } - }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -5472,7 +5432,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-key": { "version": "3.1.1", @@ -5593,7 +5554,8 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true }, "progress": { "version": "2.0.3", @@ -5620,8 +5582,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "queue-microtask": { "version": "1.2.3", @@ -5677,6 +5638,7 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -5690,15 +5652,11 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true } } }, - "reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" - }, "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -5711,10 +5669,17 @@ "integrity": "sha512-k519uI04Z3SaY0fLX843MRXnDeG2+vHOFsyhiPZvNLe7r8rD2YNRjq4BQLZZ0oAr2NrtvZlICsXysGNFPGa3CQ==", "dev": true }, + "require-at": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", + "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", + "dev": true + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true }, "require-from-string": { "version": "2.0.2", @@ -5782,7 +5747,8 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -5799,11 +5765,6 @@ "sparse-bitfield": "^3.0.3" } }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, "saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -5822,15 +5783,6 @@ "lru-cache": "^6.0.0" } }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5945,7 +5897,8 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true }, "stack-utils": { "version": "2.0.3", @@ -5978,6 +5931,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6008,6 +5962,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, "requires": { "safe-buffer": "~5.1.0" }, @@ -6015,7 +5970,8 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true } } }, @@ -6023,6 +5979,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -6049,6 +6006,7 @@ "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, "requires": { "has-flag": "^4.0.0" } @@ -6167,22 +6125,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "requires": { - "any-promise": "^1.0.0" - } - }, - "thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", - "requires": { - "thenify": ">= 3.1.0 < 4" - } - }, "throat": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", @@ -6234,7 +6176,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, "requires": { "punycode": "^2.1.1" } @@ -6298,7 +6239,8 @@ "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "tsutils": { "version": "3.21.0", @@ -6344,29 +6286,6 @@ "is-typedarray": "^1.0.0" } }, - "typeorm": { - "version": "0.2.28", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.28.tgz", - "integrity": "sha512-BTtUBGwsFzODvHY+AlWL9pvJ2uEj8qpHwmo03z43RvZkG8BAryQJQ3lZ7HlGvI9IQU8y1IYGWe97HsVr8kXB9g==", - "requires": { - "@sqltools/formatter": "1.2.2", - "app-root-path": "^3.0.0", - "buffer": "^5.5.0", - "chalk": "^4.1.0", - "cli-highlight": "^2.1.4", - "debug": "^4.1.1", - "dotenv": "^8.2.0", - "glob": "^7.1.6", - "js-yaml": "^3.14.0", - "mkdirp": "^1.0.4", - "reflect-metadata": "^0.1.13", - "sha.js": "^2.4.11", - "tslib": "^1.13.0", - "xml2js": "^0.4.23", - "yargonaut": "^1.1.2", - "yargs": "^16.0.3" - } - }, "typescript": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", @@ -6412,7 +6331,8 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, "uuid": { "version": "8.3.2", @@ -6484,8 +6404,7 @@ "webidl-conversions": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" }, "whatwg-encoding": { "version": "1.0.5", @@ -6506,7 +6425,6 @@ "version": "8.7.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, "requires": { "lodash": "^4.7.0", "tr46": "^2.1.0", @@ -6545,6 +6463,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -6554,7 +6473,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write-file-atomic": { "version": "3.0.3", @@ -6580,20 +6500,6 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, - "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - } - }, - "xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" - }, "xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -6603,7 +6509,8 @@ "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true }, "yallist": { "version": "4.0.0", @@ -6611,57 +6518,11 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "yargonaut": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/yargonaut/-/yargonaut-1.1.4.tgz", - "integrity": "sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==", - "requires": { - "chalk": "^1.1.1", - "figlet": "^1.1.1", - "parent-require": "^1.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -6675,7 +6536,8 @@ "yargs-parser": { "version": "20.2.7", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", - "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==" + "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", + "dev": true }, "yauzl": { "version": "2.10.0", diff --git a/package.json b/package.json index 0abd3e76..d9d88f57 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,8 @@ "human-interval": "2.0.1", "lodash": "4.17.21", "luxon": "2.0.1", - "mongodb": "3.6.10", + "mongodb": "4.1.0", "typed-emitter": "1.3.1", - "typeorm": "0.2.28", "uuid": "8.3.2" }, "devDependencies": { @@ -34,7 +33,6 @@ "@types/jest": "26.0.24", "@types/lodash": "4.14.171", "@types/luxon": "1.27.1", - "@types/mongodb": "3.6.20", "@types/node": "16.3.2", "@types/pino": "6.3.9", "@types/uuid": "8.3.1", @@ -55,7 +53,6 @@ "ts-jest": "27.0.3", "ts-mockito": "2.6.1", "ts-node": "10.1.0", - "typescript": "4.3.5", - "uuid": "8.3.2" + "typescript": "4.3.5" } } diff --git a/src/connect.ts b/src/connect.ts index c4984239..4b1df61d 100644 --- a/src/connect.ts +++ b/src/connect.ts @@ -1,37 +1,36 @@ -import { Connection, createConnection, getConnection } from 'typeorm'; -import { JobEntity } from './repository/JobEntity'; -import { JobRepository } from './repository/JobRepository'; -import { ExecutionsEntity } from './repository/ExecutionsEntity'; -import { ExecutionsRepository } from './repository/ExecutionsRepository'; -import { isConnected } from './isConnected'; +import { JOBS_COLLECTION_NAME } from './repository/JobRepository'; +import { MongoClient } from 'mongodb'; -export const connectionName = 'momo'; +let mongoClient: MongoClient | undefined; export interface MomoConnectionOptions { url: string; } -export async function connect(connectionOptions: MomoConnectionOptions): Promise { - if (isConnected()) { - return getConnection(connectionName); +export function connectForTest(testClient: MongoClient): void { + mongoClient = testClient; +} + +export async function connect(connectionOptions: MomoConnectionOptions): Promise { + if (mongoClient !== undefined) { + return mongoClient; } - const connection = await createConnection({ - ...connectionOptions, - type: 'mongodb', - name: connectionName, - useUnifiedTopology: true, - entities: [ExecutionsEntity, JobEntity], - }); + mongoClient = await MongoClient.connect(connectionOptions.url); - await connection.getCustomRepository(JobRepository).createCollectionIndex({ name: 1 }, { name: 'job_name_index' }); - await connection - .getCustomRepository(ExecutionsRepository) - .createCollectionIndex({ scheduleId: 1 }, { name: 'schedule_id_index' }); + await mongoClient.db().collection(JOBS_COLLECTION_NAME).createIndex({ name: 1 }, { name: 'job_name_index' }); + await mongoClient.db().collection(JOBS_COLLECTION_NAME).createIndex({ scheduleId: 1 }, { name: 'schedule_id_index' }); - return connection; + return mongoClient; } export async function disconnect(): Promise { - await getConnection(connectionName).close(); + await mongoClient?.close(); +} + +export function getConnection(): MongoClient { + if (mongoClient === undefined) { + throw new Error('momo is not connected, please call connect() first'); + } + return mongoClient; } diff --git a/src/index.ts b/src/index.ts index 893f1db1..8b8fa8b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,6 @@ export { MomoJob } from './job/MomoJob'; export { MomoJobDescription, JobSchedulerStatus } from './job/MomoJobDescription'; export { ExecutionStatus } from './job/ExecutionInfo'; export { ExecutionInfo } from './job/ExecutionInfo'; -export { isConnected } from './isConnected'; export { check } from './job/check'; export { clear } from './job/clear'; export { list } from './job/list'; diff --git a/src/isConnected.ts b/src/isConnected.ts index 923cbc3c..e69de29b 100644 --- a/src/isConnected.ts +++ b/src/isConnected.ts @@ -1,14 +0,0 @@ -import { getConnection } from 'typeorm'; -import { connectionName } from './connect'; - -/** - * Checks whether Momo is connected to a database. - */ -export function isConnected(): boolean { - try { - const connection = getConnection(connectionName); - return connection.isConnected; - } catch (e) { - return false; - } -} diff --git a/src/job/check.ts b/src/job/check.ts index e5b8c778..dc224887 100644 --- a/src/job/check.ts +++ b/src/job/check.ts @@ -1,6 +1,5 @@ import { ExecutionInfo } from './ExecutionInfo'; import { getJobRepository } from '../repository/getRepository'; -import { isConnected } from '../isConnected'; /** * Retrieves execution information about the job from the database. Returns undefined if the job cannot be found or was never executed. @@ -8,7 +7,6 @@ import { isConnected } from '../isConnected'; * @param name the job to check */ export async function check(name: string): Promise { - if (!isConnected()) return; const job = await getJobRepository().findOne({ name }); return job?.executionInfo; } diff --git a/src/job/clear.ts b/src/job/clear.ts index 2693771e..9645e231 100644 --- a/src/job/clear.ts +++ b/src/job/clear.ts @@ -1,10 +1,8 @@ -import { isConnected } from '../index'; import { getJobRepository } from '../repository/getRepository'; /** * Removes all jobs from the database. */ export async function clear(): Promise { - if (!isConnected()) return; - await getJobRepository().deleteMany({}); + await getJobRepository().delete({}); } diff --git a/src/job/define.ts b/src/job/define.ts index 856803e1..4463d67d 100644 --- a/src/job/define.ts +++ b/src/job/define.ts @@ -1,5 +1,4 @@ import { Job } from './Job'; -import { JobEntity } from '../repository/JobEntity'; import { getJobRepository } from '../repository/getRepository'; import { keepLatest } from './keepLatest'; import { Logger } from '../logging/Logger'; @@ -9,16 +8,15 @@ export async function define(job: Job, logger?: Logger): Promise { logger?.debug('define job', { name, concurrency, interval, maxRunning }); - const jobEntity = JobEntity.from(job); const jobRepository = getJobRepository(); const old = await keepLatest(name, logger); if (old) { logger?.debug('update job in database', { name }); - await jobRepository.updateJob(name, jobEntity); + await jobRepository.updateJob(name, job); return; } logger?.debug('save job to database', { name }); - await jobRepository.save(jobEntity); + await jobRepository.save(job); } diff --git a/src/job/keepLatest.ts b/src/job/keepLatest.ts index 7ca225ee..cb1a4f78 100644 --- a/src/job/keepLatest.ts +++ b/src/job/keepLatest.ts @@ -15,7 +15,7 @@ export async function keepLatest(name: string, logger?: Logger): Promise { - if (!isConnected()) return []; const jobs = await getJobRepository().find(); return jobs.map((job) => { diff --git a/src/repository/ExecutionsEntity.ts b/src/repository/ExecutionsEntity.ts index b0fd1dee..40fb496c 100644 --- a/src/repository/ExecutionsEntity.ts +++ b/src/repository/ExecutionsEntity.ts @@ -1,27 +1,12 @@ -import { Column, Entity, Index, ObjectID, ObjectIdColumn } from 'typeorm'; +import { ObjectId } from 'mongodb'; interface Executions { [name: string]: number; } -@Entity({ name: 'executions' }) -export class ExecutionsEntity { - @ObjectIdColumn() - public _id?: ObjectID; - - @Index() - @Column() - public scheduleId: string; - - @Column() - public timestamp: number; - - @Column() - public executions: Executions; - - constructor(scheduleId: string, timestamp: number, executions: Executions) { - this.scheduleId = scheduleId; - this.timestamp = timestamp; - this.executions = executions; - } +export interface ExecutionsEntity { + _id?: ObjectId; + scheduleId: string; + timestamp: number; + executions: Executions; } diff --git a/src/repository/ExecutionsRepository.ts b/src/repository/ExecutionsRepository.ts index 75ce63e9..baa4f7f4 100644 --- a/src/repository/ExecutionsRepository.ts +++ b/src/repository/ExecutionsRepository.ts @@ -1,15 +1,21 @@ import { DateTime } from 'luxon'; -import { EntityRepository, MongoRepository } from 'typeorm'; import { ExecutionsEntity } from './ExecutionsEntity'; import { defaultInterval } from '../schedule/SchedulePing'; +import { MongoClient } from 'mongodb'; +import { Repository } from './Repository'; -@EntityRepository(ExecutionsEntity) -export class ExecutionsRepository extends MongoRepository { +export const EXECUTIONS_COLLECTION_NAME = 'executions'; + +export class ExecutionsRepository extends Repository { public static deadScheduleThreshold = 2 * defaultInterval; + constructor(mongoClient: MongoClient) { + super(mongoClient, EXECUTIONS_COLLECTION_NAME); + } + async addSchedule(scheduleId: string): Promise { - await this.save(new ExecutionsEntity(scheduleId, DateTime.now().toMillis(), {})); + await this.save({ scheduleId, timestamp: DateTime.now().toMillis(), executions: {} }); } async removeJob(scheduleId: string, name: string) { @@ -20,7 +26,7 @@ export class ExecutionsRepository extends MongoRepository { const executions = executionsEntity.executions; delete executions[name]; - await this.update({ scheduleId }, { executions }); + await this.updateOne({ scheduleId }, { $set: { executions } }); } async addExecution( @@ -33,13 +39,13 @@ export class ExecutionsRepository extends MongoRepository { return { added: false, running }; } - await this.findOneAndUpdate({ scheduleId }, { $inc: { [`executions.${name}`]: 1 } }); + await this.updateOne({ scheduleId }, { $inc: { [`executions.${name}`]: 1 } }); return { added: true, running }; } async removeExecution(scheduleId: string, name: string): Promise { - await this.findOneAndUpdate({ scheduleId }, { $inc: { [`executions.${name}`]: -1 } }); + await this.updateOne({ scheduleId }, { $inc: { [`executions.${name}`]: -1 } }); } async countRunningExecutions(name: string): Promise { @@ -51,12 +57,11 @@ export class ExecutionsRepository extends MongoRepository { } async ping(scheduleId: string): Promise { - await this.findOneAndUpdate({ scheduleId }, { $set: { timestamp: DateTime.now().toMillis() } }); + await this.updateOne({ scheduleId }, { $set: { timestamp: DateTime.now().toMillis() } }); } async clean(): Promise { const timestamp = DateTime.now().toMillis() - ExecutionsRepository.deadScheduleThreshold; - const result = await this.deleteMany({ timestamp: { $lt: timestamp } }); - return result.deletedCount ?? 0; + return this.delete({ timestamp: { $lt: timestamp } }); } } diff --git a/src/repository/JobEntity.ts b/src/repository/JobEntity.ts index 56be56bb..d823aa1e 100644 --- a/src/repository/JobEntity.ts +++ b/src/repository/JobEntity.ts @@ -1,37 +1,11 @@ -import { Column, Entity, Index, ObjectID, ObjectIdColumn } from 'typeorm'; -import { Job } from '../job/Job'; import { ExecutionInfo } from '../job/ExecutionInfo'; - -@Entity({ name: 'jobs' }) -export class JobEntity { - @ObjectIdColumn() - public _id?: ObjectID; - - @Index() - @Column() - public name: string; - - @Column() - public interval: string; - - @Column() - public concurrency: number; - - @Column() - public maxRunning: number; - - @Column() - public executionInfo?: ExecutionInfo; - - constructor(name: string, interval: string, concurrency: number, maxRunning: number, executionInfo?: ExecutionInfo) { - this.name = name; - this.interval = interval; - this.concurrency = concurrency; - this.maxRunning = maxRunning; - this.executionInfo = executionInfo; - } - - static from({ name, interval, concurrency, maxRunning }: Job): JobEntity { - return new JobEntity(name, interval, concurrency, maxRunning); - } +import { ObjectId } from 'mongodb'; + +export interface JobEntity { + _id?: ObjectId; + name: string; + interval: string; + concurrency: number; + maxRunning: number; + executionInfo?: ExecutionInfo; } diff --git a/src/repository/JobRepository.ts b/src/repository/JobRepository.ts index 1c592a5c..c272b5cd 100644 --- a/src/repository/JobRepository.ts +++ b/src/repository/JobRepository.ts @@ -1,8 +1,14 @@ -import { EntityRepository, MongoRepository } from 'typeorm'; +import { MongoClient } from 'mongodb'; import { JobEntity } from './JobEntity'; +import { Repository } from './Repository'; + +export const JOBS_COLLECTION_NAME = 'jobs'; + +export class JobRepository extends Repository { + constructor(mongoClient: MongoClient) { + super(mongoClient, JOBS_COLLECTION_NAME); + } -@EntityRepository(JobEntity) -export class JobRepository extends MongoRepository { async updateJob(name: string, update: Partial): Promise { const savedJobs = await this.find({ name }); diff --git a/src/repository/Repository.ts b/src/repository/Repository.ts new file mode 100644 index 00000000..dee80fda --- /dev/null +++ b/src/repository/Repository.ts @@ -0,0 +1,34 @@ +import { Collection, Filter, MongoClient, ObjectId, OptionalId, UpdateFilter } from 'mongodb'; + +export class Repository { + private readonly collection: Collection; + + constructor(mongoClient: MongoClient, collectionName: string) { + this.collection = mongoClient.db().collection(collectionName); + } + + async save(entity: OptionalId): Promise { + await this.collection.insertOne(entity); + } + + async updateOne(filter: Filter, update: UpdateFilter | Partial): Promise { + await this.collection.updateOne(filter, update); + } + + async find(filter: Filter = {}): Promise { + return this.collection.find(filter).toArray(); + } + + async findOne(filter: Filter = {}): Promise { + return this.collection.findOne(filter); + } + + async delete(filter: Filter = {}): Promise { + const result = await this.collection.deleteMany(filter); + return result.deletedCount; + } + + async deleteOne(filter: Filter): Promise { + await this.collection.deleteOne(filter); + } +} diff --git a/src/repository/getRepository.ts b/src/repository/getRepository.ts index 4f24c6f9..03882a62 100644 --- a/src/repository/getRepository.ts +++ b/src/repository/getRepository.ts @@ -1,12 +1,20 @@ -import { getConnection } from 'typeorm'; -import { connectionName } from '../connect'; +import { getConnection } from '../connect'; import { ExecutionsRepository } from './ExecutionsRepository'; import { JobRepository } from './JobRepository'; +let jobRepository: JobRepository | undefined; +let executionsRepository: ExecutionsRepository | undefined; + export function getJobRepository(): JobRepository { - return getConnection(connectionName).getCustomRepository(JobRepository); + if (jobRepository === undefined) { + jobRepository = new JobRepository(getConnection()); + } + return jobRepository; } export function getExecutionsRepository(): ExecutionsRepository { - return getConnection(connectionName).getCustomRepository(ExecutionsRepository); + if (executionsRepository === undefined) { + executionsRepository = new ExecutionsRepository(getConnection()); + } + return executionsRepository; } diff --git a/src/schedule/Schedule.ts b/src/schedule/Schedule.ts index 96f1b00b..859d8506 100644 --- a/src/schedule/Schedule.ts +++ b/src/schedule/Schedule.ts @@ -170,7 +170,7 @@ export class Schedule extends LogEmitter { public async removeJob(name: string): Promise { await this.cancelJob(name); this.logger.debug('remove', { name }); - await getJobRepository().delete({ name }); + await getJobRepository().deleteOne({ name }); } /** @@ -180,7 +180,7 @@ export class Schedule extends LogEmitter { const names = Object.keys(this.jobSchedulers); await this.cancel(); this.logger.debug('remove all jobs', { names: names.join(', ') }); - await getJobRepository().deleteMany({ name: { $in: names } }); + await getJobRepository().delete({ name: { $in: names } }); } /** diff --git a/src/schedule/SchedulePing.ts b/src/schedule/SchedulePing.ts index bbcf9979..b2cbdb4b 100644 --- a/src/schedule/SchedulePing.ts +++ b/src/schedule/SchedulePing.ts @@ -28,6 +28,6 @@ export class SchedulePing { this.logger.debug('stop SchedulerPing', { scheduleId: this.scheduleId }); clearInterval(this.handle); } - await getExecutionsRepository().delete({ scheduleId: this.scheduleId }); + await getExecutionsRepository().deleteOne({ scheduleId: this.scheduleId }); } } diff --git a/test/connect.integration.spec.ts b/test/connect.integration.spec.ts index 18e7e5df..82e7f471 100644 --- a/test/connect.integration.spec.ts +++ b/test/connect.integration.spec.ts @@ -1,6 +1,6 @@ import { MongoMemoryServer } from 'mongodb-memory-server'; import { connect, disconnect } from '../src/connect'; -import { isConnected } from '../src'; +import { getConnection } from '../dist/connect'; describe('connect', () => { let mongo: MongoMemoryServer; @@ -8,7 +8,7 @@ describe('connect', () => { beforeAll(async () => { mongo = await MongoMemoryServer.create(); - url = await mongo.getUri(); + url = mongo.getUri(); }); afterAll(async () => await mongo.stop()); @@ -16,13 +16,13 @@ describe('connect', () => { it('connects mongo', async () => { await connect({ url }); - expect(isConnected()).toBe(true); + expect(getConnection()).not.toThrow(); }); it('disconnects mongo', async () => { await connect({ url }); await disconnect(); - expect(isConnected()).toBe(false); + expect(getConnection()).not.toThrow(); }); }); diff --git a/test/isConnected.spec.ts b/test/isConnected.spec.ts deleted file mode 100644 index f87540d5..00000000 --- a/test/isConnected.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { mockRepositories } from './utils/mockRepositories'; -import { isConnected } from '../src'; - -describe('isConnected', () => { - beforeEach(() => jest.restoreAllMocks()); - - it('reports connection', () => { - mockRepositories(); - expect(isConnected()).toBe(true); - }); - - it('reports missing connection', () => { - expect(isConnected()).toBe(false); - }); -}); diff --git a/test/job/clear.integration.spec.ts b/test/job/clear.integration.spec.ts index 8e8dae2a..0ef02db9 100644 --- a/test/job/clear.integration.spec.ts +++ b/test/job/clear.integration.spec.ts @@ -15,7 +15,7 @@ describe('clear', () => { jobRepository = getJobRepository(); }); - beforeEach(async () => await jobRepository.delete({})); + beforeEach(async () => await jobRepository.deleteOne({})); afterAll(async () => { await disconnect(); diff --git a/test/job/define.spec.ts b/test/job/define.spec.ts index 01b1f50a..b1427ac7 100644 --- a/test/job/define.spec.ts +++ b/test/job/define.spec.ts @@ -42,7 +42,7 @@ describe('define', () => { const newInterval = 'two minutes'; await define(withDefaults({ ...job, interval: newInterval })); - verify(jobRepository.remove(deepEqual([duplicate]))).once(); + verify(jobRepository.delete(deepEqual([duplicate]))).once(); verify(jobRepository.updateJob(job.name, deepEqual(createJobEntity({ ...job, interval: newInterval })))).once(); }); }); diff --git a/test/job/findLatest.spec.ts b/test/job/findLatest.spec.ts index c1836ffb..f7055b6d 100644 --- a/test/job/findLatest.spec.ts +++ b/test/job/findLatest.spec.ts @@ -1,11 +1,10 @@ import { DateTime } from 'luxon'; import { findLatest } from '../../src/job/findLatest'; import { JobEntity } from '../../src/repository/JobEntity'; -import { Job } from '../../src/job/Job'; import { ExecutionInfo } from '../../src'; function createJob(lastFinished?: number) { - const job = JobEntity.from({ name: 'test' } as Job); + const job = { name: 'test' } as JobEntity; if (lastFinished) { job.executionInfo = { lastFinished: DateTime.fromMillis(lastFinished).toISO() } as ExecutionInfo; } diff --git a/test/job/keepLatest.spec.ts b/test/job/keepLatest.spec.ts index bf2bbefe..d7341487 100644 --- a/test/job/keepLatest.spec.ts +++ b/test/job/keepLatest.spec.ts @@ -4,7 +4,6 @@ import { anything, capture, deepEqual, verify, when } from 'ts-mockito'; import { JobRepository } from '../../src/repository/JobRepository'; import { keepLatest } from '../../src/job/keepLatest'; import { JobEntity } from '../../src/repository/JobEntity'; -import { Job } from '../../src/job/Job'; import { ExecutionInfo } from '../../src'; import { mockRepositories } from '../utils/mockRepositories'; @@ -19,22 +18,21 @@ describe('keepLatest', () => { afterEach(() => jest.clearAllMocks()); it('removes all duplicate jobs except latest', async () => { - const duplicate = JobEntity.from({ name } as Job); - const latest = JobEntity.from({ name } as Job); - latest.executionInfo = { lastFinished: DateTime.now().toISO() } as ExecutionInfo; + const duplicate = { name } as JobEntity; + const latest = { name, executionInfo: { lastFinished: DateTime.now().toISO() } as ExecutionInfo } as JobEntity; when(jobRepository.find(deepEqual({ name }))).thenResolve([duplicate, latest]); const kept = await keepLatest(name); expect(kept).toBe(latest); - verify(jobRepository.remove(anything())).once(); - const [removedJobs] = capture(jobRepository.remove).last(); + verify(jobRepository.delete(anything())).once(); + const [removedJobs] = capture(jobRepository.delete).last(); expect(removedJobs).toEqual([duplicate]); }); it('returns a job without duplicates', async () => { - const job = JobEntity.from({ name } as Job); + const job = { name } as JobEntity; when(jobRepository.find(deepEqual({ name }))).thenResolve([job]); expect(await keepLatest(name)).toEqual(job); diff --git a/test/repository/ExecutionsRepository.integration.spec.ts b/test/repository/ExecutionsRepository.integration.spec.ts index 41853896..aaa0c442 100644 --- a/test/repository/ExecutionsRepository.integration.spec.ts +++ b/test/repository/ExecutionsRepository.integration.spec.ts @@ -20,7 +20,7 @@ describe('ExecutionsRepository', () => { executionsRepository = getExecutionsRepository(); }); - beforeEach(async () => await executionsRepository.clear()); + beforeEach(async () => await executionsRepository.delete()); afterAll(async () => { await disconnect(); diff --git a/test/repository/JobRepository.integration.spec.ts b/test/repository/JobRepository.integration.spec.ts index d94f5964..14893d69 100644 --- a/test/repository/JobRepository.integration.spec.ts +++ b/test/repository/JobRepository.integration.spec.ts @@ -21,7 +21,7 @@ describe('JobRepository', () => { jobRepository = getJobRepository(); }); - beforeEach(async () => await jobRepository.delete({})); + beforeEach(async () => await jobRepository.deleteOne({})); afterAll(async () => { await disconnect(); diff --git a/test/schedule/MongoSchedule.spec.ts b/test/schedule/MongoSchedule.spec.ts index 36eb2234..f252a2b7 100644 --- a/test/schedule/MongoSchedule.spec.ts +++ b/test/schedule/MongoSchedule.spec.ts @@ -21,6 +21,6 @@ describe('MongoSchedule', () => { await mongoSchedule.disconnect(); - verify(executionsRepository.delete(deepEqual({ scheduleId }))); + verify(executionsRepository.deleteOne(deepEqual({ scheduleId }))); }); }); diff --git a/test/schedule/MongoScheduleBuilder.integration.spec.ts b/test/schedule/MongoScheduleBuilder.integration.spec.ts index 03afde42..8fe030c2 100644 --- a/test/schedule/MongoScheduleBuilder.integration.spec.ts +++ b/test/schedule/MongoScheduleBuilder.integration.spec.ts @@ -27,7 +27,7 @@ describe('MongoScheduleBuilder', () => { beforeAll(async () => { mongo = await MongoMemoryServer.create(); - connectionOptions = { url: await mongo.getUri() }; + connectionOptions = { url: mongo.getUri() }; }); afterEach(async () => { diff --git a/test/schedule/SchedulePing.spec.ts b/test/schedule/SchedulePing.spec.ts index ae8d5021..b7d0b8b2 100644 --- a/test/schedule/SchedulePing.spec.ts +++ b/test/schedule/SchedulePing.spec.ts @@ -26,6 +26,6 @@ describe('SchedulePing', () => { await sleep(SchedulePing.interval); verify(executionsRepository.ping(scheduleId)).once(); - verify(executionsRepository.delete(deepEqual({ scheduleId: scheduleId }))).once(); + verify(executionsRepository.deleteOne(deepEqual({ scheduleId: scheduleId }))).once(); }); }); diff --git a/test/schedule/momo.integration.spec.ts b/test/schedule/momo.integration.spec.ts index 6e70092d..26710e17 100644 --- a/test/schedule/momo.integration.spec.ts +++ b/test/schedule/momo.integration.spec.ts @@ -220,7 +220,7 @@ describe('Momo', () => { await mongoSchedule.define(job); await mongoSchedule.start(); - await jobRepository.clear(); + await jobRepository.delete(); await sleep(1500); expect(jobHandler.count).toBe(0); @@ -233,7 +233,7 @@ describe('Momo', () => { const updatedConcurrency = 5; const updatedMaxRunning = 10; - await jobRepository.update( + await jobRepository.updateOne( { name: job.name }, { concurrency: updatedConcurrency, maxRunning: updatedMaxRunning } ); @@ -254,7 +254,7 @@ describe('Momo', () => { await mongoSchedule.start(); const updatedInterval = '2 seconds'; - await jobRepository.update({ name: job.name }, { interval: updatedInterval }); + await jobRepository.updateOne({ name: job.name }, { interval: updatedInterval }); await waitFor(() => expect(jobHandler.count).toBe(1)); @@ -546,7 +546,7 @@ describe('Momo', () => { expect(running).toBe(1); }, 1100); - await jobRepository.clear(); + await jobRepository.delete(); await waitFor(async () => { expect(await jobRepository.find({ name: job.name })).toHaveLength(0); @@ -558,7 +558,7 @@ describe('Momo', () => { await mongoSchedule.start(); - await jobRepository.clear(); + await jobRepository.delete(); await waitFor(() => { expect(receivedError).toEqual({ diff --git a/test/utils/createJobEntity.ts b/test/utils/createJobEntity.ts index 3ab99b93..a45c41d3 100644 --- a/test/utils/createJobEntity.ts +++ b/test/utils/createJobEntity.ts @@ -2,6 +2,7 @@ import { MomoJob } from '../../src'; import { JobEntity } from '../../src/repository/JobEntity'; import { withDefaults } from '../../src/job/withDefaults'; +// TODO remove this? export function createJobEntity(job: MomoJob): JobEntity { - return JobEntity.from(withDefaults(job)); + return withDefaults(job); } diff --git a/test/utils/mockRepositories.ts b/test/utils/mockRepositories.ts index 89da9364..3d8e425a 100644 --- a/test/utils/mockRepositories.ts +++ b/test/utils/mockRepositories.ts @@ -1,25 +1,19 @@ import { instance, mock } from 'ts-mockito'; -import * as typeorm from 'typeorm'; -import { Connection, MongoRepository } from 'typeorm'; import { JobRepository } from '../../src/repository/JobRepository'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; +import { connectForTest } from '../../src/connect'; +import { MongoClient } from 'mongodb'; export function mockRepositories(): { jobRepository: JobRepository; executionsRepository: ExecutionsRepository } { + connectForTest(instance(mock(MongoClient))); + const jobRepository = mock(JobRepository); const executionsRepository = mock(ExecutionsRepository); - jest.spyOn(typeorm, 'getConnection').mockReturnValue({ - isConnected: true, - close: jest.fn(), - getCustomRepository: (clazz: typeof MongoRepository) => { - switch (clazz) { - case JobRepository: - return instance(jobRepository); - case ExecutionsRepository: - return instance(executionsRepository); - default: - return undefined; - } - }, - } as unknown as Connection); + + jest.mock('../../src/repository/getRepository', { + getJobRepository: () => instance(jobRepository), + getExecutionsRepository: () => instance(executionsRepository), + } as any); + return { jobRepository, executionsRepository }; } From 6ff1a63199399ea93981c8acaec42cc359bb5e4f Mon Sep 17 00:00:00 2001 From: Niklas Eicker Date: Thu, 26 Aug 2021 17:36:58 +0200 Subject: [PATCH 02/13] fix: Fix some mongo update queries and tests Signed-off-by: Niklas Eicker --- src/job/Job.ts | 5 ++-- src/repository/ExecutionsRepository.ts | 2 +- src/repository/JobEntity.ts | 7 ++--- src/repository/JobRepository.ts | 26 +--------------- src/repository/Repository.ts | 2 +- test/connect.integration.spec.ts | 3 +- test/executor/JobExecutor.spec.ts | 29 ++++++++++-------- test/job/check.spec.ts | 30 ++++++++++++------- test/schedule/MongoSchedule.spec.ts | 21 +++++++++---- .../MongoScheduleBuilder.integration.spec.ts | 5 +--- test/schedule/Schedule.integration.spec.ts | 13 ++++---- test/schedule/Schedule.spec.ts | 28 +++++++++-------- test/schedule/SchedulePing.spec.ts | 16 +++++++--- test/schedule/momo.integration.spec.ts | 4 +-- test/scheduler/JobScheduler.spec.ts | 20 ++++++++----- test/scheduler/calculateDelay.spec.ts | 8 +++-- test/utils/mockRepositories.ts | 19 ------------ 17 files changed, 114 insertions(+), 124 deletions(-) delete mode 100644 test/utils/mockRepositories.ts diff --git a/src/job/Job.ts b/src/job/Job.ts index 9c2763f6..ea9e4597 100644 --- a/src/job/Job.ts +++ b/src/job/Job.ts @@ -1,7 +1,8 @@ import { Handler } from './MomoJob'; -import { ExecutionInfo } from './ExecutionInfo'; +import { WithoutId } from 'mongodb'; +import { JobEntity } from '../repository/JobEntity'; -export type MomoJobStatus = JobDefinition & { executionInfo?: ExecutionInfo }; +export type MomoJobStatus = WithoutId; export interface JobDefinition { name: string; diff --git a/src/repository/ExecutionsRepository.ts b/src/repository/ExecutionsRepository.ts index baa4f7f4..683c0d92 100644 --- a/src/repository/ExecutionsRepository.ts +++ b/src/repository/ExecutionsRepository.ts @@ -50,7 +50,7 @@ export class ExecutionsRepository extends Repository { async countRunningExecutions(name: string): Promise { const timestamp = DateTime.now().toMillis() - ExecutionsRepository.deadScheduleThreshold; - const numbers = (await this.find({ where: { timestamp: { $gt: timestamp } } })).map((executionsEntity) => { + const numbers = (await this.find({ timestamp: { $gt: timestamp } })).map((executionsEntity) => { return executionsEntity.executions[name] ?? 0; }); return numbers.reduce((sum, current) => sum + current, 0); diff --git a/src/repository/JobEntity.ts b/src/repository/JobEntity.ts index d823aa1e..a44c63ba 100644 --- a/src/repository/JobEntity.ts +++ b/src/repository/JobEntity.ts @@ -1,11 +1,8 @@ import { ExecutionInfo } from '../job/ExecutionInfo'; import { ObjectId } from 'mongodb'; +import { JobDefinition } from '../job/Job'; -export interface JobEntity { +export interface JobEntity extends JobDefinition { _id?: ObjectId; - name: string; - interval: string; - concurrency: number; - maxRunning: number; executionInfo?: ExecutionInfo; } diff --git a/src/repository/JobRepository.ts b/src/repository/JobRepository.ts index c272b5cd..ee9e6fb3 100644 --- a/src/repository/JobRepository.ts +++ b/src/repository/JobRepository.ts @@ -10,30 +10,6 @@ export class JobRepository extends Repository { } async updateJob(name: string, update: Partial): Promise { - const savedJobs = await this.find({ name }); - - if (savedJobs.length === 0) { - return; - } - const updatedJob = merge(savedJobs, update); - - await this.save(updatedJob); - } -} - -function merge(savedJobs: JobEntity[], { interval, concurrency, maxRunning, executionInfo }: Partial) { - const updatedJob = savedJobs[0]; - if (interval !== undefined) { - updatedJob.interval = interval; - } - if (concurrency !== undefined) { - updatedJob.concurrency = concurrency; - } - if (maxRunning !== undefined) { - updatedJob.maxRunning = maxRunning; - } - if (executionInfo !== undefined) { - updatedJob.executionInfo = executionInfo; + await this.updateOne({ name }, { $set: update }); } - return updatedJob; } diff --git a/src/repository/Repository.ts b/src/repository/Repository.ts index dee80fda..218e3bc0 100644 --- a/src/repository/Repository.ts +++ b/src/repository/Repository.ts @@ -11,7 +11,7 @@ export class Repository { await this.collection.insertOne(entity); } - async updateOne(filter: Filter, update: UpdateFilter | Partial): Promise { + async updateOne(filter: Filter, update: UpdateFilter): Promise { await this.collection.updateOne(filter, update); } diff --git a/test/connect.integration.spec.ts b/test/connect.integration.spec.ts index 82e7f471..8a52e82a 100644 --- a/test/connect.integration.spec.ts +++ b/test/connect.integration.spec.ts @@ -1,6 +1,5 @@ import { MongoMemoryServer } from 'mongodb-memory-server'; -import { connect, disconnect } from '../src/connect'; -import { getConnection } from '../dist/connect'; +import { connect, disconnect, getConnection } from '../src/connect'; describe('connect', () => { let mongo: MongoMemoryServer; diff --git a/test/executor/JobExecutor.spec.ts b/test/executor/JobExecutor.spec.ts index c9b86b4d..0b4f8be5 100644 --- a/test/executor/JobExecutor.spec.ts +++ b/test/executor/JobExecutor.spec.ts @@ -1,14 +1,21 @@ -import { anything, capture, verify, when } from 'ts-mockito'; +import { anything, capture, instance, mock, verify, when } from 'ts-mockito'; import { JobExecutor } from '../../src/executor/JobExecutor'; -import { JobEntity } from '../../src/repository/JobEntity'; import { JobRepository } from '../../src/repository/JobRepository'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; import { Job } from '../../src/job/Job'; import { ExecutionStatus, MomoErrorType } from '../../src'; -import { mockRepositories } from '../utils/mockRepositories'; import { loggerForTests } from '../utils/logging'; +let jobRepository: JobRepository; +let executionsRepository: ExecutionsRepository; +jest.mock('../../src/repository/getRepository', () => { + return { + getJobRepository: () => instance(jobRepository), + getExecutionsRepository: () => instance(executionsRepository), + }; +}); + describe('JobExecutor', () => { const scheduleId = '123'; const handler = jest.fn(); @@ -20,19 +27,15 @@ describe('JobExecutor', () => { maxRunning: 0, handler, }; - const savedJob = JobEntity.from(job); const errorFn = jest.fn(); - let jobRepository: JobRepository; - let executionsRepository: ExecutionsRepository; let jobExecutor: JobExecutor; beforeEach(() => { jest.clearAllMocks(); - const repositories = mockRepositories(); - jobRepository = repositories.jobRepository; - executionsRepository = repositories.executionsRepository; + jobRepository = mock(JobRepository); + executionsRepository = mock(ExecutionsRepository); when(executionsRepository.addExecution(scheduleId, job.name, job.maxRunning)).thenResolve({ added: true, running: 0, @@ -42,7 +45,7 @@ describe('JobExecutor', () => { }); it('executes job', async () => { - await jobExecutor.execute(savedJob); + await jobExecutor.execute(job); expect(job.handler).toHaveBeenCalled(); @@ -60,7 +63,7 @@ describe('JobExecutor', () => { running: 0, }); - await jobExecutor.execute(savedJob); + await jobExecutor.execute(job); expect(errorFn).not.toHaveBeenCalled(); expect(job.handler).not.toHaveBeenCalled(); @@ -74,7 +77,7 @@ describe('JobExecutor', () => { throw error; }); - await jobExecutor.execute(savedJob); + await jobExecutor.execute(job); expect(errorFn).toHaveBeenCalledWith('job failed', MomoErrorType.executeJob, { name: job.name }, error); @@ -88,7 +91,7 @@ describe('JobExecutor', () => { return handlerResult; }); - await jobExecutor.execute(savedJob); + await jobExecutor.execute(job); verify(jobRepository.updateJob(job.name, anything())).once(); const [, update] = capture(jobRepository.updateJob).last(); diff --git a/test/job/check.spec.ts b/test/job/check.spec.ts index e400e022..f2bc0531 100644 --- a/test/job/check.spec.ts +++ b/test/job/check.spec.ts @@ -1,18 +1,31 @@ import { DateTime } from 'luxon'; -import { deepEqual, when } from 'ts-mockito'; +import { anyString, deepEqual, instance, mock, when } from 'ts-mockito'; -import { mockRepositories } from '../utils/mockRepositories'; -import { check, ExecutionInfo, ExecutionStatus } from '../../src'; +import { check, clear, ExecutionInfo, ExecutionStatus } from '../../src'; import { JobEntity } from '../../src/repository/JobEntity'; +import { JobRepository } from '../../src/repository/JobRepository'; + +let jobRepository: JobRepository; +jest.mock('../../src/repository/getRepository', () => { + return { + getJobRepository: () => instance(jobRepository), + }; +}); describe('check', () => { const name = 'test'; - afterEach(() => jest.resetAllMocks()); + beforeEach(() => { + jobRepository = mock(JobRepository); + when(jobRepository.findOne(anyString())).thenResolve(undefined); + }); - it('returns executionInfo', async () => { - const jobRepository = mockRepositories().jobRepository; + afterEach(async () => { + jest.resetAllMocks(); + await clear(); + }); + it('returns executionInfo', async () => { const executionInfo: ExecutionInfo = { lastStarted: DateTime.now().toISO(), lastFinished: DateTime.now().toISO(), @@ -26,11 +39,6 @@ describe('check', () => { }); it('returns nothing if job not found', async () => { - mockRepositories().jobRepository; - expect(await check(name)).toBeUndefined(); - }); - - it('returns nothing if not connected', async () => { expect(await check(name)).toBeUndefined(); }); }); diff --git a/test/schedule/MongoSchedule.spec.ts b/test/schedule/MongoSchedule.spec.ts index f252a2b7..53e92fd9 100644 --- a/test/schedule/MongoSchedule.spec.ts +++ b/test/schedule/MongoSchedule.spec.ts @@ -1,16 +1,27 @@ -import { anyString, capture, deepEqual, verify } from 'ts-mockito'; +import { anyString, capture, deepEqual, instance, mock, verify } from 'ts-mockito'; import { MongoSchedule } from '../../src'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; -import { mockRepositories } from '../utils/mockRepositories'; +import { JobRepository } from '../../src/repository/JobRepository'; +import { connectForTest } from '../../src/connect'; +import { MongoClient } from 'mongodb'; + +let jobRepository: JobRepository; +let executionsRepository: ExecutionsRepository; +jest.mock('../../src/repository/getRepository', () => { + return { + getJobRepository: () => instance(jobRepository), + getExecutionsRepository: () => instance(executionsRepository), + }; +}); describe('MongoSchedule', () => { - let executionsRepository: ExecutionsRepository; - beforeEach(async () => { jest.clearAllMocks(); - executionsRepository = mockRepositories().executionsRepository; + connectForTest(instance(mock(MongoClient))); + jobRepository = mock(JobRepository); + executionsRepository = mock(ExecutionsRepository); }); it('connects and starts the ping and disconnects and stops the ping', async () => { diff --git a/test/schedule/MongoScheduleBuilder.integration.spec.ts b/test/schedule/MongoScheduleBuilder.integration.spec.ts index 8fe030c2..f8ce5a23 100644 --- a/test/schedule/MongoScheduleBuilder.integration.spec.ts +++ b/test/schedule/MongoScheduleBuilder.integration.spec.ts @@ -30,10 +30,6 @@ describe('MongoScheduleBuilder', () => { connectionOptions = { url: mongo.getUri() }; }); - afterEach(async () => { - await clear(); - }); - afterAll(async () => { await mongo.stop(); }); @@ -42,6 +38,7 @@ describe('MongoScheduleBuilder', () => { let mongoSchedule: MongoSchedule; afterEach(async () => { + await clear(); await mongoSchedule.disconnect(); }); diff --git a/test/schedule/Schedule.integration.spec.ts b/test/schedule/Schedule.integration.spec.ts index d052e2e8..9230f557 100644 --- a/test/schedule/Schedule.integration.spec.ts +++ b/test/schedule/Schedule.integration.spec.ts @@ -3,7 +3,6 @@ import { MongoMemoryServer } from 'mongodb-memory-server'; import { clear, MomoJob, MongoSchedule } from '../../src'; import { JobRepository } from '../../src/repository/JobRepository'; import { getJobRepository } from '../../src/repository/getRepository'; -import { JobEntity } from '../../src/repository/JobEntity'; import { withDefaults } from '../../src/job/withDefaults'; import { initLoggingForTests } from '../utils/logging'; @@ -63,13 +62,11 @@ describe('Schedule', () => { it('lists jobs on the schedule', async () => { await jobRepository.save( - JobEntity.from( - withDefaults({ - name: 'some job that is in the database but not on the schedule', - handler: jest.fn(), - interval: 'one minute', - }) - ) + withDefaults({ + name: 'some job that is in the database but not on the schedule', + handler: jest.fn(), + interval: 'one minute', + }) ); await mongoSchedule.define(job); diff --git a/test/schedule/Schedule.spec.ts b/test/schedule/Schedule.spec.ts index c3a63ce0..60a4afb0 100644 --- a/test/schedule/Schedule.spec.ts +++ b/test/schedule/Schedule.spec.ts @@ -1,13 +1,21 @@ -import { anyString, deepEqual, verify, when } from 'ts-mockito'; +import { anyString, deepEqual, instance, mock, verify, when } from 'ts-mockito'; import { ExecutionStatus, MomoEvent, MomoJob, MongoSchedule } from '../../src'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; import { JobRepository } from '../../src/repository/JobRepository'; -import { JobEntity } from '../../src/repository/JobEntity'; -import { Job } from '../../src/job/Job'; import { initLoggingForTests } from '../utils/logging'; -import { mockRepositories } from '../utils/mockRepositories'; import { createJobEntity } from '../utils/createJobEntity'; +import { connectForTest } from '../../src/connect'; +import { MongoClient } from 'mongodb'; + +let jobRepository: JobRepository; +let executionsRepository: ExecutionsRepository; +jest.mock('../../src/repository/getRepository', () => { + return { + getJobRepository: () => instance(jobRepository), + getExecutionsRepository: () => instance(executionsRepository), + }; +}); describe('Schedule', () => { const job: MomoJob = { @@ -16,19 +24,17 @@ describe('Schedule', () => { handler: jest.fn(), }; - let jobRepository: JobRepository; - let executionsRepository: ExecutionsRepository; let mongoSchedule: MongoSchedule; beforeEach(async () => { jest.clearAllMocks(); - const repositories = mockRepositories(); - jobRepository = repositories.jobRepository; - executionsRepository = repositories.executionsRepository; + jobRepository = mock(JobRepository); + executionsRepository = mock(ExecutionsRepository); when(jobRepository.find(deepEqual({ name: job.name }))).thenResolve([]); + connectForTest(instance(mock(MongoClient))); mongoSchedule = await MongoSchedule.connect({ url: 'mongodb://does.not/matter' }); initLoggingForTests(mongoSchedule); }); @@ -49,9 +55,7 @@ describe('Schedule', () => { it('does not report error when concurrency > maxRunning but maxRunning is not set', async () => { await mongoSchedule.define({ ...job, concurrency: 3 }); - verify( - jobRepository.save(deepEqual(JobEntity.from({ ...job, maxRunning: 0, concurrency: 3, immediate: false } as Job))) - ).once(); + verify(jobRepository.save(deepEqual({ ...job, maxRunning: 0, concurrency: 3, immediate: false }))).once(); }); it('counts jobs', async () => { diff --git a/test/schedule/SchedulePing.spec.ts b/test/schedule/SchedulePing.spec.ts index b7d0b8b2..60099290 100644 --- a/test/schedule/SchedulePing.spec.ts +++ b/test/schedule/SchedulePing.spec.ts @@ -1,18 +1,26 @@ -import { deepEqual, verify } from 'ts-mockito'; +import { deepEqual, instance, verify, mock } from 'ts-mockito'; import { SchedulePing } from '../../src/schedule/SchedulePing'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; import { sleep } from '../utils/sleep'; -import { mockRepositories } from '../utils/mockRepositories'; +import { JobRepository } from '../../src/repository/JobRepository'; + +let jobRepository: JobRepository; +let executionsRepository: ExecutionsRepository; +jest.mock('../../src/repository/getRepository', () => { + return { + getJobRepository: () => instance(jobRepository), + getExecutionsRepository: () => instance(executionsRepository), + }; +}); describe('SchedulePing', () => { const scheduleId = '123'; const schedulePing = new SchedulePing(scheduleId, { debug: jest.fn(), error: jest.fn() }); - let executionsRepository: ExecutionsRepository; beforeAll(() => { SchedulePing.interval = 1000; - executionsRepository = mockRepositories().executionsRepository; + executionsRepository = mock(ExecutionsRepository); }); it('starts, pings, cleans and stops', async () => { diff --git a/test/schedule/momo.integration.spec.ts b/test/schedule/momo.integration.spec.ts index 26710e17..3a0773c2 100644 --- a/test/schedule/momo.integration.spec.ts +++ b/test/schedule/momo.integration.spec.ts @@ -235,7 +235,7 @@ describe('Momo', () => { const updatedMaxRunning = 10; await jobRepository.updateOne( { name: job.name }, - { concurrency: updatedConcurrency, maxRunning: updatedMaxRunning } + { $set: { concurrency: updatedConcurrency, maxRunning: updatedMaxRunning } } ); await waitFor(async () => { @@ -254,7 +254,7 @@ describe('Momo', () => { await mongoSchedule.start(); const updatedInterval = '2 seconds'; - await jobRepository.updateOne({ name: job.name }, { interval: updatedInterval }); + await jobRepository.updateOne({ name: job.name }, { $set: { interval: updatedInterval } }); await waitFor(() => expect(jobHandler.count).toBe(1)); diff --git a/test/scheduler/JobScheduler.spec.ts b/test/scheduler/JobScheduler.spec.ts index 980e5def..b4625508 100644 --- a/test/scheduler/JobScheduler.spec.ts +++ b/test/scheduler/JobScheduler.spec.ts @@ -5,13 +5,20 @@ import { JobRepository } from '../../src/repository/JobRepository'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; import { Job } from '../../src/job/Job'; import { JobExecutor } from '../../src/executor/JobExecutor'; -import { JobEntity } from '../../src/repository/JobEntity'; import { MomoError, MomoErrorType } from '../../src'; -import { mockRepositories } from '../utils/mockRepositories'; import { loggerForTests } from '../utils/logging'; import { createJobEntity } from '../utils/createJobEntity'; import { sleep } from '../utils/sleep'; +let jobRepository: JobRepository; +let executionsRepository: ExecutionsRepository; +jest.mock('../../src/repository/getRepository', () => { + return { + getJobRepository: () => instance(jobRepository), + getExecutionsRepository: () => instance(executionsRepository), + }; +}); + describe('JobScheduler', () => { const defaultJob = { name: 'test', @@ -25,17 +32,14 @@ describe('JobScheduler', () => { const scheduleId = '123'; let jobExecutor: JobExecutor; - let jobRepository: JobRepository; - let executionsRepository: ExecutionsRepository; let jobScheduler: JobScheduler; beforeEach(() => { jest.clearAllMocks(); jobExecutor = mock(JobExecutor); - const repositories = mockRepositories(); - jobRepository = repositories.jobRepository; - executionsRepository = repositories.executionsRepository; + jobRepository = mock(JobRepository); + executionsRepository = mock(ExecutionsRepository); when(jobExecutor.execute(anything())).thenResolve(); }); @@ -53,7 +57,7 @@ describe('JobScheduler', () => { scheduleId, loggerForTests(errorFn) ); - when(jobRepository.findOne(deepEqual({ name: job.name }))).thenResolve(JobEntity.from(job)); + when(jobRepository.findOne(deepEqual({ name: job.name }))).thenResolve(job); when(executionsRepository.countRunningExecutions(job.name)).thenResolve(0); return job; } diff --git a/test/scheduler/calculateDelay.spec.ts b/test/scheduler/calculateDelay.spec.ts index 816d2e01..6dfb085a 100644 --- a/test/scheduler/calculateDelay.spec.ts +++ b/test/scheduler/calculateDelay.spec.ts @@ -2,7 +2,6 @@ import { Clock, install } from '@sinonjs/fake-timers'; import { DateTime } from 'luxon'; import { JobEntity } from '../../src/repository/JobEntity'; import { calculateDelay } from '../../src/scheduler/calculateDelay'; -import { Job } from '../../src/job/Job'; import { ExecutionInfo } from '../../src'; describe('calculateDelay', () => { @@ -10,7 +9,12 @@ describe('calculateDelay', () => { let clock: Clock; beforeEach(() => { - job = JobEntity.from({ name: 'test', interval: 'one second' } as Job); + job = { + name: 'test', + interval: 'one second', + concurrency: 0, + maxRunning: 1, + }; clock = install(); }); diff --git a/test/utils/mockRepositories.ts b/test/utils/mockRepositories.ts deleted file mode 100644 index 3d8e425a..00000000 --- a/test/utils/mockRepositories.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { instance, mock } from 'ts-mockito'; -import { JobRepository } from '../../src/repository/JobRepository'; -import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; -import { connectForTest } from '../../src/connect'; -import { MongoClient } from 'mongodb'; - -export function mockRepositories(): { jobRepository: JobRepository; executionsRepository: ExecutionsRepository } { - connectForTest(instance(mock(MongoClient))); - - const jobRepository = mock(JobRepository); - const executionsRepository = mock(ExecutionsRepository); - - jest.mock('../../src/repository/getRepository', { - getJobRepository: () => instance(jobRepository), - getExecutionsRepository: () => instance(executionsRepository), - } as any); - - return { jobRepository, executionsRepository }; -} From 0154aa662734437a4b3b8427c0ccfea662c22769 Mon Sep 17 00:00:00 2001 From: Niklas Eicker Date: Thu, 26 Aug 2021 21:25:58 +0200 Subject: [PATCH 03/13] refactor: Replace withDefaults with fromMomoJob Signed-off-by: Niklas Eicker --- src/job/Job.ts | 6 +++++- src/job/withDefaults.ts | 6 ------ src/schedule/Schedule.ts | 4 ++-- test/job/{withDefaults.spec.ts => Job.ts} | 6 +++--- test/job/define.spec.ts | 8 ++++---- test/job/validate.spec.ts | 15 +++++++-------- test/schedule/Schedule.integration.spec.ts | 4 ++-- test/utils/createJobEntity.ts | 4 ++-- 8 files changed, 25 insertions(+), 28 deletions(-) delete mode 100644 src/job/withDefaults.ts rename test/job/{withDefaults.spec.ts => Job.ts} (60%) diff --git a/src/job/Job.ts b/src/job/Job.ts index ea9e4597..d51f17da 100644 --- a/src/job/Job.ts +++ b/src/job/Job.ts @@ -1,4 +1,4 @@ -import { Handler } from './MomoJob'; +import { Handler, MomoJob } from './MomoJob'; import { WithoutId } from 'mongodb'; import { JobEntity } from '../repository/JobEntity'; @@ -15,3 +15,7 @@ export interface Job extends JobDefinition { immediate: boolean; handler: Handler; } + +export function fromMomoJob(job: MomoJob): Job { + return { immediate: false, concurrency: 1, maxRunning: 0, ...job }; +} diff --git a/src/job/withDefaults.ts b/src/job/withDefaults.ts deleted file mode 100644 index 0c0b9953..00000000 --- a/src/job/withDefaults.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { MomoJob } from './MomoJob'; -import { Job } from './Job'; - -export function withDefaults(job: MomoJob): Job { - return { immediate: false, concurrency: 1, maxRunning: 0, ...job }; -} diff --git a/src/schedule/Schedule.ts b/src/schedule/Schedule.ts index 859d8506..9609f135 100644 --- a/src/schedule/Schedule.ts +++ b/src/schedule/Schedule.ts @@ -1,7 +1,6 @@ import { sum } from 'lodash'; import { JobScheduler } from '../scheduler/JobScheduler'; import { MomoJob } from '../job/MomoJob'; -import { withDefaults } from '../job/withDefaults'; import { validate } from '../job/validate'; import { define } from '../job/define'; import { LogEmitter } from '../logging/LogEmitter'; @@ -9,6 +8,7 @@ import { getJobRepository } from '../repository/getRepository'; import { ExecutionStatus, JobResult } from '../job/ExecutionInfo'; import { MomoJobDescription } from '../job/MomoJobDescription'; import { MomoErrorType } from '../logging/error/MomoErrorType'; +import { fromMomoJob } from '../job/Job'; export class Schedule extends LogEmitter { private jobSchedulers: { [name: string]: JobScheduler } = {}; @@ -48,7 +48,7 @@ export class Schedule extends LogEmitter { * @param momoJob the job to define */ public async define(momoJob: MomoJob): Promise { - const job = withDefaults(momoJob); + const job = fromMomoJob(momoJob); if (!validate(job, this.logger)) { return; diff --git a/test/job/withDefaults.spec.ts b/test/job/Job.ts similarity index 60% rename from test/job/withDefaults.spec.ts rename to test/job/Job.ts index b5bb5a4c..26b559f9 100644 --- a/test/job/withDefaults.spec.ts +++ b/test/job/Job.ts @@ -1,9 +1,9 @@ -import { withDefaults } from '../../src/job/withDefaults'; +import { fromMomoJob } from '../../src/job/Job'; -describe('withDefaults', () => { +describe('fromMomoJob', () => { it('sets defaults', () => { const job = { name: 'test', interval: '1 second', handler: () => undefined }; - expect(withDefaults(job)).toMatchObject({ + expect(fromMomoJob(job)).toMatchObject({ ...job, immediate: false, concurrency: 1, diff --git a/test/job/define.spec.ts b/test/job/define.spec.ts index b1427ac7..a2a836c3 100644 --- a/test/job/define.spec.ts +++ b/test/job/define.spec.ts @@ -5,7 +5,7 @@ import { define } from '../../src/job/define'; import { createJobEntity } from '../utils/createJobEntity'; import { DateTime } from 'luxon'; import { ExecutionInfo } from '../../src'; -import { withDefaults } from '../../src/job/withDefaults'; +import { fromMomoJob } from '../../src/job/Job'; describe('define', () => { const job = { name: 'test', interval: '1 minute', handler: () => 'finished' }; @@ -17,7 +17,7 @@ describe('define', () => { when(jobRepository.find(deepEqual({ name: job.name }))).thenResolve([]); - await define(withDefaults(job)); + await define(fromMomoJob(job)); verify(jobRepository.save(deepEqual(createJobEntity(job)))).once(); }); @@ -28,7 +28,7 @@ describe('define', () => { when(jobRepository.find(deepEqual({ name: job.name }))).thenResolve([createJobEntity(job)]); const newInterval = '2 minutes'; - await define(withDefaults({ ...job, interval: newInterval })); + await define(fromMomoJob({ ...job, interval: newInterval })); verify(jobRepository.updateJob(job.name, deepEqual(createJobEntity({ ...job, interval: newInterval })))).once(); }); @@ -40,7 +40,7 @@ describe('define', () => { when(jobRepository.find(deepEqual({ name: job.name }))).thenResolve([duplicate, latest]); const newInterval = 'two minutes'; - await define(withDefaults({ ...job, interval: newInterval })); + await define(fromMomoJob({ ...job, interval: newInterval })); verify(jobRepository.delete(deepEqual([duplicate]))).once(); verify(jobRepository.updateJob(job.name, deepEqual(createJobEntity({ ...job, interval: newInterval })))).once(); diff --git a/test/job/validate.spec.ts b/test/job/validate.spec.ts index 3e0689dc..96e4345b 100644 --- a/test/job/validate.spec.ts +++ b/test/job/validate.spec.ts @@ -1,6 +1,5 @@ import { validate } from '../../src/job/validate'; -import { withDefaults } from '../../src/job/withDefaults'; -import { Job } from '../../src/job/Job'; +import { fromMomoJob, Job } from '../../src/job/Job'; import { Logger } from '../../src/logging/Logger'; import { MomoError, MomoErrorType } from '../../src'; @@ -13,12 +12,12 @@ describe('validate', () => { beforeEach(async () => jest.clearAllMocks()); it('validates a job', () => { - const job: Job = withDefaults({ name: 'test', interval: '1 minute', handler: () => 'finished' }); + const job: Job = fromMomoJob({ name: 'test', interval: '1 minute', handler: () => 'finished' }); expect(validate(job, logger)).toBe(true); }); it('reports error when interval cannot be parsed', () => { - const job: Job = withDefaults({ name: 'test', interval: 'not an interval', handler: () => 'finished' }); + const job: Job = fromMomoJob({ name: 'test', interval: 'not an interval', handler: () => 'finished' }); expect(validate(job, logger)).toBe(false); expect(logger.error).toHaveBeenCalledTimes(1); @@ -31,7 +30,7 @@ describe('validate', () => { }); it('reports error when interval is not positive', () => { - const job: Job = withDefaults({ name: 'test', interval: '-1 minute', handler: () => 'finished' }); + const job: Job = fromMomoJob({ name: 'test', interval: '-1 minute', handler: () => 'finished' }); expect(validate(job, logger)).toBe(false); expect(logger.error).toHaveBeenCalledTimes(1); @@ -44,7 +43,7 @@ describe('validate', () => { }); it('reports error when maxRunning is invalid', async () => { - const job: Job = withDefaults({ name: 'test', interval: '1 minute', handler: () => 'finished', maxRunning: -1 }); + const job: Job = fromMomoJob({ name: 'test', interval: '1 minute', handler: () => 'finished', maxRunning: -1 }); expect(validate(job, logger)).toBe(false); expect(logger.error).toHaveBeenCalledTimes(1); @@ -57,7 +56,7 @@ describe('validate', () => { }); it('reports error when concurrency is invalid', async () => { - const job: Job = withDefaults({ name: 'test', interval: '1 minute', handler: () => 'finished', concurrency: 0 }); + const job: Job = fromMomoJob({ name: 'test', interval: '1 minute', handler: () => 'finished', concurrency: 0 }); expect(validate(job, logger)).toBe(false); expect(logger.error).toHaveBeenCalledTimes(1); @@ -70,7 +69,7 @@ describe('validate', () => { }); it('reports error when concurrency > maxRunning', async () => { - const job: Job = withDefaults({ + const job: Job = fromMomoJob({ name: 'test', interval: '1 minute', handler: () => 'finished', diff --git a/test/schedule/Schedule.integration.spec.ts b/test/schedule/Schedule.integration.spec.ts index 9230f557..d1be86db 100644 --- a/test/schedule/Schedule.integration.spec.ts +++ b/test/schedule/Schedule.integration.spec.ts @@ -3,8 +3,8 @@ import { MongoMemoryServer } from 'mongodb-memory-server'; import { clear, MomoJob, MongoSchedule } from '../../src'; import { JobRepository } from '../../src/repository/JobRepository'; import { getJobRepository } from '../../src/repository/getRepository'; -import { withDefaults } from '../../src/job/withDefaults'; import { initLoggingForTests } from '../utils/logging'; +import { fromMomoJob } from '../../src/job/Job'; describe('Schedule', () => { const job: MomoJob = { @@ -62,7 +62,7 @@ describe('Schedule', () => { it('lists jobs on the schedule', async () => { await jobRepository.save( - withDefaults({ + fromMomoJob({ name: 'some job that is in the database but not on the schedule', handler: jest.fn(), interval: 'one minute', diff --git a/test/utils/createJobEntity.ts b/test/utils/createJobEntity.ts index a45c41d3..9fbf3895 100644 --- a/test/utils/createJobEntity.ts +++ b/test/utils/createJobEntity.ts @@ -1,8 +1,8 @@ import { MomoJob } from '../../src'; import { JobEntity } from '../../src/repository/JobEntity'; -import { withDefaults } from '../../src/job/withDefaults'; +import { fromMomoJob } from '../../src/job/Job'; // TODO remove this? export function createJobEntity(job: MomoJob): JobEntity { - return withDefaults(job); + return fromMomoJob(job); } From 6ecc95283bc1c66ff87a4190b5fb95992a1fb6b1 Mon Sep 17 00:00:00 2001 From: Ute Weiss Date: Fri, 27 Aug 2021 13:59:40 +0200 Subject: [PATCH 04/13] fix: update some tests Signed-off-by: Ute Weiss --- src/connect.ts | 3 ++ test/connect.integration.spec.ts | 4 +-- .../MongoScheduleBuilder.integration.spec.ts | 2 ++ test/schedule/Schedule.spec.ts | 31 +++++++++++-------- test/schedule/SchedulePing.spec.ts | 5 +-- test/scheduler/JobScheduler.spec.ts | 17 ++-------- test/utils/mockRepositories.ts | 18 +++++++++++ 7 files changed, 47 insertions(+), 33 deletions(-) create mode 100644 test/utils/mockRepositories.ts diff --git a/src/connect.ts b/src/connect.ts index 4b1df61d..e64f4ccc 100644 --- a/src/connect.ts +++ b/src/connect.ts @@ -13,6 +13,7 @@ export function connectForTest(testClient: MongoClient): void { export async function connect(connectionOptions: MomoConnectionOptions): Promise { if (mongoClient !== undefined) { + console.log('mongoClient already defined', mongoClient); return mongoClient; } @@ -25,7 +26,9 @@ export async function connect(connectionOptions: MomoConnectionOptions): Promise } export async function disconnect(): Promise { + console.log('disconnect momo'); await mongoClient?.close(); + mongoClient = undefined; } export function getConnection(): MongoClient { diff --git a/test/connect.integration.spec.ts b/test/connect.integration.spec.ts index 8a52e82a..3669a95d 100644 --- a/test/connect.integration.spec.ts +++ b/test/connect.integration.spec.ts @@ -15,13 +15,13 @@ describe('connect', () => { it('connects mongo', async () => { await connect({ url }); - expect(getConnection()).not.toThrow(); + expect(getConnection).not.toThrow(); }); it('disconnects mongo', async () => { await connect({ url }); await disconnect(); - expect(getConnection()).not.toThrow(); + expect(getConnection).not.toThrow(); }); }); diff --git a/test/schedule/MongoScheduleBuilder.integration.spec.ts b/test/schedule/MongoScheduleBuilder.integration.spec.ts index f8ce5a23..7fcb5a9f 100644 --- a/test/schedule/MongoScheduleBuilder.integration.spec.ts +++ b/test/schedule/MongoScheduleBuilder.integration.spec.ts @@ -2,6 +2,7 @@ import { MongoMemoryServer } from 'mongodb-memory-server'; import { clear, MomoConnectionOptions, MomoJob, MongoSchedule } from '../../src'; import { MongoScheduleBuilder } from '../../src/schedule/MongoScheduleBuilder'; +import { getExecutionsRepository } from '../../src/repository/getRepository'; describe('MongoScheduleBuilder', () => { const job1: MomoJob = { @@ -39,6 +40,7 @@ describe('MongoScheduleBuilder', () => { afterEach(async () => { await clear(); + await getExecutionsRepository().delete(); await mongoSchedule.disconnect(); }); diff --git a/test/schedule/Schedule.spec.ts b/test/schedule/Schedule.spec.ts index 60a4afb0..4a5f26ce 100644 --- a/test/schedule/Schedule.spec.ts +++ b/test/schedule/Schedule.spec.ts @@ -1,19 +1,28 @@ import { anyString, deepEqual, instance, mock, verify, when } from 'ts-mockito'; - -import { ExecutionStatus, MomoEvent, MomoJob, MongoSchedule } from '../../src'; -import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; -import { JobRepository } from '../../src/repository/JobRepository'; +import { MongoClient } from 'mongodb'; +import { ExecutionStatus, MomoConnectionOptions, MomoEvent, MomoJob, MongoSchedule } from '../../src'; import { initLoggingForTests } from '../utils/logging'; import { createJobEntity } from '../utils/createJobEntity'; -import { connectForTest } from '../../src/connect'; -import { MongoClient } from 'mongodb'; +import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; +import { JobRepository } from '../../src/repository/JobRepository'; + +const mongoClient = mock(MongoClient); + +jest.mock('../../src/connect', () => { + return { + connect: (_momoConnectionOptions: MomoConnectionOptions) => undefined, + disconnect: () => undefined, + getConnection: () => instance(mongoClient), + }; +}); + +const executionsRepository = mock(ExecutionsRepository); +const jobRepository = mock(JobRepository); -let jobRepository: JobRepository; -let executionsRepository: ExecutionsRepository; jest.mock('../../src/repository/getRepository', () => { return { - getJobRepository: () => instance(jobRepository), getExecutionsRepository: () => instance(executionsRepository), + getJobRepository: () => instance(jobRepository), }; }); @@ -29,12 +38,8 @@ describe('Schedule', () => { beforeEach(async () => { jest.clearAllMocks(); - jobRepository = mock(JobRepository); - executionsRepository = mock(ExecutionsRepository); - when(jobRepository.find(deepEqual({ name: job.name }))).thenResolve([]); - connectForTest(instance(mock(MongoClient))); mongoSchedule = await MongoSchedule.connect({ url: 'mongodb://does.not/matter' }); initLoggingForTests(mongoSchedule); }); diff --git a/test/schedule/SchedulePing.spec.ts b/test/schedule/SchedulePing.spec.ts index 60099290..b83fea31 100644 --- a/test/schedule/SchedulePing.spec.ts +++ b/test/schedule/SchedulePing.spec.ts @@ -1,15 +1,12 @@ -import { deepEqual, instance, verify, mock } from 'ts-mockito'; +import { deepEqual, instance, mock, verify } from 'ts-mockito'; import { SchedulePing } from '../../src/schedule/SchedulePing'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; import { sleep } from '../utils/sleep'; -import { JobRepository } from '../../src/repository/JobRepository'; -let jobRepository: JobRepository; let executionsRepository: ExecutionsRepository; jest.mock('../../src/repository/getRepository', () => { return { - getJobRepository: () => instance(jobRepository), getExecutionsRepository: () => instance(executionsRepository), }; }); diff --git a/test/scheduler/JobScheduler.spec.ts b/test/scheduler/JobScheduler.spec.ts index b4625508..5ad46088 100644 --- a/test/scheduler/JobScheduler.spec.ts +++ b/test/scheduler/JobScheduler.spec.ts @@ -1,8 +1,9 @@ import { anything, deepEqual, instance, mock, verify, when } from 'ts-mockito'; +import { mockRepositories } from '../utils/mockRepositories'; +const { executionsRepository, jobRepository } = mockRepositories(); + import { JobScheduler } from '../../src/scheduler/JobScheduler'; -import { JobRepository } from '../../src/repository/JobRepository'; -import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; import { Job } from '../../src/job/Job'; import { JobExecutor } from '../../src/executor/JobExecutor'; import { MomoError, MomoErrorType } from '../../src'; @@ -10,15 +11,6 @@ import { loggerForTests } from '../utils/logging'; import { createJobEntity } from '../utils/createJobEntity'; import { sleep } from '../utils/sleep'; -let jobRepository: JobRepository; -let executionsRepository: ExecutionsRepository; -jest.mock('../../src/repository/getRepository', () => { - return { - getJobRepository: () => instance(jobRepository), - getExecutionsRepository: () => instance(executionsRepository), - }; -}); - describe('JobScheduler', () => { const defaultJob = { name: 'test', @@ -38,9 +30,6 @@ describe('JobScheduler', () => { jest.clearAllMocks(); jobExecutor = mock(JobExecutor); - jobRepository = mock(JobRepository); - executionsRepository = mock(ExecutionsRepository); - when(jobExecutor.execute(anything())).thenResolve(); }); diff --git a/test/utils/mockRepositories.ts b/test/utils/mockRepositories.ts new file mode 100644 index 00000000..915d28ed --- /dev/null +++ b/test/utils/mockRepositories.ts @@ -0,0 +1,18 @@ +import { instance, mock } from 'ts-mockito'; + +import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; +import { JobRepository } from '../../src/repository/JobRepository'; + +export function mockRepositories() { + const executionsRepository = mock(ExecutionsRepository); + const jobRepository = mock(JobRepository); + + jest.mock('../../src/repository/getRepository', () => { + return { + getExecutionsRepository: () => instance(executionsRepository), + getJobRepository: () => instance(jobRepository), + }; + }); + + return { executionsRepository, jobRepository }; +} From 740152a18c70d8234732ad64be255dbfb0d3c121 Mon Sep 17 00:00:00 2001 From: Ute Weiss Date: Fri, 27 Aug 2021 15:41:09 +0200 Subject: [PATCH 05/13] refactor: remove "loose" database methods. Replace "loose" connection/repository handling with Connection class. BREAKING CHANGE: functions clear/check/connect/disconnect/isConnected are removed. Create MongoSchedule instead and use its methods. Signed-off-by: Ute Weiss --- README.md | 14 +-- src/Connection.ts | 46 +++++++ src/connect.ts | 39 ------ src/executor/JobExecutor.ts | 13 +- src/index.ts | 5 +- src/isConnected.ts | 0 src/job/check.ts | 12 -- src/job/clear.ts | 8 -- src/job/define.ts | 22 ---- src/job/keepLatest.ts | 21 ---- src/job/list.ts | 19 --- src/repository/JobRepository.ts | 61 +++++++++ src/repository/createJobEntity.ts | 13 ++ src/repository/getRepository.ts | 20 --- src/schedule/MongoSchedule.ts | 36 ++++-- src/schedule/MongoScheduleBuilder.ts | 2 +- src/schedule/Schedule.ts | 53 ++++++-- src/schedule/SchedulePing.ts | 15 ++- src/scheduler/JobScheduler.ts | 31 +++-- test/connect.integration.spec.ts | 27 ---- test/executor/JobExecutor.spec.ts | 22 ++-- test/job/{Job.ts => Job.spec.ts} | 0 test/job/check.spec.ts | 44 ------- test/job/clear.integration.spec.ts | 33 ----- test/job/define.spec.ts | 48 ------- test/job/keepLatest.spec.ts | 46 ------- test/job/list.spec.ts | 53 -------- .../ExecutionsRepository.integration.spec.ts | 10 +- .../JobRepository.integration.spec.ts | 118 ++++++++++++++++-- test/schedule/MongoSchedule.spec.ts | 23 ++-- .../MongoScheduleBuilder.integration.spec.ts | 11 +- test/schedule/Schedule.integration.spec.ts | 14 ++- test/schedule/Schedule.spec.ts | 33 +++-- test/schedule/SchedulePing.spec.ts | 14 +-- test/schedule/momo.integration.spec.ts | 19 +-- test/scheduler/JobScheduler.spec.ts | 15 ++- test/utils/createJobEntity.ts | 8 -- test/utils/mockRepositories.ts | 18 --- 38 files changed, 426 insertions(+), 560 deletions(-) create mode 100644 src/Connection.ts delete mode 100644 src/connect.ts delete mode 100644 src/isConnected.ts delete mode 100644 src/job/check.ts delete mode 100644 src/job/clear.ts delete mode 100644 src/job/define.ts delete mode 100644 src/job/keepLatest.ts delete mode 100644 src/job/list.ts create mode 100644 src/repository/createJobEntity.ts delete mode 100644 src/repository/getRepository.ts delete mode 100644 test/connect.integration.spec.ts rename test/job/{Job.ts => Job.spec.ts} (100%) delete mode 100644 test/job/check.spec.ts delete mode 100644 test/job/clear.integration.spec.ts delete mode 100644 test/job/define.spec.ts delete mode 100644 test/job/keepLatest.spec.ts delete mode 100644 test/job/list.spec.ts delete mode 100644 test/utils/createJobEntity.ts delete mode 100644 test/utils/mockRepositories.ts diff --git a/README.md b/README.md index 4596163d..9d97d0cc 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ If the parameter is omitted, all jobs are started/stopped/cancelled/removed. | removeJob | `string` | Stops and removes the job with the provided name (if on the schedule) from both the schedule and the database. | | count | `boolean` (optional) | Returns the number of jobs on the schedule. Only started jobs are counted if parameter is set to true. | | list | | Returns descriptions of all jobs on the schedule. | +| check | `string` | Returns execution information of the job with the provided name from the database. This also works if the job is not on the schedule. | +| clear | | Removes all jobs from the database. This also removes jobs that are not on this schedule, but were defined by other schedules. However, does NOT stop job executions - this will cause currently running jobs to fail. Consider using stop/cancel/remove methods instead! | | get | `string` | Returns a description of the job. Returns undefined if no job with the provided name is defined. | | run | `string` | Runs the job with the provided name once, immediately. Note that `maxRunning` is respected, ie. the execution is skipped if the job is already running `maxRunning` times. | | on | `'debug'` or `'error'`, `function` | Define a callback for debug or error events. | @@ -103,18 +105,6 @@ mongoSchedule.on('debug', ({ data, message }: MomoEvent) => { | error | type | `MomoErrorType` | `'defining job failed'` or `'scheduling job failed'` or `'executing job failed'` or `'stopping job failed'` | | error | error (optional) | `Error` | The root cause of the error. | -### Other functions - -momo-scheduler also includes some utility functions to retrieve information on momo jobs from its database: - -| function | parameter | description | -|---------------|-----------|-------------| -| `connect` | `{ url: string }` | Establishes a connection with MongoDB. If you want to schedule jobs, you should use `MongoSchedule.connect` instead to create a connected schedule. | -| `isConnected` | | Returns true if a connection to MongoDB was established and false otherwise. | -| `check` | `string` | Retrieves information on the last job execution. Returns undefined if job cannot be found or was never executed. | -| `list` | | Lists all jobs. | -| `clear` | | Removes all jobs from the database. Do not use this if MongoSchedule has already been started. Subsequent execution of all jobs will fail! | - ## License This project is open source and licensed under [Apache 2.0](LICENSE). diff --git a/src/Connection.ts b/src/Connection.ts new file mode 100644 index 00000000..5412031a --- /dev/null +++ b/src/Connection.ts @@ -0,0 +1,46 @@ +import { JobRepository, JOBS_COLLECTION_NAME } from './repository/JobRepository'; +import { MongoClient } from 'mongodb'; +import { ExecutionsRepository } from './repository/ExecutionsRepository'; + +export interface MomoConnectionOptions { + url: string; +} + +export class Connection { + private executionsRepository?: ExecutionsRepository; + private jobRepository?: JobRepository; + + constructor(private readonly mongoClient: MongoClient) {} + + static async create(connectionOptions: MomoConnectionOptions): Promise { + const mongoClient = await MongoClient.connect(connectionOptions.url); + + await mongoClient.db().collection(JOBS_COLLECTION_NAME).createIndex({ name: 1 }, { name: 'job_name_index' }); + await mongoClient + .db() + .collection(JOBS_COLLECTION_NAME) + .createIndex({ scheduleId: 1 }, { name: 'schedule_id_index' }); + + return new Connection(mongoClient); + } + + getExecutionsRepository(): ExecutionsRepository { + if (this.executionsRepository === undefined) { + this.executionsRepository = new ExecutionsRepository(this.mongoClient); + } + return this.executionsRepository!; + } + + getJobRepository(): JobRepository { + if (this.jobRepository === undefined) { + this.jobRepository = new JobRepository(this.mongoClient); + } + return this.jobRepository!; + } + + async disconnect() { + await this.mongoClient.close(); + } + + // TODO connection instance unusable after calling disconnect +} diff --git a/src/connect.ts b/src/connect.ts deleted file mode 100644 index e64f4ccc..00000000 --- a/src/connect.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { JOBS_COLLECTION_NAME } from './repository/JobRepository'; -import { MongoClient } from 'mongodb'; - -let mongoClient: MongoClient | undefined; - -export interface MomoConnectionOptions { - url: string; -} - -export function connectForTest(testClient: MongoClient): void { - mongoClient = testClient; -} - -export async function connect(connectionOptions: MomoConnectionOptions): Promise { - if (mongoClient !== undefined) { - console.log('mongoClient already defined', mongoClient); - return mongoClient; - } - - mongoClient = await MongoClient.connect(connectionOptions.url); - - await mongoClient.db().collection(JOBS_COLLECTION_NAME).createIndex({ name: 1 }, { name: 'job_name_index' }); - await mongoClient.db().collection(JOBS_COLLECTION_NAME).createIndex({ scheduleId: 1 }, { name: 'schedule_id_index' }); - - return mongoClient; -} - -export async function disconnect(): Promise { - console.log('disconnect momo'); - await mongoClient?.close(); - mongoClient = undefined; -} - -export function getConnection(): MongoClient { - if (mongoClient === undefined) { - throw new Error('momo is not connected, please call connect() first'); - } - return mongoClient; -} diff --git a/src/executor/JobExecutor.ts b/src/executor/JobExecutor.ts index 8dd5aaad..cbb20521 100644 --- a/src/executor/JobExecutor.ts +++ b/src/executor/JobExecutor.ts @@ -3,9 +3,10 @@ import { DateTime } from 'luxon'; import { JobEntity } from '../repository/JobEntity'; import { MomoErrorType } from '../logging/error/MomoErrorType'; import { ExecutionStatus, JobResult } from '../job/ExecutionInfo'; -import { getExecutionsRepository, getJobRepository } from '../repository/getRepository'; import { Logger } from '../logging/Logger'; import { Handler } from '../job/MomoJob'; +import { ExecutionsRepository } from '../repository/ExecutionsRepository'; +import { JobRepository } from '../repository/JobRepository'; export class JobExecutor { private stopped = false; @@ -13,6 +14,8 @@ export class JobExecutor { constructor( private readonly handler: Handler, private readonly scheduleId: string, + private readonly executionsRepository: ExecutionsRepository, + private readonly jobRepository: JobRepository, private readonly logger: Logger ) {} @@ -21,9 +24,7 @@ export class JobExecutor { } async execute(jobEntity: JobEntity): Promise { - const executionsRepository = getExecutionsRepository(); - - const { added, running } = await executionsRepository.addExecution( + const { added, running } = await this.executionsRepository.addExecution( this.scheduleId, jobEntity.name, jobEntity.maxRunning @@ -40,7 +41,7 @@ export class JobExecutor { const { started, result } = await this.executeHandler(jobEntity); - await getJobRepository().updateJob(jobEntity.name, { + await this.jobRepository.updateJob(jobEntity.name, { executionInfo: { lastStarted: started.toISO(), lastFinished: DateTime.now().toISO(), @@ -55,7 +56,7 @@ export class JobExecutor { }); if (!this.stopped) { - await executionsRepository.removeExecution(this.scheduleId, jobEntity.name); + await this.executionsRepository.removeExecution(this.scheduleId, jobEntity.name); } return result; diff --git a/src/index.ts b/src/index.ts index 8b8fa8b7..1dce2933 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ export { MongoSchedule } from './schedule/MongoSchedule'; -export { connect, MomoConnectionOptions } from './connect'; +export { MomoConnectionOptions } from './Connection'; export { MomoError } from './logging/error/MomoError'; export { MomoErrorType } from './logging/error/MomoErrorType'; export { MomoEvent, MomoErrorEvent, MomoEventData } from './logging/MomoEvents'; @@ -7,6 +7,3 @@ export { MomoJob } from './job/MomoJob'; export { MomoJobDescription, JobSchedulerStatus } from './job/MomoJobDescription'; export { ExecutionStatus } from './job/ExecutionInfo'; export { ExecutionInfo } from './job/ExecutionInfo'; -export { check } from './job/check'; -export { clear } from './job/clear'; -export { list } from './job/list'; diff --git a/src/isConnected.ts b/src/isConnected.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/job/check.ts b/src/job/check.ts deleted file mode 100644 index dc224887..00000000 --- a/src/job/check.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ExecutionInfo } from './ExecutionInfo'; -import { getJobRepository } from '../repository/getRepository'; - -/** - * Retrieves execution information about the job from the database. Returns undefined if the job cannot be found or was never executed. - * - * @param name the job to check - */ -export async function check(name: string): Promise { - const job = await getJobRepository().findOne({ name }); - return job?.executionInfo; -} diff --git a/src/job/clear.ts b/src/job/clear.ts deleted file mode 100644 index 9645e231..00000000 --- a/src/job/clear.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { getJobRepository } from '../repository/getRepository'; - -/** - * Removes all jobs from the database. - */ -export async function clear(): Promise { - await getJobRepository().delete({}); -} diff --git a/src/job/define.ts b/src/job/define.ts deleted file mode 100644 index 4463d67d..00000000 --- a/src/job/define.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Job } from './Job'; -import { getJobRepository } from '../repository/getRepository'; -import { keepLatest } from './keepLatest'; -import { Logger } from '../logging/Logger'; - -export async function define(job: Job, logger?: Logger): Promise { - const { name, interval, concurrency, maxRunning } = job; - - logger?.debug('define job', { name, concurrency, interval, maxRunning }); - - const jobRepository = getJobRepository(); - const old = await keepLatest(name, logger); - - if (old) { - logger?.debug('update job in database', { name }); - await jobRepository.updateJob(name, job); - return; - } - - logger?.debug('save job to database', { name }); - await jobRepository.save(job); -} diff --git a/src/job/keepLatest.ts b/src/job/keepLatest.ts deleted file mode 100644 index cb1a4f78..00000000 --- a/src/job/keepLatest.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { JobEntity } from '../repository/JobEntity'; -import { getJobRepository } from '../repository/getRepository'; -import { findLatest } from './findLatest'; -import { Logger } from '../logging/Logger'; - -export async function keepLatest(name: string, logger?: Logger): Promise { - const jobRepository = getJobRepository(); - const jobs = await jobRepository.find({ name }); - - if (jobs.length === 1) return jobs[0]; - - const latest = findLatest(jobs); - if (!latest) return undefined; - - logger?.debug('duplicate job, keep latest only', { name, count: jobs.length }); - - jobs.splice(jobs.indexOf(latest), 1); - await jobRepository.delete(jobs); // TODO does this work? - - return latest; -} diff --git a/src/job/list.ts b/src/job/list.ts deleted file mode 100644 index d30a292c..00000000 --- a/src/job/list.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { MomoJobStatus } from './Job'; -import { getJobRepository } from '../repository/getRepository'; - -/** - * Lists all jobs stored in the database. - */ -export async function list(): Promise { - const jobs = await getJobRepository().find(); - - return jobs.map((job) => { - return { - name: job.name, - interval: job.interval, - concurrency: job.concurrency, - maxRunning: job.maxRunning, - executionInfo: job.executionInfo, - }; - }); -} diff --git a/src/repository/JobRepository.ts b/src/repository/JobRepository.ts index ee9e6fb3..9539ec3c 100644 --- a/src/repository/JobRepository.ts +++ b/src/repository/JobRepository.ts @@ -1,6 +1,11 @@ import { MongoClient } from 'mongodb'; import { JobEntity } from './JobEntity'; import { Repository } from './Repository'; +import { ExecutionInfo } from '../job/ExecutionInfo'; +import { Job, MomoJobStatus } from '../job/Job'; +import { Logger } from '../logging/Logger'; +import { findLatest } from '../job/findLatest'; +import { createJobEntity } from './createJobEntity'; export const JOBS_COLLECTION_NAME = 'jobs'; @@ -9,6 +14,62 @@ export class JobRepository extends Repository { super(mongoClient, JOBS_COLLECTION_NAME); } + async check(name: string): Promise { + const job = await this.findOne({ name }); + return job?.executionInfo; + } + + async clear(): Promise { + await this.delete(); + } + + async define(job: Job, logger?: Logger): Promise { + const { name, interval, concurrency, maxRunning } = job; + + logger?.debug('define job', { name, concurrency, interval, maxRunning }); + + const old = await this.keepLatest(name, logger); + + if (old) { + logger?.debug('update job in database', { name }); + await this.updateJob(name, createJobEntity(job)); + return; + } + + logger?.debug('save job to database', { name }); + await this.save(createJobEntity(job)); + } + + private async keepLatest(name: string, logger?: Logger): Promise { + const jobs = await this.find({ name }); + + if (jobs.length === 1) return jobs[0]; + + const latest = findLatest(jobs); + if (!latest) return undefined; + + logger?.debug('duplicate job, keep latest only', { name, count: jobs.length }); + + jobs.splice(jobs.indexOf(latest), 1); + await this.delete({ _id: { $in: jobs.map(({ _id }) => _id) } }); + + return latest; + } + + async list(): Promise { + const jobs = await this.find(); + + return jobs.map((job) => { + return { + name: job.name, + interval: job.interval, + concurrency: job.concurrency, + maxRunning: job.maxRunning, + executionInfo: job.executionInfo, + }; + }); + } + async updateJob(name: string, update: Partial): Promise { await this.updateOne({ name }, { $set: update }); } diff --git a/src/repository/createJobEntity.ts b/src/repository/createJobEntity.ts new file mode 100644 index 00000000..cf315df9 --- /dev/null +++ b/src/repository/createJobEntity.ts @@ -0,0 +1,13 @@ +import { MomoJob } from '../index'; +import { JobEntity } from './JobEntity'; +import { fromMomoJob } from '../job/Job'; + +export function createJobEntity(momoJob: MomoJob): JobEntity { + const job = fromMomoJob(momoJob); + return { + name: job.name, + interval: job.interval, + maxRunning: job.maxRunning, + concurrency: job.concurrency, + }; +} diff --git a/src/repository/getRepository.ts b/src/repository/getRepository.ts deleted file mode 100644 index 03882a62..00000000 --- a/src/repository/getRepository.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { getConnection } from '../connect'; -import { ExecutionsRepository } from './ExecutionsRepository'; -import { JobRepository } from './JobRepository'; - -let jobRepository: JobRepository | undefined; -let executionsRepository: ExecutionsRepository | undefined; - -export function getJobRepository(): JobRepository { - if (jobRepository === undefined) { - jobRepository = new JobRepository(getConnection()); - } - return jobRepository; -} - -export function getExecutionsRepository(): ExecutionsRepository { - if (executionsRepository === undefined) { - executionsRepository = new ExecutionsRepository(getConnection()); - } - return executionsRepository; -} diff --git a/src/schedule/MongoSchedule.ts b/src/schedule/MongoSchedule.ts index d095dfa5..fefa5331 100644 --- a/src/schedule/MongoSchedule.ts +++ b/src/schedule/MongoSchedule.ts @@ -1,15 +1,21 @@ import { v4 as uuid } from 'uuid'; import { Schedule } from './Schedule'; import { SchedulePing } from './SchedulePing'; -import { connect, disconnect, MomoConnectionOptions } from '../connect'; -import { getExecutionsRepository } from '../repository/getRepository'; +import { Connection, MomoConnectionOptions } from '../Connection'; +import { JobRepository } from '../repository/JobRepository'; +import { ExecutionsRepository } from '../repository/ExecutionsRepository'; export class MongoSchedule extends Schedule { private readonly schedulePing: SchedulePing; - private constructor(scheduleId: string) { - super(scheduleId); - this.schedulePing = new SchedulePing(scheduleId, this.logger); + private constructor( + scheduleId: string, + private readonly disconnectFct: () => Promise, + executionsRepository: ExecutionsRepository, + jobRepository: JobRepository + ) { + super(scheduleId, executionsRepository, jobRepository); + this.schedulePing = new SchedulePing(scheduleId, executionsRepository, this.logger); } /** @@ -18,11 +24,23 @@ export class MongoSchedule extends Schedule { * @param connectionOptions for the MongoDB connection to establish */ public static async connect(connectionOptions: MomoConnectionOptions): Promise { + const connection = await Connection.create(connectionOptions); + + const executionsRepository = connection.getExecutionsRepository(); + const jobRepository = connection.getJobRepository(); + const scheduleId = uuid(); - const mongoSchedule = new MongoSchedule(scheduleId); - await connect(connectionOptions); - await getExecutionsRepository().addSchedule(scheduleId); + await executionsRepository.addSchedule(scheduleId); + + const mongoSchedule = new MongoSchedule( + scheduleId, + connection.disconnect.bind(connection), + executionsRepository, + jobRepository + ); + mongoSchedule.schedulePing.start(); + return mongoSchedule; } @@ -32,6 +50,6 @@ export class MongoSchedule extends Schedule { public async disconnect(): Promise { await this.cancel(); await this.schedulePing.stop(); - await disconnect(); + await this.disconnectFct(); } } diff --git a/src/schedule/MongoScheduleBuilder.ts b/src/schedule/MongoScheduleBuilder.ts index 90246ecb..2073ad7b 100644 --- a/src/schedule/MongoScheduleBuilder.ts +++ b/src/schedule/MongoScheduleBuilder.ts @@ -1,5 +1,5 @@ import { MomoJob } from '../job/MomoJob'; -import { MomoConnectionOptions } from '../connect'; +import { MomoConnectionOptions } from '../Connection'; import { MongoSchedule } from './MongoSchedule'; export class MongoScheduleBuilder { diff --git a/src/schedule/Schedule.ts b/src/schedule/Schedule.ts index 9609f135..728bba80 100644 --- a/src/schedule/Schedule.ts +++ b/src/schedule/Schedule.ts @@ -2,18 +2,22 @@ import { sum } from 'lodash'; import { JobScheduler } from '../scheduler/JobScheduler'; import { MomoJob } from '../job/MomoJob'; import { validate } from '../job/validate'; -import { define } from '../job/define'; import { LogEmitter } from '../logging/LogEmitter'; -import { getJobRepository } from '../repository/getRepository'; -import { ExecutionStatus, JobResult } from '../job/ExecutionInfo'; +import { ExecutionInfo, ExecutionStatus, JobResult } from '../job/ExecutionInfo'; import { MomoJobDescription } from '../job/MomoJobDescription'; import { MomoErrorType } from '../logging/error/MomoErrorType'; import { fromMomoJob } from '../job/Job'; +import { JobRepository } from '../repository/JobRepository'; +import { ExecutionsRepository } from '../repository/ExecutionsRepository'; export class Schedule extends LogEmitter { private jobSchedulers: { [name: string]: JobScheduler } = {}; - constructor(private readonly scheduleId: string) { + constructor( + private readonly scheduleId: string, + private readonly executionsRepository: ExecutionsRepository, + private readonly jobRepository: JobRepository + ) { super(); } @@ -46,18 +50,27 @@ export class Schedule extends LogEmitter { * the scheduler has to be started again to pick up the change. * * @param momoJob the job to define + * @returns true if jobs was defined, false if the job was invalid */ - public async define(momoJob: MomoJob): Promise { + public async define(momoJob: MomoJob): Promise { const job = fromMomoJob(momoJob); if (!validate(job, this.logger)) { - return; + return false; } await this.stopJob(job.name); - await define(job, this.logger); + await this.jobRepository.define(job, this.logger); + + this.jobSchedulers[job.name] = JobScheduler.forJob( + this.scheduleId, + job, + this.logger, + this.executionsRepository, + this.jobRepository + ); - this.jobSchedulers[job.name] = JobScheduler.forJob(this.scheduleId, job, this.logger); + return true; } /** @@ -170,7 +183,7 @@ export class Schedule extends LogEmitter { public async removeJob(name: string): Promise { await this.cancelJob(name); this.logger.debug('remove', { name }); - await getJobRepository().deleteOne({ name }); + await this.jobRepository.deleteOne({ name }); } /** @@ -180,7 +193,7 @@ export class Schedule extends LogEmitter { const names = Object.keys(this.jobSchedulers); await this.cancel(); this.logger.debug('remove all jobs', { names: names.join(', ') }); - await getJobRepository().delete({ name: { $in: names } }); + await this.jobRepository.delete({ name: { $in: names } }); } /** @@ -202,6 +215,26 @@ export class Schedule extends LogEmitter { await Promise.all(Object.values(this.jobSchedulers).map((jobScheduler) => jobScheduler.getJobDescription())) ).filter((jobDescription): jobDescription is MomoJobDescription => jobDescription !== undefined); } + /** + * Retrieves execution information about the job from the database. Returns undefined if the job cannot be found or was never executed. + * + * @param name the job to check + */ + public async check(name: string): Promise { + return this.jobRepository.check(name); + } + + /** + * Removes all jobs from the database. + * + * NOTE: + * This also removes jobs that are not on this schedule, but were defined by other schedules. + * However, does NOT stop job executions - this will cause currently running jobs to fail. + * Consider using stop/cancel/remove methods instead! + */ + public async clear(): Promise { + await this.jobRepository.delete(); + } /** * Returns the description of a job or undefined if no job with the given name is on the schedule. diff --git a/src/schedule/SchedulePing.ts b/src/schedule/SchedulePing.ts index b2cbdb4b..e4c6a5c0 100644 --- a/src/schedule/SchedulePing.ts +++ b/src/schedule/SchedulePing.ts @@ -1,5 +1,5 @@ import { Logger } from '../logging/Logger'; -import { getExecutionsRepository } from '../repository/getRepository'; +import { ExecutionsRepository } from '../repository/ExecutionsRepository'; export const defaultInterval = 60 * 1000; @@ -7,16 +7,19 @@ export class SchedulePing { public static interval = defaultInterval; private handle?: NodeJS.Timeout; - constructor(private readonly scheduleId: string, private readonly logger: Logger) {} + constructor( + private readonly scheduleId: string, + private readonly executionsRepository: ExecutionsRepository, + private readonly logger: Logger + ) {} start(): void { if (this.handle !== undefined) { return; } - const executionsRepository = getExecutionsRepository(); this.handle = setInterval(async () => { - await executionsRepository.ping(this.scheduleId); - const deletedCount = await executionsRepository.clean(); + await this.executionsRepository.ping(this.scheduleId); + const deletedCount = await this.executionsRepository.clean(); if (deletedCount > 0) { this.logger.debug('removed dead executions', { schedules: deletedCount }); } @@ -28,6 +31,6 @@ export class SchedulePing { this.logger.debug('stop SchedulerPing', { scheduleId: this.scheduleId }); clearInterval(this.handle); } - await getExecutionsRepository().deleteOne({ scheduleId: this.scheduleId }); + await this.executionsRepository.deleteOne({ scheduleId: this.scheduleId }); } } diff --git a/src/scheduler/JobScheduler.ts b/src/scheduler/JobScheduler.ts index 20766357..605c7a46 100644 --- a/src/scheduler/JobScheduler.ts +++ b/src/scheduler/JobScheduler.ts @@ -7,11 +7,12 @@ import { setIntervalWithDelay, TimeoutHandle } from './setIntervalWithDelay'; import { calculateDelay } from './calculateDelay'; import { MomoError } from '../logging/error/MomoError'; import { MomoErrorType } from '../logging/error/MomoErrorType'; -import { getExecutionsRepository, getJobRepository } from '../repository/getRepository'; import { Logger } from '../logging/Logger'; import { ExecutionStatus, JobResult } from '../job/ExecutionInfo'; import { DateTime } from 'luxon'; import { jobDescriptionFromEntity, MomoJobDescription } from '../job/MomoJobDescription'; +import { ExecutionsRepository } from '../repository/ExecutionsRepository'; +import { JobRepository } from '../repository/JobRepository'; export class JobScheduler { private jobHandle?: TimeoutHandle; @@ -23,12 +24,20 @@ export class JobScheduler { private readonly immediate: boolean, private readonly jobExecutor: JobExecutor, private readonly scheduleId: string, + private readonly executionsRepository: ExecutionsRepository, + private readonly jobRepository: JobRepository, private readonly logger: Logger ) {} - static forJob(scheduleId: string, job: Job, logger: Logger): JobScheduler { - const executor = new JobExecutor(job.handler, scheduleId, logger); - return new JobScheduler(job.name, job.immediate, executor, scheduleId, logger); + static forJob( + scheduleId: string, + job: Job, + logger: Logger, + executionsRepository: ExecutionsRepository, + jobRepository: JobRepository + ): JobScheduler { + const executor = new JobExecutor(job.handler, scheduleId, executionsRepository, jobRepository, logger); + return new JobScheduler(job.name, job.immediate, executor, scheduleId, executionsRepository, jobRepository, logger); } getUnexpectedErrorCount(): number { @@ -40,7 +49,7 @@ export class JobScheduler { } async getJobDescription(): Promise { - const jobEntity = await getJobRepository().findOne({ name: this.jobName }); + const jobEntity = await this.jobRepository.findOne({ name: this.jobName }); if (!jobEntity) { this.logger.error( 'get job description - job not found', @@ -51,7 +60,7 @@ export class JobScheduler { return; } - const running = await getExecutionsRepository().countRunningExecutions(jobEntity.name); + const running = await this.executionsRepository.countRunningExecutions(jobEntity.name); const schedulerStatus = this.interval !== undefined ? { interval: this.interval, running } : undefined; return { ...jobDescriptionFromEntity(jobEntity), schedulerStatus }; @@ -60,7 +69,7 @@ export class JobScheduler { async start(): Promise { await this.stop(); - const jobEntity = await getJobRepository().findOne({ name: this.jobName }); + const jobEntity = await this.jobRepository.findOne({ name: this.jobName }); if (!jobEntity) { this.logger.error( 'cannot schedule job', @@ -93,7 +102,7 @@ export class JobScheduler { if (this.jobHandle) { clearInterval(this.jobHandle.get()); this.jobExecutor.stop(); - await getExecutionsRepository().removeJob(this.scheduleId, this.jobName); + await this.executionsRepository.removeJob(this.scheduleId, this.jobName); this.jobHandle = undefined; this.interval = undefined; } @@ -101,7 +110,7 @@ export class JobScheduler { async executeOnce(): Promise { try { - const jobEntity = await getJobRepository().findOne({ name: this.jobName }); + const jobEntity = await this.jobRepository.findOne({ name: this.jobName }); if (!jobEntity) { this.logger.error( 'job not found, skip execution', @@ -125,7 +134,7 @@ export class JobScheduler { async executeConcurrently(): Promise { try { - const jobEntity = await getJobRepository().findOne({ name: this.jobName }); + const jobEntity = await this.jobRepository.findOne({ name: this.jobName }); if (!jobEntity) { this.logger.error( 'job not found, skip execution', @@ -136,7 +145,7 @@ export class JobScheduler { return; } - const running = await getExecutionsRepository().countRunningExecutions(jobEntity.name); + const running = await this.executionsRepository.countRunningExecutions(jobEntity.name); const numToExecute = jobEntity.maxRunning > 0 ? min([jobEntity.concurrency, jobEntity.maxRunning - running]) ?? jobEntity.concurrency diff --git a/test/connect.integration.spec.ts b/test/connect.integration.spec.ts deleted file mode 100644 index 3669a95d..00000000 --- a/test/connect.integration.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { connect, disconnect, getConnection } from '../src/connect'; - -describe('connect', () => { - let mongo: MongoMemoryServer; - let url: string; - - beforeAll(async () => { - mongo = await MongoMemoryServer.create(); - url = mongo.getUri(); - }); - - afterAll(async () => await mongo.stop()); - - it('connects mongo', async () => { - await connect({ url }); - - expect(getConnection).not.toThrow(); - }); - - it('disconnects mongo', async () => { - await connect({ url }); - await disconnect(); - - expect(getConnection).not.toThrow(); - }); -}); diff --git a/test/executor/JobExecutor.spec.ts b/test/executor/JobExecutor.spec.ts index 0b4f8be5..6bdf58bf 100644 --- a/test/executor/JobExecutor.spec.ts +++ b/test/executor/JobExecutor.spec.ts @@ -7,17 +7,9 @@ import { Job } from '../../src/job/Job'; import { ExecutionStatus, MomoErrorType } from '../../src'; import { loggerForTests } from '../utils/logging'; -let jobRepository: JobRepository; -let executionsRepository: ExecutionsRepository; -jest.mock('../../src/repository/getRepository', () => { - return { - getJobRepository: () => instance(jobRepository), - getExecutionsRepository: () => instance(executionsRepository), - }; -}); - describe('JobExecutor', () => { const scheduleId = '123'; + const errorFn = jest.fn(); const handler = jest.fn(); const job: Job = { name: 'test', @@ -28,7 +20,8 @@ describe('JobExecutor', () => { handler, }; - const errorFn = jest.fn(); + let jobRepository: JobRepository; + let executionsRepository: ExecutionsRepository; let jobExecutor: JobExecutor; beforeEach(() => { @@ -36,12 +29,19 @@ describe('JobExecutor', () => { jobRepository = mock(JobRepository); executionsRepository = mock(ExecutionsRepository); + when(executionsRepository.addExecution(scheduleId, job.name, job.maxRunning)).thenResolve({ added: true, running: 0, }); - jobExecutor = new JobExecutor(job.handler, scheduleId, loggerForTests(errorFn)); + jobExecutor = new JobExecutor( + job.handler, + scheduleId, + instance(executionsRepository), + instance(jobRepository), + loggerForTests(errorFn) + ); }); it('executes job', async () => { diff --git a/test/job/Job.ts b/test/job/Job.spec.ts similarity index 100% rename from test/job/Job.ts rename to test/job/Job.spec.ts diff --git a/test/job/check.spec.ts b/test/job/check.spec.ts deleted file mode 100644 index f2bc0531..00000000 --- a/test/job/check.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { DateTime } from 'luxon'; -import { anyString, deepEqual, instance, mock, when } from 'ts-mockito'; - -import { check, clear, ExecutionInfo, ExecutionStatus } from '../../src'; -import { JobEntity } from '../../src/repository/JobEntity'; -import { JobRepository } from '../../src/repository/JobRepository'; - -let jobRepository: JobRepository; -jest.mock('../../src/repository/getRepository', () => { - return { - getJobRepository: () => instance(jobRepository), - }; -}); - -describe('check', () => { - const name = 'test'; - - beforeEach(() => { - jobRepository = mock(JobRepository); - when(jobRepository.findOne(anyString())).thenResolve(undefined); - }); - - afterEach(async () => { - jest.resetAllMocks(); - await clear(); - }); - - it('returns executionInfo', async () => { - const executionInfo: ExecutionInfo = { - lastStarted: DateTime.now().toISO(), - lastFinished: DateTime.now().toISO(), - lastResult: { status: ExecutionStatus.finished }, - }; - when(jobRepository.findOne(deepEqual({ name }))).thenResolve({ name, executionInfo } as JobEntity); - - const result = await check(name); - - expect(result).toEqual(executionInfo); - }); - - it('returns nothing if job not found', async () => { - expect(await check(name)).toBeUndefined(); - }); -}); diff --git a/test/job/clear.integration.spec.ts b/test/job/clear.integration.spec.ts deleted file mode 100644 index 0ef02db9..00000000 --- a/test/job/clear.integration.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { clear } from '../../src'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { JobRepository } from '../../src/repository/JobRepository'; -import { connect, disconnect } from '../../src/connect'; -import { getJobRepository } from '../../src/repository/getRepository'; -import { createJobEntity } from '../utils/createJobEntity'; - -describe('clear', () => { - let mongo: MongoMemoryServer; - let jobRepository: JobRepository; - - beforeAll(async () => { - mongo = await MongoMemoryServer.create(); - await connect({ url: await mongo.getUri() }); - jobRepository = getJobRepository(); - }); - - beforeEach(async () => await jobRepository.deleteOne({})); - - afterAll(async () => { - await disconnect(); - await mongo.stop(); - }); - - it('removes all jobs from mongo', async () => { - await jobRepository.save(createJobEntity({ name: 'job 1', interval: '1 minute', handler: () => undefined })); - await jobRepository.save(createJobEntity({ name: 'job 2', interval: '1 minute', handler: () => undefined })); - - await clear(); - - expect(await jobRepository.find({})).toHaveLength(0); - }); -}); diff --git a/test/job/define.spec.ts b/test/job/define.spec.ts deleted file mode 100644 index a2a836c3..00000000 --- a/test/job/define.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { mockRepositories } from '../utils/mockRepositories'; -import { JobRepository } from '../../src/repository/JobRepository'; -import { deepEqual, verify, when } from 'ts-mockito'; -import { define } from '../../src/job/define'; -import { createJobEntity } from '../utils/createJobEntity'; -import { DateTime } from 'luxon'; -import { ExecutionInfo } from '../../src'; -import { fromMomoJob } from '../../src/job/Job'; - -describe('define', () => { - const job = { name: 'test', interval: '1 minute', handler: () => 'finished' }; - - let jobRepository: JobRepository; - - it('saves a job', async () => { - jobRepository = mockRepositories().jobRepository; - - when(jobRepository.find(deepEqual({ name: job.name }))).thenResolve([]); - - await define(fromMomoJob(job)); - - verify(jobRepository.save(deepEqual(createJobEntity(job)))).once(); - }); - - it('updates a job', async () => { - jobRepository = mockRepositories().jobRepository; - - when(jobRepository.find(deepEqual({ name: job.name }))).thenResolve([createJobEntity(job)]); - - const newInterval = '2 minutes'; - await define(fromMomoJob({ ...job, interval: newInterval })); - - verify(jobRepository.updateJob(job.name, deepEqual(createJobEntity({ ...job, interval: newInterval })))).once(); - }); - - it('cleans up duplicate jobs but keeps latest job', async () => { - const duplicate = createJobEntity(job); - const latest = createJobEntity(job); - latest.executionInfo = { lastFinished: DateTime.now().toISO() } as ExecutionInfo; - when(jobRepository.find(deepEqual({ name: job.name }))).thenResolve([duplicate, latest]); - - const newInterval = 'two minutes'; - await define(fromMomoJob({ ...job, interval: newInterval })); - - verify(jobRepository.delete(deepEqual([duplicate]))).once(); - verify(jobRepository.updateJob(job.name, deepEqual(createJobEntity({ ...job, interval: newInterval })))).once(); - }); -}); diff --git a/test/job/keepLatest.spec.ts b/test/job/keepLatest.spec.ts deleted file mode 100644 index d7341487..00000000 --- a/test/job/keepLatest.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { DateTime } from 'luxon'; -import { anything, capture, deepEqual, verify, when } from 'ts-mockito'; - -import { JobRepository } from '../../src/repository/JobRepository'; -import { keepLatest } from '../../src/job/keepLatest'; -import { JobEntity } from '../../src/repository/JobEntity'; -import { ExecutionInfo } from '../../src'; -import { mockRepositories } from '../utils/mockRepositories'; - -describe('keepLatest', () => { - const name = 'test'; - let jobRepository: JobRepository; - - beforeAll(() => { - jobRepository = mockRepositories().jobRepository; - }); - - afterEach(() => jest.clearAllMocks()); - - it('removes all duplicate jobs except latest', async () => { - const duplicate = { name } as JobEntity; - const latest = { name, executionInfo: { lastFinished: DateTime.now().toISO() } as ExecutionInfo } as JobEntity; - - when(jobRepository.find(deepEqual({ name }))).thenResolve([duplicate, latest]); - - const kept = await keepLatest(name); - - expect(kept).toBe(latest); - verify(jobRepository.delete(anything())).once(); - const [removedJobs] = capture(jobRepository.delete).last(); - expect(removedJobs).toEqual([duplicate]); - }); - - it('returns a job without duplicates', async () => { - const job = { name } as JobEntity; - when(jobRepository.find(deepEqual({ name }))).thenResolve([job]); - - expect(await keepLatest(name)).toEqual(job); - }); - - it('returns undefined when no job is found', async () => { - when(jobRepository.find(deepEqual({ name }))).thenResolve([]); - - expect(await keepLatest(name)).toBeUndefined(); - }); -}); diff --git a/test/job/list.spec.ts b/test/job/list.spec.ts deleted file mode 100644 index 4ee9945c..00000000 --- a/test/job/list.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { when } from 'ts-mockito'; - -import { mockRepositories } from '../utils/mockRepositories'; -import { ExecutionInfo, list } from '../../src'; - -describe('list', () => { - afterEach(() => jest.resetAllMocks()); - - it('returns jobs', async () => { - const jobRepository = mockRepositories().jobRepository; - - const job1 = { - name: 'job1', - interval: '1 minute', - executionInfo: {} as ExecutionInfo, - running: 2, - concurrency: 1, - maxRunning: 3, - }; - const job2 = { - name: 'job2', - interval: '2 minutes', - executionInfo: {} as ExecutionInfo, - running: 0, - concurrency: 1, - maxRunning: 0, - }; - when(jobRepository.find()).thenResolve([job1, job2]); - - const jobs = await list(); - - expect(jobs).toEqual([ - { - name: job1.name, - interval: job1.interval, - concurrency: job1.concurrency, - maxRunning: job1.maxRunning, - executionInfo: {}, - }, - { - name: job2.name, - interval: job2.interval, - concurrency: job2.concurrency, - maxRunning: job2.maxRunning, - executionInfo: {}, - }, - ]); - }); - - it('returns nothing if not connected', async () => { - expect(await list()).toHaveLength(0); - }); -}); diff --git a/test/repository/ExecutionsRepository.integration.spec.ts b/test/repository/ExecutionsRepository.integration.spec.ts index aaa0c442..27e26437 100644 --- a/test/repository/ExecutionsRepository.integration.spec.ts +++ b/test/repository/ExecutionsRepository.integration.spec.ts @@ -1,8 +1,7 @@ import { MongoMemoryServer } from 'mongodb-memory-server'; -import { connect, disconnect } from '../../src/connect'; +import { Connection } from '../../src/Connection'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; -import { getExecutionsRepository } from '../../src/repository/getRepository'; import { sleep } from '../utils/sleep'; describe('ExecutionsRepository', () => { @@ -10,20 +9,21 @@ describe('ExecutionsRepository', () => { const name = 'test job'; let mongo: MongoMemoryServer; + let connection: Connection; let executionsRepository: ExecutionsRepository; beforeAll(async () => { ExecutionsRepository.deadScheduleThreshold = 1000; mongo = await MongoMemoryServer.create(); - await connect({ url: await mongo.getUri() }); - executionsRepository = getExecutionsRepository(); + connection = await Connection.create({ url: mongo.getUri() }); + executionsRepository = connection.getExecutionsRepository(); }); beforeEach(async () => await executionsRepository.delete()); afterAll(async () => { - await disconnect(); + await connection.disconnect(); await mongo.stop(); }); diff --git a/test/repository/JobRepository.integration.spec.ts b/test/repository/JobRepository.integration.spec.ts index 14893d69..95187655 100644 --- a/test/repository/JobRepository.integration.spec.ts +++ b/test/repository/JobRepository.integration.spec.ts @@ -1,10 +1,11 @@ import { DateTime } from 'luxon'; import { MongoMemoryServer } from 'mongodb-memory-server'; -import { ExecutionStatus, MomoJob } from '../../src'; -import { connect, disconnect } from '../../src/connect'; -import { createJobEntity } from '../utils/createJobEntity'; +import { ExecutionInfo, ExecutionStatus, MomoJob } from '../../src'; +import { Connection } from '../../src/Connection'; +import { createJobEntity } from '../../src/repository/createJobEntity'; import { JobRepository } from '../../src/repository/JobRepository'; -import { getJobRepository } from '../../src/repository/getRepository'; +import { JobEntity } from '../../src/repository/JobEntity'; +import { fromMomoJob } from '../../src/job/Job'; describe('JobRepository', () => { const job: MomoJob = { @@ -13,21 +14,124 @@ describe('JobRepository', () => { handler: () => undefined, }; let mongo: MongoMemoryServer; + let connection: Connection; let jobRepository: JobRepository; beforeAll(async () => { mongo = await MongoMemoryServer.create(); - await connect({ url: await mongo.getUri() }); - jobRepository = getJobRepository(); + connection = await Connection.create({ url: mongo.getUri() }); + jobRepository = connection.getJobRepository(); }); beforeEach(async () => await jobRepository.deleteOne({})); afterAll(async () => { - await disconnect(); + await connection.disconnect(); await mongo.stop(); }); + describe('check', () => { + const name = 'test'; + + it('returns executionInfo', async () => { + const executionInfo: ExecutionInfo = { + lastStarted: DateTime.now().toISO(), + lastFinished: DateTime.now().toISO(), + lastResult: { status: ExecutionStatus.finished }, + }; + await jobRepository.save({ name, executionInfo } as JobEntity); + + const result = await jobRepository.check(name); + + expect(result).toEqual(executionInfo); + }); + + it('returns nothing if job not found', async () => { + expect(await jobRepository.check(name)).toBeUndefined(); + }); + }); + + describe('define', () => { + jest.setTimeout(1000000); + const job: MomoJob = { name: 'test', interval: '1 minute', handler: () => 'finished' }; + + it('saves a job', async () => { + await jobRepository.define(fromMomoJob(job)); + + expect(await jobRepository.find({ name: job.name })).toEqual([ + { ...createJobEntity(job), _id: expect.anything() }, + ]); + }); + + it('updates a job', async () => { + await jobRepository.save(createJobEntity(job)); + + const newInterval = '2 minutes'; + await jobRepository.define(fromMomoJob({ ...job, interval: newInterval })); + + expect(await jobRepository.find({ name: job.name })).toEqual([ + { ...createJobEntity(job), interval: newInterval, _id: expect.anything() }, + ]); + }); + + it('cleans up duplicate jobs but keeps latest job', async () => { + const duplicate = createJobEntity(job); + const latest = createJobEntity(job); + latest.executionInfo = { lastFinished: DateTime.now().toISO() } as ExecutionInfo; + await jobRepository.save(duplicate); + await jobRepository.save(latest); + + const newInterval = 'two minutes'; + await jobRepository.define(fromMomoJob({ ...job, interval: newInterval })); + + expect(await jobRepository.find({ name: job.name })).toEqual([ + { ...createJobEntity(job), interval: newInterval, executionInfo: latest.executionInfo, _id: expect.anything() }, + ]); + }); + }); + + describe('list', () => { + it('returns jobs', async () => { + const job1 = { + name: 'job1', + interval: '1 minute', + executionInfo: {} as ExecutionInfo, + running: 2, + concurrency: 1, + maxRunning: 3, + }; + const job2 = { + name: 'job2', + interval: '2 minutes', + executionInfo: {} as ExecutionInfo, + running: 0, + concurrency: 1, + maxRunning: 0, + }; + await jobRepository.save(job1); + await jobRepository.save(job2); + + const jobs = await jobRepository.list(); + + expect(jobs).toEqual([ + { + name: job1.name, + interval: job1.interval, + concurrency: job1.concurrency, + maxRunning: job1.maxRunning, + executionInfo: {}, + }, + { + name: job2.name, + interval: job2.interval, + concurrency: job2.concurrency, + maxRunning: job2.maxRunning, + executionInfo: {}, + }, + ]); + }); + }); + describe('updateJob', () => { it('does not overwrite executionInfo', async () => { const savedJob = createJobEntity(job); diff --git a/test/schedule/MongoSchedule.spec.ts b/test/schedule/MongoSchedule.spec.ts index 53e92fd9..0692df1f 100644 --- a/test/schedule/MongoSchedule.spec.ts +++ b/test/schedule/MongoSchedule.spec.ts @@ -2,26 +2,27 @@ import { anyString, capture, deepEqual, instance, mock, verify } from 'ts-mockit import { MongoSchedule } from '../../src'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; +import { MomoConnectionOptions } from '../../src'; import { JobRepository } from '../../src/repository/JobRepository'; -import { connectForTest } from '../../src/connect'; -import { MongoClient } from 'mongodb'; -let jobRepository: JobRepository; -let executionsRepository: ExecutionsRepository; -jest.mock('../../src/repository/getRepository', () => { +const executionsRepository = mock(ExecutionsRepository); +jest.mock('../../src/Connection', () => { return { - getJobRepository: () => instance(jobRepository), - getExecutionsRepository: () => instance(executionsRepository), + Connection: { + create: async (_options: MomoConnectionOptions) => { + return { + getJobRepository: () => instance(mock(JobRepository)), + getExecutionsRepository: () => instance(executionsRepository), + disconnect: async () => undefined, + }; + }, + }, }; }); describe('MongoSchedule', () => { beforeEach(async () => { jest.clearAllMocks(); - - connectForTest(instance(mock(MongoClient))); - jobRepository = mock(JobRepository); - executionsRepository = mock(ExecutionsRepository); }); it('connects and starts the ping and disconnects and stops the ping', async () => { diff --git a/test/schedule/MongoScheduleBuilder.integration.spec.ts b/test/schedule/MongoScheduleBuilder.integration.spec.ts index 7fcb5a9f..17bd412d 100644 --- a/test/schedule/MongoScheduleBuilder.integration.spec.ts +++ b/test/schedule/MongoScheduleBuilder.integration.spec.ts @@ -1,8 +1,8 @@ import { MongoMemoryServer } from 'mongodb-memory-server'; -import { clear, MomoConnectionOptions, MomoJob, MongoSchedule } from '../../src'; +import { MomoConnectionOptions, MomoJob, MongoSchedule } from '../../src'; import { MongoScheduleBuilder } from '../../src/schedule/MongoScheduleBuilder'; -import { getExecutionsRepository } from '../../src/repository/getRepository'; +import { Connection } from '../../src/Connection'; describe('MongoScheduleBuilder', () => { const job1: MomoJob = { @@ -25,22 +25,25 @@ describe('MongoScheduleBuilder', () => { let mongo: MongoMemoryServer; let connectionOptions: MomoConnectionOptions; + let connection: Connection; beforeAll(async () => { mongo = await MongoMemoryServer.create(); connectionOptions = { url: mongo.getUri() }; + connection = await Connection.create(connectionOptions); }); afterAll(async () => { await mongo.stop(); + await connection.disconnect(); }); describe('build a mongoSchedule', () => { let mongoSchedule: MongoSchedule; afterEach(async () => { - await clear(); - await getExecutionsRepository().delete(); + await connection.getJobRepository().delete(); + await connection.getExecutionsRepository().delete(); await mongoSchedule.disconnect(); }); diff --git a/test/schedule/Schedule.integration.spec.ts b/test/schedule/Schedule.integration.spec.ts index d1be86db..3e6619f1 100644 --- a/test/schedule/Schedule.integration.spec.ts +++ b/test/schedule/Schedule.integration.spec.ts @@ -1,10 +1,10 @@ import { MongoMemoryServer } from 'mongodb-memory-server'; -import { clear, MomoJob, MongoSchedule } from '../../src'; +import { MomoJob, MongoSchedule } from '../../src'; import { JobRepository } from '../../src/repository/JobRepository'; -import { getJobRepository } from '../../src/repository/getRepository'; import { initLoggingForTests } from '../utils/logging'; import { fromMomoJob } from '../../src/job/Job'; +import { Connection } from '../../src/Connection'; describe('Schedule', () => { const job: MomoJob = { @@ -14,24 +14,28 @@ describe('Schedule', () => { }; let mongo: MongoMemoryServer; + let connection: Connection; let jobRepository: JobRepository; let mongoSchedule: MongoSchedule; beforeAll(async () => { mongo = await MongoMemoryServer.create(); - mongoSchedule = await MongoSchedule.connect({ url: await mongo.getUri() }); - jobRepository = getJobRepository(); + connection = await Connection.create({ url: mongo.getUri() }); + jobRepository = connection.getJobRepository(); + + mongoSchedule = await MongoSchedule.connect({ url: mongo.getUri() }); initLoggingForTests(mongoSchedule); }); beforeEach(async () => { await mongoSchedule.cancel(); - await clear(); + await mongoSchedule.clear(); }); afterAll(async () => { await mongoSchedule.disconnect(); + await connection.disconnect(); await mongo.stop(); }); diff --git a/test/schedule/Schedule.spec.ts b/test/schedule/Schedule.spec.ts index 4a5f26ce..77095ffb 100644 --- a/test/schedule/Schedule.spec.ts +++ b/test/schedule/Schedule.spec.ts @@ -1,28 +1,23 @@ -import { anyString, deepEqual, instance, mock, verify, when } from 'ts-mockito'; -import { MongoClient } from 'mongodb'; +import { anyString, deepEqual, instance, mock, when } from 'ts-mockito'; import { ExecutionStatus, MomoConnectionOptions, MomoEvent, MomoJob, MongoSchedule } from '../../src'; import { initLoggingForTests } from '../utils/logging'; -import { createJobEntity } from '../utils/createJobEntity'; +import { createJobEntity } from '../../src/repository/createJobEntity'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; import { JobRepository } from '../../src/repository/JobRepository'; -const mongoClient = mock(MongoClient); - -jest.mock('../../src/connect', () => { - return { - connect: (_momoConnectionOptions: MomoConnectionOptions) => undefined, - disconnect: () => undefined, - getConnection: () => instance(mongoClient), - }; -}); - const executionsRepository = mock(ExecutionsRepository); const jobRepository = mock(JobRepository); - -jest.mock('../../src/repository/getRepository', () => { +jest.mock('../../src/Connection', () => { return { - getExecutionsRepository: () => instance(executionsRepository), - getJobRepository: () => instance(jobRepository), + Connection: { + create: async (_options: MomoConnectionOptions) => { + return { + getJobRepository: () => instance(jobRepository), + getExecutionsRepository: () => instance(executionsRepository), + disconnect: async () => undefined, + }; + }, + }, }; }); @@ -58,9 +53,9 @@ describe('Schedule', () => { }); it('does not report error when concurrency > maxRunning but maxRunning is not set', async () => { - await mongoSchedule.define({ ...job, concurrency: 3 }); + const defined = await mongoSchedule.define({ ...job, concurrency: 3 }); - verify(jobRepository.save(deepEqual({ ...job, maxRunning: 0, concurrency: 3, immediate: false }))).once(); + expect(defined).toBe(true); }); it('counts jobs', async () => { diff --git a/test/schedule/SchedulePing.spec.ts b/test/schedule/SchedulePing.spec.ts index b83fea31..853a6a80 100644 --- a/test/schedule/SchedulePing.spec.ts +++ b/test/schedule/SchedulePing.spec.ts @@ -4,22 +4,20 @@ import { SchedulePing } from '../../src/schedule/SchedulePing'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; import { sleep } from '../utils/sleep'; -let executionsRepository: ExecutionsRepository; -jest.mock('../../src/repository/getRepository', () => { - return { - getExecutionsRepository: () => instance(executionsRepository), - }; -}); - describe('SchedulePing', () => { const scheduleId = '123'; - const schedulePing = new SchedulePing(scheduleId, { debug: jest.fn(), error: jest.fn() }); + + let executionsRepository: ExecutionsRepository; + let schedulePing: SchedulePing; beforeAll(() => { SchedulePing.interval = 1000; executionsRepository = mock(ExecutionsRepository); + schedulePing = new SchedulePing(scheduleId, instance(executionsRepository), { debug: jest.fn(), error: jest.fn() }); }); + beforeEach(jest.clearAllMocks); + it('starts, pings, cleans and stops', async () => { schedulePing.start(); await sleep(SchedulePing.interval); diff --git a/test/schedule/momo.integration.spec.ts b/test/schedule/momo.integration.spec.ts index 3a0773c2..122f4cc9 100644 --- a/test/schedule/momo.integration.spec.ts +++ b/test/schedule/momo.integration.spec.ts @@ -2,14 +2,14 @@ import { DateTime } from 'luxon'; import { MongoMemoryServer } from 'mongodb-memory-server'; import { v4 as uuid } from 'uuid'; -import { clear, ExecutionStatus, MomoError, MomoErrorEvent, MomoErrorType, MomoJob, MongoSchedule } from '../../src'; +import { ExecutionStatus, MomoError, MomoErrorEvent, MomoErrorType, MomoJob, MongoSchedule } from '../../src'; import { JobRepository } from '../../src/repository/JobRepository'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; -import { getExecutionsRepository, getJobRepository } from '../../src/repository/getRepository'; import { sleep } from '../utils/sleep'; import { waitFor } from '../utils/waitFor'; import { initLoggingForTests } from '../utils/logging'; -import { createJobEntity } from '../utils/createJobEntity'; +import { createJobEntity } from '../../src/repository/createJobEntity'; +import { Connection } from '../../src/Connection'; interface TestJobHandler { handler: () => Promise; @@ -27,12 +27,16 @@ describe('Momo', () => { let jobRepository: JobRepository; let executionsRepository: ExecutionsRepository; let mongoSchedule: MongoSchedule; + let connection: Connection; beforeAll(async () => { mongo = await MongoMemoryServer.create(); - mongoSchedule = await MongoSchedule.connect({ url: await mongo.getUri() }); - jobRepository = getJobRepository(); - executionsRepository = getExecutionsRepository(); + + connection = await Connection.create({ url: mongo.getUri() }); + jobRepository = connection.getJobRepository(); + executionsRepository = connection.getExecutionsRepository(); + + mongoSchedule = await MongoSchedule.connect({ url: mongo.getUri() }); initLoggingForTests(mongoSchedule); @@ -44,11 +48,12 @@ describe('Momo', () => { // eslint-disable-next-line jest/no-standalone-expect expect(mongoSchedule.getUnexpectedErrorCount()).toBe(0); await mongoSchedule.cancel(); - await clear(); + await mongoSchedule.clear(); }); afterAll(async () => { await mongoSchedule.disconnect(); + await connection.disconnect(); await mongo.stop(); }); diff --git a/test/scheduler/JobScheduler.spec.ts b/test/scheduler/JobScheduler.spec.ts index 5ad46088..564fe19e 100644 --- a/test/scheduler/JobScheduler.spec.ts +++ b/test/scheduler/JobScheduler.spec.ts @@ -1,15 +1,14 @@ import { anything, deepEqual, instance, mock, verify, when } from 'ts-mockito'; -import { mockRepositories } from '../utils/mockRepositories'; -const { executionsRepository, jobRepository } = mockRepositories(); - import { JobScheduler } from '../../src/scheduler/JobScheduler'; import { Job } from '../../src/job/Job'; import { JobExecutor } from '../../src/executor/JobExecutor'; import { MomoError, MomoErrorType } from '../../src'; import { loggerForTests } from '../utils/logging'; -import { createJobEntity } from '../utils/createJobEntity'; +import { createJobEntity } from '../../src/repository/createJobEntity'; import { sleep } from '../utils/sleep'; +import { JobRepository } from '../../src/repository/JobRepository'; +import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; describe('JobScheduler', () => { const defaultJob = { @@ -23,12 +22,14 @@ describe('JobScheduler', () => { const errorFn = jest.fn(); const scheduleId = '123'; + let executionsRepository: ExecutionsRepository; + let jobRepository: JobRepository; let jobExecutor: JobExecutor; let jobScheduler: JobScheduler; beforeEach(() => { - jest.clearAllMocks(); - + executionsRepository = mock(ExecutionsRepository); + jobRepository = mock(JobRepository); jobExecutor = mock(JobExecutor); when(jobExecutor.execute(anything())).thenResolve(); }); @@ -44,6 +45,8 @@ describe('JobScheduler', () => { job.immediate, instance(jobExecutor), scheduleId, + instance(executionsRepository), + instance(jobRepository), loggerForTests(errorFn) ); when(jobRepository.findOne(deepEqual({ name: job.name }))).thenResolve(job); diff --git a/test/utils/createJobEntity.ts b/test/utils/createJobEntity.ts deleted file mode 100644 index 9fbf3895..00000000 --- a/test/utils/createJobEntity.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { MomoJob } from '../../src'; -import { JobEntity } from '../../src/repository/JobEntity'; -import { fromMomoJob } from '../../src/job/Job'; - -// TODO remove this? -export function createJobEntity(job: MomoJob): JobEntity { - return fromMomoJob(job); -} diff --git a/test/utils/mockRepositories.ts b/test/utils/mockRepositories.ts deleted file mode 100644 index 915d28ed..00000000 --- a/test/utils/mockRepositories.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { instance, mock } from 'ts-mockito'; - -import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; -import { JobRepository } from '../../src/repository/JobRepository'; - -export function mockRepositories() { - const executionsRepository = mock(ExecutionsRepository); - const jobRepository = mock(JobRepository); - - jest.mock('../../src/repository/getRepository', () => { - return { - getExecutionsRepository: () => instance(executionsRepository), - getJobRepository: () => instance(jobRepository), - }; - }); - - return { executionsRepository, jobRepository }; -} From b0c250031447e75925d59eeb66152833c1756541 Mon Sep 17 00:00:00 2001 From: Ute Weiss Date: Fri, 27 Aug 2021 16:14:50 +0200 Subject: [PATCH 06/13] fix: execute integration tests only once in ci pipeline Signed-off-by: Ute Weiss --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 752eda16..39b1155a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,4 +53,3 @@ jobs: node-version: ${{ matrix.node }} - run: npm install - run: npm run test - - run: npm run test:integration From aae6d6878f25d246aa35f0523ccdcffa4a51409a Mon Sep 17 00:00:00 2001 From: Ute Weiss Date: Fri, 27 Aug 2021 16:43:14 +0200 Subject: [PATCH 07/13] refactor: change createJobEntity to toJobDefinition Signed-off-by: Ute Weiss --- src/job/Job.ts | 11 +++- src/repository/JobRepository.ts | 8 +-- src/repository/Repository.ts | 4 +- src/repository/createJobEntity.ts | 13 ---- src/schedule/Schedule.ts | 4 +- test/job/Job.spec.ts | 4 +- test/job/validate.spec.ts | 14 ++--- .../JobRepository.integration.spec.ts | 57 +++++++++-------- test/schedule/Schedule.integration.spec.ts | 4 +- test/schedule/Schedule.spec.ts | 46 +++++++------- test/schedule/momo.integration.spec.ts | 63 ++++++++++--------- test/scheduler/JobScheduler.spec.ts | 7 +-- 12 files changed, 120 insertions(+), 115 deletions(-) delete mode 100644 src/repository/createJobEntity.ts diff --git a/src/job/Job.ts b/src/job/Job.ts index 2171b35d..517bdd03 100644 --- a/src/job/Job.ts +++ b/src/job/Job.ts @@ -17,6 +17,15 @@ export interface Job extends JobDefinition { handler: Handler; } -export function fromMomoJob(job: MomoJob): Job { +export function toJob(job: MomoJob): Job { return { immediate: false, concurrency: 1, maxRunning: 0, ...job }; } + +export function toJobDefinition(job: Job): JobDefinition { + return { + name: job.name, + interval: job.interval, + maxRunning: job.maxRunning, + concurrency: job.concurrency, + }; +} diff --git a/src/repository/JobRepository.ts b/src/repository/JobRepository.ts index 26bda15e..f71e2e45 100644 --- a/src/repository/JobRepository.ts +++ b/src/repository/JobRepository.ts @@ -1,11 +1,10 @@ import { MongoClient } from 'mongodb'; import { ExecutionInfo } from '../job/ExecutionInfo'; -import { Job, MomoJobStatus } from '../job/Job'; +import { Job, MomoJobStatus, toJobDefinition } from '../job/Job'; import { JobEntity } from './JobEntity'; import { Logger } from '../logging/Logger'; import { Repository } from './Repository'; -import { createJobEntity } from './createJobEntity'; import { findLatest } from '../job/findLatest'; export const JOBS_COLLECTION_NAME = 'jobs'; @@ -26,6 +25,7 @@ export class JobRepository extends Repository { async define(job: Job, logger?: Logger): Promise { const { name, interval, concurrency, maxRunning } = job; + const jobDefinition = toJobDefinition(job); logger?.debug('define job', { name, concurrency, interval, maxRunning }); @@ -33,12 +33,12 @@ export class JobRepository extends Repository { if (old) { logger?.debug('update job in database', { name }); - await this.updateJob(name, createJobEntity(job)); + await this.updateJob(name, jobDefinition); return; } logger?.debug('save job to database', { name }); - await this.save(createJobEntity(job)); + await this.save(jobDefinition); } private async keepLatest(name: string, logger?: Logger): Promise { diff --git a/src/repository/Repository.ts b/src/repository/Repository.ts index 218e3bc0..2529675c 100644 --- a/src/repository/Repository.ts +++ b/src/repository/Repository.ts @@ -1,4 +1,5 @@ import { Collection, Filter, MongoClient, ObjectId, OptionalId, UpdateFilter } from 'mongodb'; +import { cloneDeep } from 'lodash'; export class Repository { private readonly collection: Collection; @@ -8,7 +9,8 @@ export class Repository { } async save(entity: OptionalId): Promise { - await this.collection.insertOne(entity); + // insertOne mutates the entity and adds an _id, so we use cloneDeep + await this.collection.insertOne(cloneDeep(entity)); } async updateOne(filter: Filter, update: UpdateFilter): Promise { diff --git a/src/repository/createJobEntity.ts b/src/repository/createJobEntity.ts deleted file mode 100644 index 9a47e8aa..00000000 --- a/src/repository/createJobEntity.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JobEntity } from './JobEntity'; -import { MomoJob } from '../job/MomoJob'; -import { fromMomoJob } from '../job/Job'; - -export function createJobEntity(momoJob: MomoJob): JobEntity { - const job = fromMomoJob(momoJob); - return { - name: job.name, - interval: job.interval, - maxRunning: job.maxRunning, - concurrency: job.concurrency, - }; -} diff --git a/src/schedule/Schedule.ts b/src/schedule/Schedule.ts index 3c28e269..fe14caa2 100644 --- a/src/schedule/Schedule.ts +++ b/src/schedule/Schedule.ts @@ -8,7 +8,7 @@ import { LogEmitter } from '../logging/LogEmitter'; import { MomoErrorType } from '../logging/error/MomoErrorType'; import { MomoJob } from '../job/MomoJob'; import { MomoJobDescription } from '../job/MomoJobDescription'; -import { fromMomoJob } from '../job/Job'; +import { toJob } from '../job/Job'; import { validate } from '../job/validate'; export class Schedule extends LogEmitter { @@ -54,7 +54,7 @@ export class Schedule extends LogEmitter { * @returns true if jobs was defined, false if the job was invalid */ public async define(momoJob: MomoJob): Promise { - const job = fromMomoJob(momoJob); + const job = toJob(momoJob); if (!validate(job, this.logger)) { return false; diff --git a/test/job/Job.spec.ts b/test/job/Job.spec.ts index 26b559f9..d6898177 100644 --- a/test/job/Job.spec.ts +++ b/test/job/Job.spec.ts @@ -1,9 +1,9 @@ -import { fromMomoJob } from '../../src/job/Job'; +import { toJob } from '../../src/job/Job'; describe('fromMomoJob', () => { it('sets defaults', () => { const job = { name: 'test', interval: '1 second', handler: () => undefined }; - expect(fromMomoJob(job)).toMatchObject({ + expect(toJob(job)).toMatchObject({ ...job, immediate: false, concurrency: 1, diff --git a/test/job/validate.spec.ts b/test/job/validate.spec.ts index e613a0ee..1647781a 100644 --- a/test/job/validate.spec.ts +++ b/test/job/validate.spec.ts @@ -1,4 +1,4 @@ -import { Job, fromMomoJob } from '../../src/job/Job'; +import { Job, toJob } from '../../src/job/Job'; import { Logger } from '../../src/logging/Logger'; import { MomoErrorType, momoError } from '../../src'; import { validate } from '../../src/job/validate'; @@ -12,12 +12,12 @@ describe('validate', () => { beforeEach(async () => jest.clearAllMocks()); it('validates a job', () => { - const job: Job = fromMomoJob({ name: 'test', interval: '1 minute', handler: () => 'finished' }); + const job: Job = toJob({ name: 'test', interval: '1 minute', handler: () => 'finished' }); expect(validate(job, logger)).toBe(true); }); it('reports error when interval cannot be parsed', () => { - const job: Job = fromMomoJob({ name: 'test', interval: 'not an interval', handler: () => 'finished' }); + const job: Job = toJob({ name: 'test', interval: 'not an interval', handler: () => 'finished' }); expect(validate(job, logger)).toBe(false); expect(logger.error).toHaveBeenCalledTimes(1); @@ -30,7 +30,7 @@ describe('validate', () => { }); it('reports error when interval is not positive', () => { - const job: Job = fromMomoJob({ name: 'test', interval: '-1 minute', handler: () => 'finished' }); + const job: Job = toJob({ name: 'test', interval: '-1 minute', handler: () => 'finished' }); expect(validate(job, logger)).toBe(false); expect(logger.error).toHaveBeenCalledTimes(1); @@ -43,7 +43,7 @@ describe('validate', () => { }); it('reports error when maxRunning is invalid', async () => { - const job: Job = fromMomoJob({ name: 'test', interval: '1 minute', handler: () => 'finished', maxRunning: -1 }); + const job: Job = toJob({ name: 'test', interval: '1 minute', handler: () => 'finished', maxRunning: -1 }); expect(validate(job, logger)).toBe(false); expect(logger.error).toHaveBeenCalledTimes(1); @@ -56,7 +56,7 @@ describe('validate', () => { }); it('reports error when concurrency is invalid', async () => { - const job: Job = fromMomoJob({ name: 'test', interval: '1 minute', handler: () => 'finished', concurrency: 0 }); + const job: Job = toJob({ name: 'test', interval: '1 minute', handler: () => 'finished', concurrency: 0 }); expect(validate(job, logger)).toBe(false); expect(logger.error).toHaveBeenCalledTimes(1); @@ -69,7 +69,7 @@ describe('validate', () => { }); it('reports error when concurrency > maxRunning', async () => { - const job: Job = fromMomoJob({ + const job: Job = toJob({ name: 'test', interval: '1 minute', handler: () => 'finished', diff --git a/test/repository/JobRepository.integration.spec.ts b/test/repository/JobRepository.integration.spec.ts index 33925ff5..530c28bf 100644 --- a/test/repository/JobRepository.integration.spec.ts +++ b/test/repository/JobRepository.integration.spec.ts @@ -2,18 +2,19 @@ import { DateTime } from 'luxon'; import { MongoMemoryServer } from 'mongodb-memory-server'; import { Connection } from '../../src/Connection'; -import { ExecutionInfo, ExecutionStatus, MomoJob } from '../../src'; +import { ExecutionInfo, ExecutionStatus } from '../../src'; import { JobEntity } from '../../src/repository/JobEntity'; import { JobRepository } from '../../src/repository/JobRepository'; -import { createJobEntity } from '../../src/repository/createJobEntity'; -import { fromMomoJob } from '../../src/job/Job'; +import { toJob, toJobDefinition } from '../../src/job/Job'; describe('JobRepository', () => { - const job: MomoJob = { + const job = toJob({ name: 'test job', interval: 'one minute', handler: () => undefined, - }; + }); + const jobDefinition = toJobDefinition(job); + let mongo: MongoMemoryServer; let connection: Connection; let jobRepository: JobRepository; @@ -24,7 +25,7 @@ describe('JobRepository', () => { jobRepository = connection.getJobRepository(); }); - beforeEach(async () => jobRepository.deleteOne({})); + beforeEach(async () => jobRepository.delete()); afterAll(async () => { await connection.disconnect(); @@ -54,39 +55,39 @@ describe('JobRepository', () => { describe('define', () => { jest.setTimeout(1000000); - const job: MomoJob = { name: 'test', interval: '1 minute', handler: () => 'finished' }; it('saves a job', async () => { - await jobRepository.define(fromMomoJob(job)); + await jobRepository.define(job); - expect(await jobRepository.find({ name: job.name })).toEqual([ - { ...createJobEntity(job), _id: expect.anything() }, - ]); + expect(await jobRepository.find({ name: job.name })).toEqual([{ ...jobDefinition, _id: expect.anything() }]); }); it('updates a job', async () => { - await jobRepository.save(createJobEntity(job)); + await jobRepository.save(jobDefinition); const newInterval = '2 minutes'; - await jobRepository.define(fromMomoJob({ ...job, interval: newInterval })); + await jobRepository.define({ ...job, interval: newInterval }); expect(await jobRepository.find({ name: job.name })).toEqual([ - { ...createJobEntity(job), interval: newInterval, _id: expect.anything() }, + { ...jobDefinition, interval: newInterval, _id: expect.anything() }, ]); }); it('cleans up duplicate jobs but keeps latest job', async () => { - const duplicate = createJobEntity(job); - const latest = createJobEntity(job); - latest.executionInfo = { lastFinished: DateTime.now().toISO() } as ExecutionInfo; - await jobRepository.save(duplicate); + const latest = { ...jobDefinition, executionInfo: { lastFinished: DateTime.now().toISO() } as ExecutionInfo }; + await jobRepository.save(jobDefinition); await jobRepository.save(latest); const newInterval = 'two minutes'; - await jobRepository.define(fromMomoJob({ ...job, interval: newInterval })); + await jobRepository.define({ ...job, interval: newInterval }); - expect(await jobRepository.find({ name: job.name })).toEqual([ - { ...createJobEntity(job), interval: newInterval, executionInfo: latest.executionInfo, _id: expect.anything() }, + const actual = await jobRepository.find({ name: job.name }); + expect(actual).toEqual([ + { + ...latest, + interval: newInterval, + _id: expect.anything(), + }, ]); }); }); @@ -135,11 +136,13 @@ describe('JobRepository', () => { describe('updateJob', () => { it('does not overwrite executionInfo', async () => { - const savedJob = createJobEntity(job); - savedJob.executionInfo = { - lastStarted: DateTime.now().toISO(), - lastFinished: DateTime.now().toISO(), - lastResult: { status: ExecutionStatus.finished, handlerResult: 'I was executed' }, + const savedJob = { + ...jobDefinition, + executionInfo: { + lastStarted: DateTime.now().toISO(), + lastFinished: DateTime.now().toISO(), + lastResult: { status: ExecutionStatus.finished, handlerResult: 'I was executed' }, + }, }; await jobRepository.save(savedJob); @@ -150,7 +153,7 @@ describe('JobRepository', () => { }); it('can update maxRunning to 0', async () => { - const savedJob = createJobEntity({ ...job, maxRunning: 3 }); + const savedJob = toJobDefinition({ ...job, maxRunning: 3 }); await jobRepository.save(savedJob); await jobRepository.updateJob(job.name, { maxRunning: 0 }); diff --git a/test/schedule/Schedule.integration.spec.ts b/test/schedule/Schedule.integration.spec.ts index 760eec96..d29c2d06 100644 --- a/test/schedule/Schedule.integration.spec.ts +++ b/test/schedule/Schedule.integration.spec.ts @@ -3,8 +3,8 @@ import { MongoMemoryServer } from 'mongodb-memory-server'; import { Connection } from '../../src/Connection'; import { JobRepository } from '../../src/repository/JobRepository'; import { MomoJob, MongoSchedule } from '../../src'; -import { fromMomoJob } from '../../src/job/Job'; import { initLoggingForTests } from '../utils/logging'; +import { toJob } from '../../src/job/Job'; describe('Schedule', () => { const job: MomoJob = { @@ -66,7 +66,7 @@ describe('Schedule', () => { it('lists jobs on the schedule', async () => { await jobRepository.save( - fromMomoJob({ + toJob({ name: 'some job that is in the database but not on the schedule', handler: jest.fn(), interval: 'one minute', diff --git a/test/schedule/Schedule.spec.ts b/test/schedule/Schedule.spec.ts index 5a29e26b..6d9fb532 100644 --- a/test/schedule/Schedule.spec.ts +++ b/test/schedule/Schedule.spec.ts @@ -3,8 +3,9 @@ import { anyString, deepEqual, instance, mock, when } from 'ts-mockito'; import { ExecutionStatus, MomoConnectionOptions, MomoEvent, MomoJob, MongoSchedule } from '../../src'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; import { JobRepository } from '../../src/repository/JobRepository'; -import { createJobEntity } from '../../src/repository/createJobEntity'; +import { fromMomoJob } from '../../dist/job/Job'; import { initLoggingForTests } from '../utils/logging'; +import { toJobDefinition } from '../../src/job/Job'; const executionsRepository = mock(ExecutionsRepository); const jobRepository = mock(JobRepository); @@ -24,18 +25,19 @@ jest.mock('../../src/Connection', () => { }); describe('Schedule', () => { - const job: MomoJob = { + const momoJob: MomoJob = { name: 'test job', interval: 'one minute', handler: jest.fn(), }; + const jobDefinition = toJobDefinition(fromMomoJob(momoJob)); let mongoSchedule: MongoSchedule; beforeEach(async () => { jest.clearAllMocks(); - when(jobRepository.find(deepEqual({ name: job.name }))).thenResolve([]); + when(jobRepository.find(deepEqual({ name: momoJob.name }))).thenResolve([]); mongoSchedule = await MongoSchedule.connect({ url: 'mongodb://does.not/matter' }); initLoggingForTests(mongoSchedule); @@ -55,13 +57,13 @@ describe('Schedule', () => { }); it('does not report error when concurrency > maxRunning but maxRunning is not set', async () => { - const defined = await mongoSchedule.define({ ...job, concurrency: 3 }); + const defined = await mongoSchedule.define({ ...momoJob, concurrency: 3 }); expect(defined).toBe(true); }); it('counts jobs', async () => { - await mongoSchedule.define(job); + await mongoSchedule.define(momoJob); expect(mongoSchedule.count()).toBe(1); }); @@ -70,13 +72,13 @@ describe('Schedule', () => { const name = 'not started'; when(jobRepository.find(deepEqual({ name }))).thenResolve([]); - const notStartedJob = { ...job, name }; + const notStartedJob = { ...momoJob, name }; await mongoSchedule.define(notStartedJob); - await mongoSchedule.define(job); + await mongoSchedule.define(momoJob); - when(jobRepository.findOne(deepEqual({ name: job.name }))).thenResolve(createJobEntity(job)); + when(jobRepository.findOne(deepEqual({ name: momoJob.name }))).thenResolve(jobDefinition); - await mongoSchedule.startJob(job.name); + await mongoSchedule.startJob(momoJob.name); expect(mongoSchedule.count()).toBe(2); expect(mongoSchedule.count(true)).toBe(1); @@ -90,15 +92,15 @@ describe('Schedule', () => { }); it('runs a job once', async () => { - await mongoSchedule.define(job); + await mongoSchedule.define(momoJob); - when(jobRepository.findOne(deepEqual({ name: job.name }))).thenResolve(createJobEntity(job)); - when(executionsRepository.addExecution(anyString(), job.name, 0)).thenResolve({ added: true, running: 0 }); + when(jobRepository.findOne(deepEqual({ name: momoJob.name }))).thenResolve(jobDefinition); + when(executionsRepository.addExecution(anyString(), momoJob.name, 0)).thenResolve({ added: true, running: 0 }); - const result = await mongoSchedule.run(job.name); + const result = await mongoSchedule.run(momoJob.name); expect(result).toEqual({ status: ExecutionStatus.finished, handlerResult: 'finished' }); - expect(job.handler).toHaveBeenCalledTimes(1); + expect(momoJob.handler).toHaveBeenCalledTimes(1); }); it('skips running job once when job is not found', async () => { @@ -108,27 +110,27 @@ describe('Schedule', () => { }); it('skips running job once when job is not found in repository', async () => { - await mongoSchedule.define(job); + await mongoSchedule.define(momoJob); - when(jobRepository.findOne(deepEqual({ name: job.name }))).thenResolve(undefined); + when(jobRepository.findOne(deepEqual({ name: momoJob.name }))).thenResolve(undefined); - const result = await mongoSchedule.run(job.name); + const result = await mongoSchedule.run(momoJob.name); expect(result).toEqual({ status: ExecutionStatus.notFound }); }); it('skips running job once when maxRunning is reached', async () => { - await mongoSchedule.define(job); + await mongoSchedule.define(momoJob); - when(jobRepository.findOne(deepEqual({ name: job.name }))).thenResolve(createJobEntity(job)); - when(executionsRepository.addExecution(anyString(), job.name, 0)).thenResolve({ + when(jobRepository.findOne(deepEqual({ name: momoJob.name }))).thenResolve(jobDefinition); + when(executionsRepository.addExecution(anyString(), momoJob.name, 0)).thenResolve({ added: false, running: 0, }); - const result = await mongoSchedule.run(job.name); + const result = await mongoSchedule.run(momoJob.name); expect(result).toEqual({ status: ExecutionStatus.maxRunningReached }); - expect(job.handler).toHaveBeenCalledTimes(0); + expect(momoJob.handler).toHaveBeenCalledTimes(0); }); }); diff --git a/test/schedule/momo.integration.spec.ts b/test/schedule/momo.integration.spec.ts index 7646be08..1d6e1263 100644 --- a/test/schedule/momo.integration.spec.ts +++ b/test/schedule/momo.integration.spec.ts @@ -6,9 +6,10 @@ import { Connection } from '../../src/Connection'; import { ExecutionStatus, MomoErrorEvent, MomoErrorType, MomoJob, MongoSchedule, momoError } from '../../src'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; import { JobRepository } from '../../src/repository/JobRepository'; -import { createJobEntity } from '../../src/repository/createJobEntity'; +import { fromMomoJob } from '../../dist/job/Job'; import { initLoggingForTests } from '../utils/logging'; import { sleep } from '../utils/sleep'; +import { toJobDefinition } from '../../src/job/Job'; import { waitFor } from '../utils/waitFor'; interface TestJobHandler { @@ -86,15 +87,15 @@ describe('Momo', () => { describe('single job', () => { let jobHandler: TestJobHandler; - let job: MomoJob; + let momoJob: MomoJob; beforeEach(() => { jobHandler = createTestJobHandler(); - job = createTestJob(jobHandler); + momoJob = createTestJob(jobHandler); }); it('executes job periodically', async () => { - await mongoSchedule.define(job); + await mongoSchedule.define(momoJob); await mongoSchedule.start(); @@ -104,7 +105,7 @@ describe('Momo', () => { }); it('executes an immediate job periodically', async () => { - await mongoSchedule.define({ ...job, immediate: true }); + await mongoSchedule.define({ ...momoJob, immediate: true }); await mongoSchedule.start(); @@ -113,18 +114,20 @@ describe('Momo', () => { }); it('executes job that was executed before', async () => { - const jobEntity = createJobEntity(job); - jobEntity.executionInfo = { - lastStarted: DateTime.now().toISO(), - lastFinished: DateTime.now().toISO(), - lastResult: { status: ExecutionStatus.finished, handlerResult: 'I was executed' }, + const jobEntity = { + ...toJobDefinition(fromMomoJob(momoJob)), + executionInfo: { + lastStarted: DateTime.now().toISO(), + lastFinished: DateTime.now().toISO(), + lastResult: { status: ExecutionStatus.finished, handlerResult: 'I was executed' }, + }, }; await jobRepository.save(jobEntity); await sleep(500); - await mongoSchedule.define(job); + await mongoSchedule.define(momoJob); - const jobs = await jobRepository.find({ name: job.name }); + const jobs = await jobRepository.find({ name: momoJob.name }); expect(jobs[0]?.executionInfo).toEqual(jobEntity.executionInfo); await mongoSchedule.start(); @@ -133,15 +136,15 @@ describe('Momo', () => { }); it('saves executionInfo in mongo', async () => { - await mongoSchedule.define(job); + await mongoSchedule.define(momoJob); - const jobs1 = await jobRepository.find({ name: job.name }); + const jobs1 = await jobRepository.find({ name: momoJob.name }); expect(jobs1[0]!.executionInfo).toBeUndefined(); await mongoSchedule.start(); await waitFor(() => expect(jobHandler.count).toBe(1)); - const jobs2 = await jobRepository.find({ name: job.name }); + const jobs2 = await jobRepository.find({ name: momoJob.name }); const executionInfo = jobs2[0]?.executionInfo; expect(executionInfo).toBeDefined(); @@ -154,13 +157,13 @@ describe('Momo', () => { it('updates and reports failing job in mongo', async () => { jobHandler.failJob = true; - await mongoSchedule.define(job); + await mongoSchedule.define(momoJob); await mongoSchedule.start(); await waitFor(() => expect(jobHandler.count).toBe(1)); const executionInfo = await waitFor(async () => { - const jobs = await jobRepository.find({ name: job.name }); + const jobs = await jobRepository.find({ name: momoJob.name }); const executionInfo = jobs[0]?.executionInfo; expect(executionInfo?.lastFinished).toBeDefined(); return executionInfo!; @@ -170,19 +173,19 @@ describe('Momo', () => { expect(receivedError).toEqual({ message: 'job failed', type: MomoErrorType.executeJob, - data: { name: job.name }, + data: { name: momoJob.name }, error: new Error(jobHandler.message), }); }); it('updates result message when job succeeds', async () => { jobHandler.failJob = true; - await mongoSchedule.define(job); + await mongoSchedule.define(momoJob); await mongoSchedule.start(); await waitFor(() => expect(jobHandler.count).toBe(1)); await waitFor(async () => { - const jobs = await jobRepository.find({ name: job.name }); + const jobs = await jobRepository.find({ name: momoJob.name }); expect(jobs[0]?.executionInfo?.lastResult).toEqual({ status: ExecutionStatus.failed, handlerResult: jobHandler.message, @@ -192,7 +195,7 @@ describe('Momo', () => { jobHandler.failJob = false; await waitFor(() => expect(jobHandler.count).toBe(2)); await waitFor(async () => { - const jobs = await jobRepository.find({ name: job.name }); + const jobs = await jobRepository.find({ name: momoJob.name }); expect(jobs[0]?.executionInfo?.lastResult).toEqual({ status: ExecutionStatus.finished, handlerResult: jobHandler.result, @@ -201,7 +204,7 @@ describe('Momo', () => { }); it('can be stopped and restarted', async () => { - await mongoSchedule.define(job); + await mongoSchedule.define(momoJob); await mongoSchedule.start(); await waitFor(() => expect(jobHandler.count).toBe(1)); @@ -218,11 +221,11 @@ describe('Momo', () => { }); it('updates already started job', async () => { - await mongoSchedule.define(job); + await mongoSchedule.define(momoJob); await mongoSchedule.start(); await waitFor(() => expect(jobHandler.count).toBe(1)); - await mongoSchedule.define({ ...job, interval: '2 seconds' }); + await mongoSchedule.define({ ...momoJob, interval: '2 seconds' }); await mongoSchedule.start(); // pick up the new interval await sleep(1100); @@ -232,7 +235,7 @@ describe('Momo', () => { }); it('does not execute a job that was removed from mongo', async () => { - await mongoSchedule.define(job); + await mongoSchedule.define(momoJob); await mongoSchedule.start(); await jobRepository.delete(); @@ -242,14 +245,14 @@ describe('Momo', () => { }); it('updates maxRunning and concurrency from mongo', async () => { - await mongoSchedule.define(job); + await mongoSchedule.define(momoJob); await mongoSchedule.start(); const updatedConcurrency = 5; const updatedMaxRunning = 10; await jobRepository.updateOne( - { name: job.name }, + { name: momoJob.name }, { $set: { concurrency: updatedConcurrency, maxRunning: updatedMaxRunning } } ); @@ -264,19 +267,19 @@ describe('Momo', () => { }); it('does not update interval of started job from mongo', async () => { - await mongoSchedule.define(job); + await mongoSchedule.define(momoJob); await mongoSchedule.start(); const updatedInterval = '2 seconds'; - await jobRepository.updateOne({ name: job.name }, { $set: { interval: updatedInterval } }); + await jobRepository.updateOne({ name: momoJob.name }, { $set: { interval: updatedInterval } }); await waitFor(() => expect(jobHandler.count).toBe(1)); const updatedJobs = await mongoSchedule.list(); const { interval, schedulerStatus } = updatedJobs[0]!; expect(interval).toEqual(updatedInterval); - expect(schedulerStatus?.interval).toBe(job.interval); + expect(schedulerStatus?.interval).toBe(momoJob.interval); expect(schedulerStatus?.running).toBeGreaterThanOrEqual(0); }); }); diff --git a/test/scheduler/JobScheduler.spec.ts b/test/scheduler/JobScheduler.spec.ts index a5c4a455..bd2e8f37 100644 --- a/test/scheduler/JobScheduler.spec.ts +++ b/test/scheduler/JobScheduler.spec.ts @@ -1,12 +1,11 @@ import { anything, deepEqual, instance, mock, verify, when } from 'ts-mockito'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; -import { Job } from '../../src/job/Job'; +import { Job, toJobDefinition } from '../../src/job/Job'; import { JobExecutor } from '../../src/executor/JobExecutor'; import { JobRepository } from '../../src/repository/JobRepository'; import { JobScheduler } from '../../src/scheduler/JobScheduler'; import { MomoErrorType, momoError } from '../../src'; -import { createJobEntity } from '../../src/repository/createJobEntity'; import { loggerForTests } from '../utils/logging'; import { sleep } from '../utils/sleep'; @@ -88,7 +87,7 @@ describe('JobScheduler', () => { it('returns job description', async () => { const job = createJob(); - when(jobRepository.findOne(deepEqual({ name: job.name }))).thenResolve(createJobEntity(job)); + when(jobRepository.findOne(deepEqual({ name: job.name }))).thenResolve(toJobDefinition(job)); const jobDescription = await jobScheduler.getJobDescription(); expect(jobDescription).toEqual({ @@ -103,7 +102,7 @@ describe('JobScheduler', () => { const job = createJob(); await jobScheduler.start(); - when(jobRepository.findOne(deepEqual({ name: job.name }))).thenResolve(createJobEntity(job)); + when(jobRepository.findOne(deepEqual({ name: job.name }))).thenResolve(toJobDefinition(job)); const jobDescription = await jobScheduler.getJobDescription(); expect(jobDescription).toEqual({ From 77930894a5764f92d2597332fdee35b84eb385c0 Mon Sep 17 00:00:00 2001 From: Ute Weiss Date: Mon, 30 Aug 2021 08:13:10 +0200 Subject: [PATCH 08/13] fix: fix imports in tests Signed-off-by: Ute Weiss --- test/schedule/Schedule.spec.ts | 5 ++--- test/schedule/momo.integration.spec.ts | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/test/schedule/Schedule.spec.ts b/test/schedule/Schedule.spec.ts index 6d9fb532..62e45230 100644 --- a/test/schedule/Schedule.spec.ts +++ b/test/schedule/Schedule.spec.ts @@ -3,9 +3,8 @@ import { anyString, deepEqual, instance, mock, when } from 'ts-mockito'; import { ExecutionStatus, MomoConnectionOptions, MomoEvent, MomoJob, MongoSchedule } from '../../src'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; import { JobRepository } from '../../src/repository/JobRepository'; -import { fromMomoJob } from '../../dist/job/Job'; +import { toJob, toJobDefinition } from '../../src/job/Job'; import { initLoggingForTests } from '../utils/logging'; -import { toJobDefinition } from '../../src/job/Job'; const executionsRepository = mock(ExecutionsRepository); const jobRepository = mock(JobRepository); @@ -30,7 +29,7 @@ describe('Schedule', () => { interval: 'one minute', handler: jest.fn(), }; - const jobDefinition = toJobDefinition(fromMomoJob(momoJob)); + const jobDefinition = toJobDefinition(toJob(momoJob)); let mongoSchedule: MongoSchedule; diff --git a/test/schedule/momo.integration.spec.ts b/test/schedule/momo.integration.spec.ts index 1d6e1263..a18a860e 100644 --- a/test/schedule/momo.integration.spec.ts +++ b/test/schedule/momo.integration.spec.ts @@ -6,10 +6,9 @@ import { Connection } from '../../src/Connection'; import { ExecutionStatus, MomoErrorEvent, MomoErrorType, MomoJob, MongoSchedule, momoError } from '../../src'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; import { JobRepository } from '../../src/repository/JobRepository'; -import { fromMomoJob } from '../../dist/job/Job'; +import { toJob, toJobDefinition } from '../../src/job/Job'; import { initLoggingForTests } from '../utils/logging'; import { sleep } from '../utils/sleep'; -import { toJobDefinition } from '../../src/job/Job'; import { waitFor } from '../utils/waitFor'; interface TestJobHandler { @@ -115,7 +114,7 @@ describe('Momo', () => { it('executes job that was executed before', async () => { const jobEntity = { - ...toJobDefinition(fromMomoJob(momoJob)), + ...toJobDefinition(toJob(momoJob)), executionInfo: { lastStarted: DateTime.now().toISO(), lastFinished: DateTime.now().toISO(), From 936696f8dbfa0bd49c1612f7a43a53fac890caa0 Mon Sep 17 00:00:00 2001 From: Niklas Eicker Date: Mon, 30 Aug 2021 11:37:28 +0200 Subject: [PATCH 09/13] fix: Remove colliding eslint rule (see marudor/eslint-plugin-sort-imports-es6-autofix#2) Signed-off-by: Niklas Eicker --- .eslintrc.js | 9 +-------- package-lock.json | 6 ------ package.json | 1 - src/schedule/Schedule.ts | 1 - test/schedule/Schedule.spec.ts | 2 +- test/schedule/momo.integration.spec.ts | 2 +- 6 files changed, 3 insertions(+), 18 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 6e63aa01..9b063c7b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,13 +6,7 @@ module.exports = { project: 'tsconfig.eslint.json', sourceType: 'module', }, - plugins: [ - '@typescript-eslint', - 'eslint-plugin-import', - 'eslint-plugin-jsdoc', - 'eslint-plugin-prefer-arrow', - 'sort-imports-es6-autofix', - ], + plugins: ['@typescript-eslint', 'eslint-plugin-import', 'eslint-plugin-jsdoc', 'eslint-plugin-prefer-arrow'], rules: { '@typescript-eslint/adjacent-overload-signatures': 'error', '@typescript-eslint/array-type': [ @@ -192,7 +186,6 @@ module.exports = { radix: 'error', semi: 'off', // use @typescript-eslint/semi instead 'sort-imports': ['error', { allowSeparatedGroups: true, ignoreDeclarationSort: true }], - 'sort-imports-es6-autofix/sort-imports-es6': 'error', 'space-before-function-paren': 'off', 'space-in-parens': ['error', 'never'], 'spaced-comment': [ diff --git a/package-lock.json b/package-lock.json index c03c89b6..f2a02d28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2578,12 +2578,6 @@ "prettier-linter-helpers": "^1.0.0" } }, - "eslint-plugin-sort-imports-es6-autofix": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-sort-imports-es6-autofix/-/eslint-plugin-sort-imports-es6-autofix-0.6.0.tgz", - "integrity": "sha512-2NVaBGF9NN+727Fyq+jJYihdIeegjXeUUrZED9Q8FVB8MsV3YQEyXG96GVnXqWt0pmn7xfCZOZf3uKnIhBrfeQ==", - "dev": true - }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", diff --git a/package.json b/package.json index 697297a9..cf1667be 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "eslint-plugin-markdown": "2.2.0", "eslint-plugin-prefer-arrow": "1.2.3", "eslint-plugin-prettier": "3.4.0", - "eslint-plugin-sort-imports-es6-autofix": "0.6.0", "jest": "27.0.6", "mongodb-memory-server": "7.2.1", "pino": "6.12.0", diff --git a/src/schedule/Schedule.ts b/src/schedule/Schedule.ts index fe14caa2..ecc80dfe 100644 --- a/src/schedule/Schedule.ts +++ b/src/schedule/Schedule.ts @@ -1,5 +1,4 @@ import { sum } from 'lodash'; - import { ExecutionInfo, ExecutionStatus, JobResult } from '../job/ExecutionInfo'; import { ExecutionsRepository } from '../repository/ExecutionsRepository'; import { JobRepository } from '../repository/JobRepository'; diff --git a/test/schedule/Schedule.spec.ts b/test/schedule/Schedule.spec.ts index 62e45230..b54c01c2 100644 --- a/test/schedule/Schedule.spec.ts +++ b/test/schedule/Schedule.spec.ts @@ -3,8 +3,8 @@ import { anyString, deepEqual, instance, mock, when } from 'ts-mockito'; import { ExecutionStatus, MomoConnectionOptions, MomoEvent, MomoJob, MongoSchedule } from '../../src'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; import { JobRepository } from '../../src/repository/JobRepository'; -import { toJob, toJobDefinition } from '../../src/job/Job'; import { initLoggingForTests } from '../utils/logging'; +import { toJob, toJobDefinition } from '../../src/job/Job'; const executionsRepository = mock(ExecutionsRepository); const jobRepository = mock(JobRepository); diff --git a/test/schedule/momo.integration.spec.ts b/test/schedule/momo.integration.spec.ts index a18a860e..781df2f6 100644 --- a/test/schedule/momo.integration.spec.ts +++ b/test/schedule/momo.integration.spec.ts @@ -6,9 +6,9 @@ import { Connection } from '../../src/Connection'; import { ExecutionStatus, MomoErrorEvent, MomoErrorType, MomoJob, MongoSchedule, momoError } from '../../src'; import { ExecutionsRepository } from '../../src/repository/ExecutionsRepository'; import { JobRepository } from '../../src/repository/JobRepository'; -import { toJob, toJobDefinition } from '../../src/job/Job'; import { initLoggingForTests } from '../utils/logging'; import { sleep } from '../utils/sleep'; +import { toJob, toJobDefinition } from '../../src/job/Job'; import { waitFor } from '../utils/waitFor'; interface TestJobHandler { From 120bec020b548965c9b5c198cbf1e17ab0f8cb74 Mon Sep 17 00:00:00 2001 From: Niklas Eicker Date: Tue, 31 Aug 2021 13:59:35 +0200 Subject: [PATCH 10/13] refactor: Move connection of a MongoClient into Connection to allow disconnecting/connecting on a single instance Signed-off-by: Niklas Eicker --- src/Connection.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Connection.ts b/src/Connection.ts index 62a5d7e6..c7f61cab 100644 --- a/src/Connection.ts +++ b/src/Connection.ts @@ -14,15 +14,11 @@ export class Connection { constructor(private readonly mongoClient: MongoClient) {} static async create(connectionOptions: MomoConnectionOptions): Promise { - const mongoClient = await MongoClient.connect(connectionOptions.url); + const connection = new Connection(new MongoClient(connectionOptions.url)); - await mongoClient.db().collection(JOBS_COLLECTION_NAME).createIndex({ name: 1 }, { name: 'job_name_index' }); - await mongoClient - .db() - .collection(JOBS_COLLECTION_NAME) - .createIndex({ scheduleId: 1 }, { name: 'schedule_id_index' }); + await connection.connect(); - return new Connection(mongoClient); + return connection; } getExecutionsRepository(): ExecutionsRepository { @@ -43,5 +39,13 @@ export class Connection { await this.mongoClient.close(); } - // TODO connection instance unusable after calling disconnect + async connect(): Promise { + await this.mongoClient.connect(); + + await this.mongoClient.db().collection(JOBS_COLLECTION_NAME).createIndex({ name: 1 }, { name: 'job_name_index' }); + await this.mongoClient + .db() + .collection(JOBS_COLLECTION_NAME) + .createIndex({ scheduleId: 1 }, { name: 'schedule_id_index' }); + } } From 9353a7ed960721ca6400f8e83a5891c8d283ec27 Mon Sep 17 00:00:00 2001 From: Niklas Eicker Date: Tue, 31 Aug 2021 14:09:40 +0200 Subject: [PATCH 11/13] refactor: Simplify MongoSchedule constructor Signed-off-by: Niklas Eicker --- src/schedule/MongoSchedule.ts | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/schedule/MongoSchedule.ts b/src/schedule/MongoSchedule.ts index 8c216a7c..5b4e3f76 100644 --- a/src/schedule/MongoSchedule.ts +++ b/src/schedule/MongoSchedule.ts @@ -1,21 +1,19 @@ import { v4 as uuid } from 'uuid'; import { Connection, MomoConnectionOptions } from '../Connection'; -import { ExecutionsRepository } from '../repository/ExecutionsRepository'; -import { JobRepository } from '../repository/JobRepository'; import { Schedule } from './Schedule'; import { SchedulePing } from './SchedulePing'; export class MongoSchedule extends Schedule { private readonly schedulePing: SchedulePing; + private readonly disconnectFct: () => Promise; + + private constructor(scheduleId: string, connection: Connection) { + const executionsRepository = connection.getExecutionsRepository(); + const jobRepository = connection.getJobRepository(); - private constructor( - scheduleId: string, - private readonly disconnectFct: () => Promise, - executionsRepository: ExecutionsRepository, - jobRepository: JobRepository - ) { super(scheduleId, executionsRepository, jobRepository); + this.disconnectFct = connection.disconnect.bind(connection); this.schedulePing = new SchedulePing(scheduleId, executionsRepository, this.logger); } @@ -28,17 +26,11 @@ export class MongoSchedule extends Schedule { const connection = await Connection.create(connectionOptions); const executionsRepository = connection.getExecutionsRepository(); - const jobRepository = connection.getJobRepository(); const scheduleId = uuid(); await executionsRepository.addSchedule(scheduleId); - const mongoSchedule = new MongoSchedule( - scheduleId, - connection.disconnect.bind(connection), - executionsRepository, - jobRepository - ); + const mongoSchedule = new MongoSchedule(scheduleId, connection); mongoSchedule.schedulePing.start(); From 69f68a253e9536f96e01af592609110a0a78bf7e Mon Sep 17 00:00:00 2001 From: Niklas Eicker Date: Tue, 31 Aug 2021 14:19:54 +0200 Subject: [PATCH 12/13] fix: Remove debugging leftovers Signed-off-by: Niklas Eicker --- test/repository/JobRepository.integration.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/repository/JobRepository.integration.spec.ts b/test/repository/JobRepository.integration.spec.ts index 530c28bf..ddf5eea6 100644 --- a/test/repository/JobRepository.integration.spec.ts +++ b/test/repository/JobRepository.integration.spec.ts @@ -54,8 +54,6 @@ describe('JobRepository', () => { }); describe('define', () => { - jest.setTimeout(1000000); - it('saves a job', async () => { await jobRepository.define(job); From fd29c3c573cfcdf90557ec2abf0f512190b44aee Mon Sep 17 00:00:00 2001 From: Ute Weiss Date: Mon, 6 Sep 2021 08:50:36 +0200 Subject: [PATCH 13/13] refactor: make logger a class member in JobRepository Signed-off-by: Ute Weiss --- src/repository/JobRepository.ts | 20 +++++++++++++------- src/schedule/MongoSchedule.ts | 3 +++ src/schedule/Schedule.ts | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/repository/JobRepository.ts b/src/repository/JobRepository.ts index f71e2e45..80c41ef4 100644 --- a/src/repository/JobRepository.ts +++ b/src/repository/JobRepository.ts @@ -10,10 +10,16 @@ import { findLatest } from '../job/findLatest'; export const JOBS_COLLECTION_NAME = 'jobs'; export class JobRepository extends Repository { + private logger: Logger | undefined; + constructor(mongoClient: MongoClient) { super(mongoClient, JOBS_COLLECTION_NAME); } + setLogger(logger: Logger): void { + this.logger = logger; + } + async check(name: string): Promise { const job = await this.findOne({ name }); return job?.executionInfo; @@ -23,25 +29,25 @@ export class JobRepository extends Repository { await this.delete(); } - async define(job: Job, logger?: Logger): Promise { + async define(job: Job): Promise { const { name, interval, concurrency, maxRunning } = job; const jobDefinition = toJobDefinition(job); - logger?.debug('define job', { name, concurrency, interval, maxRunning }); + this.logger?.debug('define job', { name, concurrency, interval, maxRunning }); - const old = await this.keepLatest(name, logger); + const old = await this.keepLatest(name); if (old) { - logger?.debug('update job in database', { name }); + this.logger?.debug('update job in database', { name }); await this.updateJob(name, jobDefinition); return; } - logger?.debug('save job to database', { name }); + this.logger?.debug('save job to database', { name }); await this.save(jobDefinition); } - private async keepLatest(name: string, logger?: Logger): Promise { + private async keepLatest(name: string): Promise { const jobs = await this.find({ name }); if (jobs.length === 1) return jobs[0]; @@ -49,7 +55,7 @@ export class JobRepository extends Repository { const latest = findLatest(jobs); if (!latest) return undefined; - logger?.debug('duplicate job, keep latest only', { name, count: jobs.length }); + this.logger?.debug('duplicate job, keep latest only', { name, count: jobs.length }); jobs.splice(jobs.indexOf(latest), 1); await this.delete({ _id: { $in: jobs.map(({ _id }) => _id) } }); diff --git a/src/schedule/MongoSchedule.ts b/src/schedule/MongoSchedule.ts index 5b4e3f76..1d29ddaf 100644 --- a/src/schedule/MongoSchedule.ts +++ b/src/schedule/MongoSchedule.ts @@ -13,6 +13,9 @@ export class MongoSchedule extends Schedule { const jobRepository = connection.getJobRepository(); super(scheduleId, executionsRepository, jobRepository); + + jobRepository.setLogger(this.logger); + this.disconnectFct = connection.disconnect.bind(connection); this.schedulePing = new SchedulePing(scheduleId, executionsRepository, this.logger); } diff --git a/src/schedule/Schedule.ts b/src/schedule/Schedule.ts index ecc80dfe..beeefa27 100644 --- a/src/schedule/Schedule.ts +++ b/src/schedule/Schedule.ts @@ -60,7 +60,7 @@ export class Schedule extends LogEmitter { } await this.stopJob(job.name); - await this.jobRepository.define(job, this.logger); + await this.jobRepository.define(job); this.jobSchedulers[job.name] = JobScheduler.forJob( this.scheduleId,