From 614b362fbdd1dea011268a80e0f04b02fbf6b296 Mon Sep 17 00:00:00 2001 From: Julius Knorr Date: Thu, 12 Sep 2024 23:34:41 +0200 Subject: [PATCH] test: Add vitest and some basic integration tests Signed-off-by: Julius Knorr --- package-lock.json | 401 ++++++++++++++++++++++++++++- package.json | 14 +- tests/integration/metrics.spec.mjs | 59 +++++ tests/integration/socket.spec.mjs | 94 +++++++ vitest.config.js | 10 + websocket_server/SocketManager.js | 2 +- 6 files changed, 568 insertions(+), 12 deletions(-) create mode 100644 tests/integration/metrics.spec.mjs create mode 100644 tests/integration/socket.spec.mjs create mode 100644 vitest.config.js diff --git a/package-lock.json b/package-lock.json index 9999f94..12d355c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "whiteboard", - "version": "0.3.0-beta.1", + "version": "1.0.0-rc.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "whiteboard", - "version": "0.3.0-beta.1", + "version": "1.0.0-rc.2", "license": "AGPL-3.0-or-later", "dependencies": { "@excalidraw/excalidraw": "^0.17.6", @@ -24,6 +24,7 @@ "@nextcloud/sharing": "^0.2.3", "@nextcloud/vue": "^8.17.1", "@socket.io/redis-streams-adapter": "^0.2.2", + "axios": "^1.7.7", "dotenv": "^16.4.5", "express": "^5.0.0", "jsonwebtoken": "^9.0.2", @@ -53,7 +54,8 @@ "typescript": "^5.5.4", "typescript-plugin-css-modules": "^5.1.0", "vite": "^5.4.3", - "vite-plugin-static-copy": "^1.0.6" + "vite-plugin-static-copy": "^1.0.6", + "vitest": "^2.1.0" }, "engines": { "node": "^20", @@ -3031,6 +3033,130 @@ "vue": "^2.7.0-0" } }, + "node_modules/@vitest/expect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.0.tgz", + "integrity": "sha512-N3/xR4fSu0+6sVZETEtPT1orUs2+Y477JOXTcU3xKuu3uBlsgbD7/7Mz2LZ1Jr1XjwilEWlrIgSCj4N1+5ZmsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.0", + "@vitest/utils": "2.1.0", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.0.tgz", + "integrity": "sha512-ZxENovUqhzl+QiOFpagiHUNUuZ1qPd5yYTCYHomGIZOFArzn4mgX2oxZmiAItJWAaXHG6bbpb/DpSPhlk5DgtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "^2.1.0-beta.1", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.11" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/spy": "2.1.0", + "msw": "^2.3.5", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.0.tgz", + "integrity": "sha512-7sxf2F3DNYatgmzXXcTh6cq+/fxwB47RIQqZJFoSH883wnVAoccSRT6g+dTKemUBo8Q5N4OYYj1EBXLuRKvp3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.0.tgz", + "integrity": "sha512-D9+ZiB8MbMt7qWDRJc4CRNNUlne/8E1X7dcKhZVAbcOKG58MGGYVDqAq19xlhNfMFZsW0bpVKgztBwks38Ko0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.0", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.0.tgz", + "integrity": "sha512-x69CygGMzt9VCO283K2/FYQ+nBrOj66OTKpsPykjCR4Ac3lLV+m85hj9reaIGmjBSsKzVvbxWmjWE3kF5ha3uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.0", + "magic-string": "^0.30.11", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.0.tgz", + "integrity": "sha512-IXX5NkbdgTYTog3F14i2LgnBc+20YmkXMx0IWai84mcxySUDRgm0ihbOfR4L0EVRBDFG85GjmQQEZNNKVVpkZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.0.tgz", + "integrity": "sha512-rreyfVe0PuNqJfKYUwfPDfi6rrp0VSu0Wgvp5WBqJonP+4NvXHk48X6oBam1Lj47Hy6jbJtnMj3OcRdrkTP0tA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.0", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@volar/language-core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.11.1.tgz", @@ -3633,6 +3759,16 @@ "util": "^0.12.5" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -3667,9 +3803,9 @@ } }, "node_modules/axios": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", - "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -4136,6 +4272,16 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -4203,6 +4349,23 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -4246,6 +4409,16 @@ "node": "*" } }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -4818,6 +4991,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -6877,6 +7060,16 @@ "node": ">=6.9.0" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -8441,6 +8634,16 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, "node_modules/lru-cache": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", @@ -10177,6 +10380,23 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", @@ -11659,6 +11879,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -11973,6 +12200,13 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -11981,6 +12215,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true, + "license": "MIT" + }, "node_modules/stream-browserify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", @@ -12712,12 +12953,56 @@ "node": ">=0.6.0" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinycolor2": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", "license": "MIT" }, + "node_modules/tinyexec": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", + "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -13454,6 +13739,28 @@ } } }, + "node_modules/vite-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.0.tgz", + "integrity": "sha512-+ybYqBVUjYyIscoLzMWodus2enQDZOpGhcU6HdOVD6n8WZdk12w1GFL3mbnxLs7hPtRtqs1Wo5YF6/Tsr6fmhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.6", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/vite-plugin-css-injected-by-js": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.5.1.tgz", @@ -13996,6 +14303,71 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/vitest": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.0.tgz", + "integrity": "sha512-XuuEeyNkqbfr0FtAvd9vFbInSSNY1ykCQTYQ0sj9wPy4hx+1gR7gqVNdW0AX2wrrM1wWlN5fnJDjF9xG6mYRSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.0", + "@vitest/mocker": "2.1.0", + "@vitest/pretty-format": "^2.1.0", + "@vitest/runner": "2.1.0", + "@vitest/snapshot": "2.1.0", + "@vitest/spy": "2.1.0", + "@vitest/utils": "2.1.0", + "chai": "^5.1.1", + "debug": "^4.3.6", + "magic-string": "^0.30.11", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.0", + "tinypool": "^1.0.0", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.0", + "@vitest/ui": "2.1.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -14304,6 +14676,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index d48808b..e8183f8 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,14 @@ "build": "vite --mode production build", "dev": "vite --mode development build", "watch": "vite --mode development build --watch", - "lint": "eslint --ext .js,.ts,.tsx,.vue src websocket_server", - "lint:fix": "eslint --ext .js,.ts,.tsx,.vue src websocket_server --fix", + "lint": "eslint --ext .js,.mjs,.ts,.tsx,.vue src websocket_server tests/integration ", + "lint:fix": "eslint --ext .js,.mjs,.ts,.tsx,.vue src websocket_server tests/integration --fix", "stylelint": "stylelint 'src/**/*.{css,scss,sass}'", "stylelint:fix": "stylelint 'src/**/*.{css,scss,sass}' --fix", "server:start": "node websocket_server/main.js", - "server:watch": "nodemon websocket_server/main.js" + "server:watch": "nodemon websocket_server/main.js", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { "@excalidraw/excalidraw": "^0.17.6", @@ -32,6 +34,7 @@ "@nextcloud/sharing": "^0.2.3", "@nextcloud/vue": "^8.17.1", "@socket.io/redis-streams-adapter": "^0.2.2", + "axios": "^1.7.7", "dotenv": "^16.4.5", "express": "^5.0.0", "jsonwebtoken": "^9.0.2", @@ -61,7 +64,8 @@ "typescript": "^5.5.4", "typescript-plugin-css-modules": "^5.1.0", "vite": "^5.4.3", - "vite-plugin-static-copy": "^1.0.6" + "vite-plugin-static-copy": "^1.0.6", + "vitest": "^2.1.0" }, "prettier": { "useTabs": true, @@ -85,4 +89,4 @@ "node": "^20", "npm": "^10" } -} \ No newline at end of file +} diff --git a/tests/integration/metrics.spec.mjs b/tests/integration/metrics.spec.mjs new file mode 100644 index 0000000..ef18cea --- /dev/null +++ b/tests/integration/metrics.spec.mjs @@ -0,0 +1,59 @@ +import { beforeAll, afterAll, describe, it, expect, vi } from 'vitest' +import axios from 'axios' +import ServerManager from '../../websocket_server/ServerManager.js' + +const SERVER_URL = 'http://localhost:3008' +const SECRET = 'secret' + +vi.stubEnv('METRICS_TOKEN', SECRET) + +describe('Metrics endpoint', () => { + let serverManager + + beforeAll(async () => { + serverManager = new ServerManager({ + port: 3008, + storageStrategy: 'lru', + }) + + serverManager.start() + }) + + afterAll(async () => { + await serverManager.server.close() + }) + + it('should work with bearer auth', async () => { + const response = await axios.get(`${SERVER_URL}/metrics`, { + headers: { + Authorization: `Bearer ${SECRET}`, + }, + }) + expect(response.status).toBe(200) + expect(response.data).toContain('whiteboard_memory_usage{type="rss"}') + expect(response.data).toContain('whiteboard_room_stats{stat="activeRooms"}') + expect(response.data).toContain('whiteboard_room_stats{stat="totalUsers"}') + expect(response.data).toContain('whiteboard_room_stats{stat="totalDataSize"}') + expect(response.data).toContain('whiteboard_cache_info{info="size"}') + expect(response.data).toContain('socket_io_connected') + }) + + it('should work with token param', async () => { + const response = await axios.get(`${SERVER_URL}/metrics?token=${SECRET}`) + expect(response.status).toBe(200) + expect(response.data).toContain('whiteboard_room_stats{stat="activeRooms"}') + }) + + it('Not return on invalid auth', async () => { + try { + await axios.get(`${SERVER_URL}/metrics`, { + headers: { + Authorization: 'Bearer wrongtoken', + }, + }) + expect(true).toBe(false) + } catch (error) { + expect(error.response.status).toBe(403) + } + }) +}) diff --git a/tests/integration/socket.spec.mjs b/tests/integration/socket.spec.mjs new file mode 100644 index 0000000..292f9cc --- /dev/null +++ b/tests/integration/socket.spec.mjs @@ -0,0 +1,94 @@ +import { beforeAll, afterAll, describe, it, expect, vi } from 'vitest' +import ServerManager from '../../websocket_server/ServerManager.js' +import io from 'socket.io-client' +import jwt from 'jsonwebtoken' +import Utils from '../../websocket_server/Utils.js' + +const SERVER_URL = 'http://localhost:3009' +const SECRET = 'secret' + +vi.stubEnv('JWT_SECRET_KEY', SECRET) + +function waitFor(socket, event) { + return new Promise((resolve) => { + socket.once(event, resolve) + }) +} + +describe('Socket handling', () => { + let serverManager, socket + + beforeAll(async () => { + serverManager = new ServerManager({ + port: 3009, + storageStrategy: 'lru', + }) + + serverManager.start() + + socket = io(SERVER_URL, { + auth: { + token: jwt.sign({ roomID: 123, user: { name: 'Admin' } }, SECRET), + }, + }) + + socket.on('connect_error', (error) => { + throw error + }) + }) + + afterAll(async () => { + await socket.disconnect() + await serverManager.server.close() + }) + + it('socket invalid jwt', async () => { + const socket = io(SERVER_URL, { + auth: { + token: jwt.sign({ roomID: 123, user: { name: 'Admin' } }, 'wrongsecret'), + }, + }) + return new Promise((resolve) => { + socket.on('connect_error', () => { + resolve() + }) + }) + }) + + it('socket valid jwt', async () => { + const socket = io(SERVER_URL, { + auth: { + token: jwt.sign({ roomID: 123, user: { name: 'Admin' } }, SECRET), + }, + }) + return new Promise((resolve) => { + socket.on('connect', () => { + resolve() + }) + }) + }) + + it('join room', async () => { + const joinedDataMessage = waitFor(socket, 'joined-data') + socket.emit('join-room', 123) + const result = await joinedDataMessage + const roomData = JSON.parse(Utils.convertArrayBufferToString(result)) + + expect(roomData).toEqual([]) + }) + + it('read only socket', async () => { + const socket = io(SERVER_URL, { + auth: { + token: jwt.sign({ roomID: 123, user: { name: 'Admin' }, isFileReadOnly: true }, SECRET), + }, + }) + return new Promise((resolve) => { + const readOnlyMessage = waitFor(socket, 'read-only') + socket.on('connect', async () => { + await readOnlyMessage + resolve() + }) + }) + }) +}) diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..53b9740 --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: [ + 'tests/integration/*.spec.?(c|m)[jt]s?(x)' + ], + }, + }) diff --git a/websocket_server/SocketManager.js b/websocket_server/SocketManager.js index fa3a031..7d34338 100644 --- a/websocket_server/SocketManager.js +++ b/websocket_server/SocketManager.js @@ -23,7 +23,7 @@ export default class SocketManager { this.socketDataManager = new SocketDataManager(storageManager) this.io = new SocketIO(server, { - transports: ['websocket'], + transports: ['websocket', 'polling'], cors: { origin: process.env.NEXTCLOUD_URL || 'http://nextcloud.local', methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],