From 59cbd1178baa8173c144e50be653435016ee59ed Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 13 Nov 2024 10:17:00 +0800 Subject: [PATCH 01/15] Add docker type to monitor model --- Server/db/models/Monitor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/db/models/Monitor.js b/Server/db/models/Monitor.js index 111c48d03..d44566a97 100644 --- a/Server/db/models/Monitor.js +++ b/Server/db/models/Monitor.js @@ -28,7 +28,7 @@ const MonitorSchema = mongoose.Schema( type: { type: String, required: true, - enum: ["http", "ping", "pagespeed", "hardware"], + enum: ["http", "ping", "pagespeed", "hardware", "docker"], }, url: { type: String, From 41370c8d042a327bf95b55e16cd80d5d46a36c9b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 13 Nov 2024 10:33:46 +0800 Subject: [PATCH 02/15] Add check to see if existing service/method exists to logger statement --- Server/service/jobQueue.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index aaab725e5..0eb265405 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -178,10 +178,11 @@ class JobQueue { prevStatus, }); } catch (error) { + console.log(error.service); this.logger.error({ message: error.message, - service: SERVICE_NAME, - method: "createWorker", + service: error.service ?? SERVICE_NAME, + method: error.method ?? "createJobHandler", details: `Error processing job ${job.id}: ${error.message}`, stack: error.stack, }); From 2f4189382ad66cd28421618a8792687725b82eb8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 13 Nov 2024 10:34:05 +0800 Subject: [PATCH 03/15] Add hanlder for Docker types --- Server/service/networkService.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Server/service/networkService.js b/Server/service/networkService.js index fa665e1ea..b7d76e475 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -13,6 +13,7 @@ class NetworkService { this.TYPE_HTTP = "http"; this.TYPE_PAGESPEED = "pagespeed"; this.TYPE_HARDWARE = "hardware"; + this.TYPE_DOCKER = "docker"; this.SERVICE_NAME = "NetworkService"; this.NETWORK_ERROR = 5000; this.PING_ERROR = 5001; @@ -162,6 +163,11 @@ class NetworkService { return this.requestHttp(job); } + async requestDocker(job) { + console.log(job); + return {}; + } + /** * Gets the status of a job based on its type and returns the appropriate response. * @@ -181,13 +187,13 @@ class NetworkService { return await this.requestPagespeed(job); case this.TYPE_HARDWARE: return await this.requestHardware(job); + case this.TYPE_DOCKER: + return await this.requestDocker(job); default: - this.logger.error({ - message: `Unsupported type: ${job.data.type}`, - service: this.SERVICE_NAME, - method: "getStatus", - }); - return {}; + const err = new Error(`Unsupported type: ${job.data.type}`); + err.service = this.SERVICE_NAME; + err.method = "getStatus"; + throw err; } } } From 61406be2a3f7dd1ff8d5b06855403494800cf57a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 13 Nov 2024 10:34:40 +0800 Subject: [PATCH 04/15] Add dockerode --- Server/package-lock.json | 276 +++++++++++++++++++++++++++++++++++++++ Server/package.json | 1 + 2 files changed, 277 insertions(+) diff --git a/Server/package-lock.json b/Server/package-lock.json index d03515453..22ffaa863 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -14,6 +14,7 @@ "bcrypt": "^5.1.1", "bullmq": "5.25.6", "cors": "^2.8.5", + "dockerode": "4.0.2", "dotenv": "^16.4.5", "express": "^4.19.2", "handlebars": "^4.7.8", @@ -89,6 +90,12 @@ "node": ">=6.9.0" } }, + "node_modules/@balena/dockerignore": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", + "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", + "license": "Apache-2.0" + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -996,6 +1003,15 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1032,6 +1048,26 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -1045,6 +1081,15 @@ "node": ">= 10.0.0" } }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1056,6 +1101,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -1152,6 +1208,30 @@ "node": ">=16.20.1" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -1163,6 +1243,15 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/buildcheck": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", + "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/bullmq": { "version": "5.25.6", "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.25.6.tgz", @@ -1708,6 +1797,20 @@ } } }, + "node_modules/cpu-features": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.19.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/cron-parser": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", @@ -2014,6 +2117,58 @@ "node": ">=0.3.1" } }, + "node_modules/docker-modem": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.3.tgz", + "integrity": "sha512-89zhop5YVhcPEt5FpUFGr3cDyceGhq/F9J+ZndQ4KfqNvfbJpPMfgeixFgUj5OjCYAboElqODxY5Z1EBsSa6sg==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.1", + "readable-stream": "^3.5.0", + "split-ca": "^1.0.1", + "ssh2": "^1.15.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/docker-modem/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/docker-modem/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/dockerode": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.2.tgz", + "integrity": "sha512-9wM1BVpVMFr2Pw3eJNXrYYt6DT9k0xMcsSCjtPvyQ+xa1iPg/Mo3T/gUcwI0B2cczqCeCYRPF8yFYDwtFXT0+w==", + "license": "Apache-2.0", + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "docker-modem": "^5.0.3", + "tar-fs": "~2.0.1" + }, + "engines": { + "node": ">= 8.0" + } + }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -2145,6 +2300,15 @@ "node": ">=0.10.0" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -2439,6 +2603,12 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -2822,6 +2992,26 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -4129,6 +4319,12 @@ "node": ">=10" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, "node_modules/mocha": { "version": "10.8.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", @@ -4503,6 +4699,13 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/nan": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", + "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", + "license": "MIT", + "optional": true + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -5553,6 +5756,16 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6046,6 +6259,29 @@ "memory-pager": "^1.0.2" } }, + "node_modules/split-ca": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", + "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", + "license": "ISC" + }, + "node_modules/ssh2": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz", + "integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==", + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.10", + "nan": "^2.20.0" + } + }, "node_modules/ssl-checker": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/ssl-checker/-/ssl-checker-2.0.10.tgz", @@ -6248,6 +6484,40 @@ "node": ">=10" } }, + "node_modules/tar-fs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", + "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -6311,6 +6581,12 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", diff --git a/Server/package.json b/Server/package.json index 16e0e9d1c..ce34ee0f2 100644 --- a/Server/package.json +++ b/Server/package.json @@ -17,6 +17,7 @@ "bcrypt": "^5.1.1", "bullmq": "5.25.6", "cors": "^2.8.5", + "dockerode": "4.0.2", "dotenv": "^16.4.5", "express": "^4.19.2", "handlebars": "^4.7.8", From 2548742f31f8446040fe3a0432d8b9811cbcdeb7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 13 Nov 2024 10:36:42 +0800 Subject: [PATCH 05/15] Inject dockerode, instantiate instance --- Server/index.js | 5 +++-- Server/service/networkService.js | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Server/index.js b/Server/index.js index 11b5b2151..0e9811a43 100644 --- a/Server/index.js +++ b/Server/index.js @@ -28,6 +28,7 @@ import NetworkService from "./service/networkService.js"; import axios from "axios"; import ping from "ping"; import http from "http"; +import Docker from "dockerode"; // Email service and dependencies import EmailService from "./service/emailService.js"; @@ -45,7 +46,7 @@ import NotificationService from "./service/notificationService.js"; import db from "./db/mongo/MongoDB.js"; const SERVICE_NAME = "Server"; -const SHUTDOWN_TIMEOUT = 0; +const SHUTDOWN_TIMEOUT = 1000; let isShuttingDown = false; const __filename = fileURLToPath(import.meta.url); @@ -130,7 +131,7 @@ const startApp = async () => { nodemailer, logger ); - const networkService = new NetworkService(axios, ping, logger, http); + const networkService = new NetworkService(axios, ping, logger, http, Docker); const statusService = new StatusService(db, logger); const notificationService = new NotificationService(emailService, db, logger); const jobQueue = await JobQueue.createJobQueue( diff --git a/Server/service/networkService.js b/Server/service/networkService.js index b7d76e475..74ee8105c 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -8,7 +8,7 @@ import { errorMessages, successMessages } from "../utils/messages.js"; * @param {Object} http - The HTTP utility for network operations. */ class NetworkService { - constructor(axios, ping, logger, http) { + constructor(axios, ping, logger, http, Docker) { this.TYPE_PING = "ping"; this.TYPE_HTTP = "http"; this.TYPE_PAGESPEED = "pagespeed"; @@ -21,6 +21,7 @@ class NetworkService { this.ping = ping; this.logger = logger; this.http = http; + this.Docker = Docker; } /** @@ -164,7 +165,8 @@ class NetworkService { } async requestDocker(job) { - console.log(job); + var docker = new Docker({ socketPath: "/var/run/docker.sock" }); + return {}; } From b079327296923a863d13151c29ca04c394e9a3de Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 13 Nov 2024 10:59:40 +0800 Subject: [PATCH 06/15] remove console log --- Server/service/jobQueue.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index 0eb265405..eedeb1a5c 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -178,7 +178,6 @@ class JobQueue { prevStatus, }); } catch (error) { - console.log(error.service); this.logger.error({ message: error.message, service: error.service ?? SERVICE_NAME, From 5285fd30a1e43c947a548bfb15cdfaebee425cbb Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 13 Nov 2024 10:59:53 +0800 Subject: [PATCH 07/15] Add docker messages --- Server/utils/messages.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Server/utils/messages.js b/Server/utils/messages.js index c75c617d2..1a994527a 100644 --- a/Server/utils/messages.js +++ b/Server/utils/messages.js @@ -55,6 +55,9 @@ const errorMessages = { // Status Page Errors STATUS_PAGE_NOT_FOUND: "Status page not found", STATUS_PAGE_URL_NOT_UNIQUE: "Status page url must be unique", + + // Docker + DOCKER_FAIL: "Failed to fetch Docker container information", }; const successMessages = { @@ -123,6 +126,9 @@ const successMessages = { // Status Page STATUS_PAGE_BY_URL: "Got status page by url successfully", STATUS_PAGE_CREATE: "Status page created successfully", + + // Docker + DOCKER_SUCCESS: "Docker container status fetched successfully", }; export { errorMessages, successMessages }; From 922c4d38d6ed95d52aafafbd8e6a74c4313c20b8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 13 Nov 2024 11:00:16 +0800 Subject: [PATCH 08/15] Build docker response --- Server/service/networkService.js | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Server/service/networkService.js b/Server/service/networkService.js index 74ee8105c..153121503 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -165,9 +165,32 @@ class NetworkService { } async requestDocker(job) { - var docker = new Docker({ socketPath: "/var/run/docker.sock" }); + const docker = new this.Docker({ socketPath: "/var/run/docker.sock" }); + const container = docker.getContainer(job.data.url); - return {}; + const { response, responseTime, error } = await this.timeRequest(() => + container.inspect() + ); + + const containerState = response?.State ?? {}; + + const dockerResponse = { + monitorId: job.data._id, + type: job.data.type, + responseTime, + }; + + if (error) { + const code = error.statusCode || this.NETWORK_ERROR; + dockerResponse.code = code; + dockerResponse.status = false; + dockerResponse.message = error.reason || errorMessages.DOCKER_FAIL; + return dockerResponse; + } + dockerResponse.status = containerState.Status === "running" ? true : false; + dockerResponse.code = 200; + dockerResponse.message = successMessages.DOCKER_SUCCESS; + return dockerResponse; } /** From 11b84610c5ebc3e317d02337dc635d13461f66d0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 13 Nov 2024 11:07:03 +0800 Subject: [PATCH 09/15] safe object access --- Server/service/networkService.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Server/service/networkService.js b/Server/service/networkService.js index 153121503..a1ff64c20 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -172,8 +172,6 @@ class NetworkService { container.inspect() ); - const containerState = response?.State ?? {}; - const dockerResponse = { monitorId: job.data._id, type: job.data.type, @@ -187,7 +185,7 @@ class NetworkService { dockerResponse.message = error.reason || errorMessages.DOCKER_FAIL; return dockerResponse; } - dockerResponse.status = containerState.Status === "running" ? true : false; + dockerResponse.status = response?.State?.Status === "running" ? true : false; dockerResponse.code = 200; dockerResponse.message = successMessages.DOCKER_SUCCESS; return dockerResponse; From 34ae35ea219b81d65dc26265ed44f0dd1fd36e2e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 13 Nov 2024 11:07:36 +0800 Subject: [PATCH 10/15] Add db operation type for Docker type checks to statusService --- Server/service/statusService.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Server/service/statusService.js b/Server/service/statusService.js index 22254d050..4f7c1a4ce 100644 --- a/Server/service/statusService.js +++ b/Server/service/statusService.js @@ -133,6 +133,7 @@ class StatusService { ping: this.db.createCheck, pagespeed: this.db.createPageSpeedCheck, hardware: this.db.createHardwareCheck, + docker: this.db.createCheck, }; const operation = operationMap[networkResponse.type]; From c17efe3d016a41b422e0684965930de55040efd9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 13 Nov 2024 12:34:11 +0800 Subject: [PATCH 11/15] add tests --- Server/tests/services/networkService.test.js | 128 +++++++++++++++++-- 1 file changed, 114 insertions(+), 14 deletions(-) diff --git a/Server/tests/services/networkService.test.js b/Server/tests/services/networkService.test.js index 4e48524e2..9f4a30b20 100644 --- a/Server/tests/services/networkService.test.js +++ b/Server/tests/services/networkService.test.js @@ -3,7 +3,7 @@ import NetworkService from "../../service/networkService.js"; import { expect } from "chai"; import http from "http"; describe("Network Service", () => { - let axios, ping, logger, networkService; + let axios, ping, Docker, logger, networkService; beforeEach(() => { axios = { @@ -12,6 +12,11 @@ describe("Network Service", () => { status: 200, }), }; + Docker = class { + getContainer = sinon.stub().returns({ + inspect: sinon.stub().resolves({ State: { Status: "running" } }), + }); + }; ping = { promise: { probe: sinon @@ -20,7 +25,7 @@ describe("Network Service", () => { }, }; logger = { error: sinon.stub() }; - networkService = new NetworkService(axios, ping, logger, http); + networkService = new NetworkService(axios, ping, logger, http, Docker); }); describe("constructor", () => { it("should create a new NetworkService instance", () => { @@ -203,45 +208,140 @@ describe("Network Service", () => { }); }); + describe("requestDocker", () => { + it("should return a response object if docker successful", async () => { + const job = { data: { url: "http://test.com", _id: "123", type: "docker" } }; + const dockerResult = await networkService.requestDocker(job); + expect(dockerResult.monitorId).to.equal("123"); + expect(dockerResult.type).to.equal("docker"); + expect(dockerResult.responseTime).to.be.a("number"); + expect(dockerResult.status).to.be.true; + }); + + it("should return a response object with status false if container not running", async () => { + Docker = class { + getContainer = sinon.stub().returns({ + inspect: sinon.stub().resolves({ State: { Status: "stopped" } }), + }); + }; + networkService = new NetworkService(axios, ping, logger, http, Docker); + const job = { data: { url: "http://test.com", _id: "123", type: "docker" } }; + const dockerResult = await networkService.requestDocker(job); + expect(dockerResult.status).to.be.false; + expect(dockerResult.code).to.equal(200); + }); + + it("should handle an error when fetching the container", async () => { + Docker = class { + getContainer = sinon.stub().returns({ + inspect: sinon.stub().throws(new Error("test error")), + }); + }; + networkService = new NetworkService(axios, ping, logger, http, Docker); + const job = { data: { url: "http://test.com", _id: "123", type: "docker" } }; + const dockerResult = await networkService.requestDocker(job); + expect(dockerResult.status).to.be.false; + expect(dockerResult.code).to.equal(networkService.NETWORK_ERROR); + }); + + it("should throw an error if operations fail", async () => { + Docker = class { + getContainer = sinon.stub().throws(new Error("test error")); + }; + networkService = new NetworkService(axios, ping, logger, http, Docker); + const job = { data: { url: "http://test.com", _id: "123", type: "docker" } }; + try { + await networkService.requestDocker(job); + } catch (error) { + expect(error.message).to.equal("test error"); + } + }); + }); + describe("getStatus", () => { beforeEach(() => { networkService.requestPing = sinon.stub(); networkService.requestHttp = sinon.stub(); networkService.requestPagespeed = sinon.stub(); networkService.requestHardware = sinon.stub(); + networkService.requestDocker = sinon.stub(); }); afterEach(() => { sinon.restore(); }); - it("should call requestPing if type is ping", () => { - networkService.getStatus({ data: { type: "ping" } }); + it("should call requestPing if type is ping", async () => { + await networkService.getStatus({ data: { type: "ping" } }); expect(networkService.requestPing.calledOnce).to.be.true; + expect(networkService.requestDocker.notCalled).to.be.true; expect(networkService.requestHttp.notCalled).to.be.true; expect(networkService.requestPagespeed.notCalled).to.be.true; }); - it("should call requestHttp if type is http", () => { - networkService.getStatus({ data: { type: "http" } }); + it("should call requestHttp if type is http", async () => { + await networkService.getStatus({ data: { type: "http" } }); expect(networkService.requestPing.notCalled).to.be.true; + expect(networkService.requestDocker.notCalled).to.be.true; expect(networkService.requestHttp.calledOnce).to.be.true; expect(networkService.requestPagespeed.notCalled).to.be.true; }); - it("should call requestPagespeed if type is pagespeed", () => { - networkService.getStatus({ data: { type: "pagespeed" } }); + it("should call requestPagespeed if type is pagespeed", async () => { + await networkService.getStatus({ data: { type: "pagespeed" } }); expect(networkService.requestPing.notCalled).to.be.true; + expect(networkService.requestDocker.notCalled).to.be.true; expect(networkService.requestHttp.notCalled).to.be.true; expect(networkService.requestPagespeed.calledOnce).to.be.true; }); - it("should call requestHardware if type is hardware", () => { - networkService.getStatus({ data: { type: "hardware" } }); + it("should call requestHardware if type is hardware", async () => { + await networkService.getStatus({ data: { type: "hardware" } }); expect(networkService.requestHardware.calledOnce).to.be.true; + expect(networkService.requestDocker.notCalled).to.be.true; expect(networkService.requestPing.notCalled).to.be.true; expect(networkService.requestPagespeed.notCalled).to.be.true; }); - it("should log an error if an unknown type is provided", () => { - networkService.getStatus({ data: { type: "unknown" } }); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.args[0][0].message).to.equal("Unsupported type: unknown"); + it("should call requestDocker if type is Docker", async () => { + await networkService.getStatus({ data: { type: "docker" } }); + expect(networkService.requestDocker.calledOnce).to.be.true; + expect(networkService.requestHardware.notCalled).to.be.true; + expect(networkService.requestPing.notCalled).to.be.true; + expect(networkService.requestPagespeed.notCalled).to.be.true; + }); + it("should throw an error if an unknown type is provided", async () => { + try { + await networkService.getStatus({ data: { type: "unknown" } }); + } catch (error) { + expect(error.service).to.equal("NetworkService"); + expect(error.method).to.equal("getStatus"); + expect(error.message).to.equal("Unsupported type: unknown"); + } + }); + it("should throw an error if job type is undefined", async () => { + try { + await networkService.getStatus({ data: { type: undefined } }); + } catch (error) { + expect(error.service).to.equal("NetworkService"); + expect(error.method).to.equal("getStatus"); + expect(error.message).to.equal("Unsupported type: unknown"); + } + }); + it("should throw an error if job is empty", async () => { + try { + await networkService.getStatus({}); + } catch (error) { + console.log(error); + expect(error.service).to.equal("NetworkService"); + expect(error.method).to.equal("getStatus"); + expect(error.message).to.equal("Unsupported type: unknown"); + } + }); + it("should throw an error if job is null", async () => { + try { + await networkService.getStatus(null); + } catch (error) { + console.log(error); + expect(error.service).to.equal("NetworkService"); + expect(error.method).to.equal("getStatus"); + expect(error.message).to.equal("Unsupported type: unknown"); + } }); }); }); From 7639dd2e804be471a8a6836454ce6775ee921e8d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 13 Nov 2024 12:34:53 +0800 Subject: [PATCH 12/15] Refactored switch statement, extracted error building --- Server/service/networkService.js | 57 ++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/Server/service/networkService.js b/Server/service/networkService.js index a1ff64c20..7b40e288b 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -160,10 +160,43 @@ class NetworkService { return this.requestHttp(updatedJob); } + /** + * Sends an HTTP request to check hardware status and returns the response. + * + * @param {Object} job - The job object containing the data for the hardware request. + * @param {Object} job.data - The data object within the job. + * @param {string} job.data.url - The URL to send the hardware status request to. + * @param {string} job.data._id - The monitor ID for the hardware request. + * @param {string} job.data.type - The type of request, which is "hardware". + * @returns {Promise} An object containing the hardware status response details. + * @property {string} monitorId - The monitor ID for the hardware request. + * @property {string} type - The type of request ("hardware"). + * @property {number} responseTime - The time taken for the request to complete, in milliseconds. + * @property {Object} payload - The response payload from the hardware status request. + * @property {boolean} status - The status of the request (true if successful, false otherwise). + * @property {number} code - The response code (200 if successful, error code otherwise). + * @property {string} message - The message indicating the result of the hardware status request. + */ async requestHardware(job) { return this.requestHttp(job); } + /** + * Sends a request to inspect a Docker container and returns its status. + * + * @param {Object} job - The job object containing the data for the Docker request. + * @param {Object} job.data - The data object within the job. + * @param {string} job.data.url - The container ID or name to inspect. + * @param {string} job.data._id - The monitor ID for the Docker request. + * @param {string} job.data.type - The type of request, which is "docker". + * @returns {Promise} An object containing the Docker container status details. + * @property {string} monitorId - The monitor ID for the Docker request. + * @property {string} type - The type of request ("docker"). + * @property {number} responseTime - The time taken for the Docker inspection to complete, in milliseconds. + * @property {boolean} status - The status of the container (true if running, false otherwise). + * @property {number} code - The response code (200 if successful, error code otherwise). + * @property {string} message - The message indicating the result of the Docker inspection. + */ async requestDocker(job) { const docker = new this.Docker({ socketPath: "/var/run/docker.sock" }); const container = docker.getContainer(job.data.url); @@ -179,9 +212,8 @@ class NetworkService { }; if (error) { - const code = error.statusCode || this.NETWORK_ERROR; - dockerResponse.code = code; dockerResponse.status = false; + dockerResponse.code = error.statusCode || this.NETWORK_ERROR; dockerResponse.message = error.reason || errorMessages.DOCKER_FAIL; return dockerResponse; } @@ -191,6 +223,19 @@ class NetworkService { return dockerResponse; } + /** + * Handles unsupported job types by throwing an error with details. + * + * @param {string} type - The unsupported job type that was provided + * @throws {Error} An error with service name, method name and unsupported type message + */ + handleUnsupportedType(type) { + const err = new Error(`Unsupported type: ${type}`); + err.service = this.SERVICE_NAME; + err.method = "getStatus"; + throw err; + } + /** * Gets the status of a job based on its type and returns the appropriate response. * @@ -201,7 +246,8 @@ class NetworkService { * @throws {Error} Throws an error if the job type is unsupported. */ async getStatus(job) { - switch (job.data.type) { + const type = job.data?.type ?? "unknown"; + switch (type) { case this.TYPE_PING: return await this.requestPing(job); case this.TYPE_HTTP: @@ -213,10 +259,7 @@ class NetworkService { case this.TYPE_DOCKER: return await this.requestDocker(job); default: - const err = new Error(`Unsupported type: ${job.data.type}`); - err.service = this.SERVICE_NAME; - err.method = "getStatus"; - throw err; + return this.handleUnsupportedType(type); } } } From b2c43955f8ad87ac8566cc3b6fe824e5ab11904a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 13 Nov 2024 14:56:15 +0800 Subject: [PATCH 13/15] Add more robust error handling to networkService --- Server/service/networkService.js | 171 +++++++++++++++++++------------ 1 file changed, 105 insertions(+), 66 deletions(-) diff --git a/Server/service/networkService.js b/Server/service/networkService.js index 7b40e288b..23b3a62b1 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -64,28 +64,34 @@ class NetworkService { * @property {string} message - The message indicating the result of the ping request. */ async requestPing(job) { - const url = job.data.url; - const { response, responseTime, error } = await this.timeRequest(() => - this.ping.promise.probe(url) - ); + try { + const url = job.data.url; + const { response, responseTime, error } = await this.timeRequest(() => + this.ping.promise.probe(url) + ); - const pingResponse = { - monitorId: job.data._id, - type: "ping", - responseTime, - payload: response, - }; - if (error) { - pingResponse.status = false; - pingResponse.code = this.PING_ERROR; - pingResponse.message = errorMessages.PING_CANNOT_RESOLVE; + const pingResponse = { + monitorId: job.data._id, + type: "ping", + responseTime, + payload: response, + }; + if (error) { + pingResponse.status = false; + pingResponse.code = this.PING_ERROR; + pingResponse.message = errorMessages.PING_CANNOT_RESOLVE; + return pingResponse; + } + + pingResponse.code = 200; + pingResponse.status = response.alive; + pingResponse.message = successMessages.PING_SUCCESS; return pingResponse; + } catch (error) { + error.service = this.SERVICE_NAME; + error.method = "requestPing"; + throw error; } - - pingResponse.code = 200; - pingResponse.status = response.alive; - pingResponse.message = successMessages.PING_SUCCESS; - return pingResponse; } /** @@ -106,34 +112,40 @@ class NetworkService { * @property {string} message - The message indicating the result of the HTTP request. */ async requestHttp(job) { - const url = job.data.url; - const config = {}; + try { + const url = job.data.url; + const config = {}; - job.data.secret !== undefined && - (config.headers = { Authorization: `Bearer ${job.data.secret}` }); + job.data.secret !== undefined && + (config.headers = { Authorization: `Bearer ${job.data.secret}` }); - const { response, responseTime, error } = await this.timeRequest(() => - this.axios.get(url, config) - ); + const { response, responseTime, error } = await this.timeRequest(() => + this.axios.get(url, config) + ); - const httpResponse = { - monitorId: job.data._id, - type: job.data.type, - responseTime, - payload: response?.data, - }; + const httpResponse = { + monitorId: job.data._id, + type: job.data.type, + responseTime, + payload: response?.data, + }; - if (error) { - const code = error.response?.status || this.NETWORK_ERROR; - httpResponse.code = code; - httpResponse.status = false; - httpResponse.message = this.http.STATUS_CODES[code] || "Network Error"; + if (error) { + const code = error.response?.status || this.NETWORK_ERROR; + httpResponse.code = code; + httpResponse.status = false; + httpResponse.message = this.http.STATUS_CODES[code] || "Network Error"; + return httpResponse; + } + httpResponse.status = true; + httpResponse.code = response.status; + httpResponse.message = this.http.STATUS_CODES[response.status]; return httpResponse; + } catch (error) { + error.service = this.SERVICE_NAME; + error.method = "requestHttp"; + throw error; } - httpResponse.status = true; - httpResponse.code = response.status; - httpResponse.message = this.http.STATUS_CODES[response.status]; - return httpResponse; } /** @@ -153,11 +165,17 @@ class NetworkService { * @property {string} message - The message indicating the result of the PageSpeed request. */ async requestPagespeed(job) { - const url = job.data.url; - const updatedJob = { ...job }; - const pagespeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`; - updatedJob.data.url = pagespeedUrl; - return this.requestHttp(updatedJob); + try { + const url = job.data.url; + const updatedJob = { ...job }; + const pagespeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`; + updatedJob.data.url = pagespeedUrl; + return await this.requestHttp(updatedJob); + } catch (error) { + error.service = this.SERVICE_NAME; + error.method = "requestPagespeed"; + throw error; + } } /** @@ -178,7 +196,13 @@ class NetworkService { * @property {string} message - The message indicating the result of the hardware status request. */ async requestHardware(job) { - return this.requestHttp(job); + try { + return await this.requestHttp(job); + } catch (error) { + error.service = this.SERVICE_NAME; + error.method = "requestHardware"; + throw error; + } } /** @@ -198,29 +222,44 @@ class NetworkService { * @property {string} message - The message indicating the result of the Docker inspection. */ async requestDocker(job) { - const docker = new this.Docker({ socketPath: "/var/run/docker.sock" }); - const container = docker.getContainer(job.data.url); + try { + const docker = new this.Docker({ + socketPath: "/var/run/docker.sock", + handleError: true, // Enable error handling + }); - const { response, responseTime, error } = await this.timeRequest(() => - container.inspect() - ); + const containers = await docker.listContainers({ all: true }); + const containerExists = containers.some((c) => c.Id === job.data.url); + if (!containerExists) { + throw new Error(errorMessages.DOCKER_NOT_FOUND); + } + const container = docker.getContainer(job.data.url); - const dockerResponse = { - monitorId: job.data._id, - type: job.data.type, - responseTime, - }; + const { response, responseTime, error } = await this.timeRequest(() => + container.inspect() + ); - if (error) { - dockerResponse.status = false; - dockerResponse.code = error.statusCode || this.NETWORK_ERROR; - dockerResponse.message = error.reason || errorMessages.DOCKER_FAIL; + const dockerResponse = { + monitorId: job.data._id, + type: job.data.type, + responseTime, + }; + + if (error) { + dockerResponse.status = false; + dockerResponse.code = error.statusCode || this.NETWORK_ERROR; + dockerResponse.message = error.reason || errorMessages.DOCKER_FAIL; + return dockerResponse; + } + dockerResponse.status = response?.State?.Status === "running" ? true : false; + dockerResponse.code = 200; + dockerResponse.message = successMessages.DOCKER_SUCCESS; return dockerResponse; + } catch (error) { + error.service = this.SERVICE_NAME; + error.method = "requestDocker"; + throw error; } - dockerResponse.status = response?.State?.Status === "running" ? true : false; - dockerResponse.code = 200; - dockerResponse.message = successMessages.DOCKER_SUCCESS; - return dockerResponse; } /** @@ -246,7 +285,7 @@ class NetworkService { * @throws {Error} Throws an error if the job type is unsupported. */ async getStatus(job) { - const type = job.data?.type ?? "unknown"; + const type = job?.data?.type ?? "unknown"; switch (type) { case this.TYPE_PING: return await this.requestPing(job); From b8f99e3f1cfcde588a4591c7231265dc897765c5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 13 Nov 2024 14:56:32 +0800 Subject: [PATCH 14/15] Update tests --- Server/tests/services/networkService.test.js | 95 ++++++++++++++++++-- 1 file changed, 89 insertions(+), 6 deletions(-) diff --git a/Server/tests/services/networkService.test.js b/Server/tests/services/networkService.test.js index 9f4a30b20..d5c177d6f 100644 --- a/Server/tests/services/networkService.test.js +++ b/Server/tests/services/networkService.test.js @@ -2,6 +2,7 @@ import sinon from "sinon"; import NetworkService from "../../service/networkService.js"; import { expect } from "chai"; import http from "http"; +import { errorMessages } from "../../utils/messages.js"; describe("Network Service", () => { let axios, ping, Docker, logger, networkService; @@ -13,6 +14,12 @@ describe("Network Service", () => { }), }; Docker = class { + listContainers = sinon.stub().resolves([ + { + Names: ["http://test.com"], + Id: "http://test.com", + }, + ]); getContainer = sinon.stub().returns({ inspect: sinon.stub().resolves({ State: { Status: "running" } }), }); @@ -75,6 +82,18 @@ describe("Network Service", () => { expect(pingResult.status).to.be.false; expect(pingResult.code).to.equal(networkService.PING_ERROR); }); + it("should throw an error if ping cannot resolve", async () => { + const error = new Error("test error"); + networkService.timeRequest = sinon.stub().throws(error); + try { + await networkService.requestPing({ + data: { url: "http://test.com", _id: "123" }, + }); + } catch (error) { + expect(error).to.exist; + expect(error.method).to.equal("requestPing"); + } + }); }); describe("requestHttp", () => { it("should return a response object if http successful", async () => { @@ -113,6 +132,18 @@ describe("Network Service", () => { expect(httpResult.status).to.be.false; expect(httpResult.code).to.equal(networkService.NETWORK_ERROR); }); + it("should throw an error if an error occurs", async () => { + const error = new Error("test error"); + networkService.timeRequest = sinon.stub().throws(error); + try { + await networkService.requestHttp({ + data: { url: "http://test.com", _id: "123" }, + }); + } catch (error) { + expect(error).to.exist; + expect(error.method).to.equal("requestHttp"); + } + }); }); describe("requestPagespeed", () => { @@ -152,6 +183,18 @@ describe("Network Service", () => { expect(pagespeedResult.status).to.be.false; expect(pagespeedResult.code).to.equal(networkService.NETWORK_ERROR); }); + it("should throw an error if pagespeed cannot resolve", async () => { + const error = new Error("test error"); + networkService.timeRequest = sinon.stub().throws(error); + try { + await networkService.requestPagespeed({ + data: { url: "http://test.com", _id: "123" }, + }); + } catch (error) { + expect(error).to.exist; + expect(error.method).to.equal("requestPagespeed"); + } + }); }); describe("requestHardware", () => { @@ -206,6 +249,18 @@ describe("Network Service", () => { expect(httpResult.status).to.be.false; expect(httpResult.code).to.equal(networkService.NETWORK_ERROR); }); + it("should throw an error if hardware cannot resolve", async () => { + const error = new Error("test error"); + networkService.timeRequest = sinon.stub().throws(error); + try { + await networkService.requestHardware({ + data: { url: "http://test.com", _id: "123" }, + }); + } catch (error) { + expect(error).to.exist; + expect(error.method).to.equal("requestHardware"); + } + }); }); describe("requestDocker", () => { @@ -220,12 +275,18 @@ describe("Network Service", () => { it("should return a response object with status false if container not running", async () => { Docker = class { + listContainers = sinon.stub().resolves([ + { + Names: ["/my_container"], + Id: "abc123", + }, + ]); getContainer = sinon.stub().returns({ inspect: sinon.stub().resolves({ State: { Status: "stopped" } }), }); }; networkService = new NetworkService(axios, ping, logger, http, Docker); - const job = { data: { url: "http://test.com", _id: "123", type: "docker" } }; + const job = { data: { url: "abc123", _id: "123", type: "docker" } }; const dockerResult = await networkService.requestDocker(job); expect(dockerResult.status).to.be.false; expect(dockerResult.code).to.equal(200); @@ -233,12 +294,18 @@ describe("Network Service", () => { it("should handle an error when fetching the container", async () => { Docker = class { + listContainers = sinon.stub().resolves([ + { + Names: ["/my_container"], + Id: "abc123", + }, + ]); getContainer = sinon.stub().returns({ inspect: sinon.stub().throws(new Error("test error")), }); }; networkService = new NetworkService(axios, ping, logger, http, Docker); - const job = { data: { url: "http://test.com", _id: "123", type: "docker" } }; + const job = { data: { url: "abc123", _id: "123", type: "docker" } }; const dockerResult = await networkService.requestDocker(job); expect(dockerResult.status).to.be.false; expect(dockerResult.code).to.equal(networkService.NETWORK_ERROR); @@ -246,16 +313,35 @@ describe("Network Service", () => { it("should throw an error if operations fail", async () => { Docker = class { + listContainers = sinon.stub().resolves([ + { + Names: ["/my_container"], + Id: "abc123", + }, + ]); getContainer = sinon.stub().throws(new Error("test error")); }; networkService = new NetworkService(axios, ping, logger, http, Docker); - const job = { data: { url: "http://test.com", _id: "123", type: "docker" } }; + const job = { data: { url: "abc123", _id: "123", type: "docker" } }; try { await networkService.requestDocker(job); } catch (error) { expect(error.message).to.equal("test error"); } }); + it("should throw an error if no matching images found", async () => { + Docker = class { + listContainers = sinon.stub().resolves([]); + getContainer = sinon.stub().throws(new Error("test error")); + }; + networkService = new NetworkService(axios, ping, logger, http, Docker); + const job = { data: { url: "abc123", _id: "123", type: "docker" } }; + try { + await networkService.requestDocker(job); + } catch (error) { + expect(error.message).to.equal(errorMessages.DOCKER_NOT_FOUND); + } + }); }); describe("getStatus", () => { @@ -327,8 +413,6 @@ describe("Network Service", () => { try { await networkService.getStatus({}); } catch (error) { - console.log(error); - expect(error.service).to.equal("NetworkService"); expect(error.method).to.equal("getStatus"); expect(error.message).to.equal("Unsupported type: unknown"); } @@ -337,7 +421,6 @@ describe("Network Service", () => { try { await networkService.getStatus(null); } catch (error) { - console.log(error); expect(error.service).to.equal("NetworkService"); expect(error.method).to.equal("getStatus"); expect(error.message).to.equal("Unsupported type: unknown"); From b5f67a83dd40ea2e73ff038851d99fc36dfb92bf Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 13 Nov 2024 14:57:14 +0800 Subject: [PATCH 15/15] Add error message for docker container not found --- Server/utils/messages.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Server/utils/messages.js b/Server/utils/messages.js index 1a994527a..2db427bd8 100644 --- a/Server/utils/messages.js +++ b/Server/utils/messages.js @@ -58,6 +58,7 @@ const errorMessages = { // Docker DOCKER_FAIL: "Failed to fetch Docker container information", + DOCKER_NOT_FOUND: "Docker container not found", }; const successMessages = {