From c1dcf103c32a39b32ac3b8edd643bb55e11679ab Mon Sep 17 00:00:00 2001 From: Geoff House Date: Tue, 2 Jun 2026 12:56:04 +0100 Subject: [PATCH] [bug-app] added tests for bug server --- .github/workflows/test-bug-server.yml | 38 ++ ...er-tests.yml => test-module-container.yml} | 2 +- .gitignore | 1 + package.json | 469 +++++++++--------- src/server/Dockerfile.test | 17 + src/server/jest.server.config.cjs | 31 ++ src/server/routes/icons.test.js | 2 +- src/server/routes/log.test.js | 21 + src/server/routes/login.test.js | 44 ++ src/server/routes/logout.test.js | 13 + src/server/routes/panel.test.js | 14 +- src/server/routes/panelconfig.test.js | 17 +- src/server/routes/panelgroup.test.js | 26 + src/server/routes/proxy.test.js | 27 + src/server/routes/strategy.test.js | 71 +++ src/server/routes/user.test.js | 95 ++++ 16 files changed, 647 insertions(+), 241 deletions(-) create mode 100644 .github/workflows/test-bug-server.yml rename .github/workflows/{module-container-tests.yml => test-module-container.yml} (99%) create mode 100644 src/server/Dockerfile.test create mode 100644 src/server/jest.server.config.cjs create mode 100644 src/server/routes/log.test.js create mode 100644 src/server/routes/login.test.js create mode 100644 src/server/routes/logout.test.js create mode 100644 src/server/routes/panelgroup.test.js create mode 100644 src/server/routes/proxy.test.js create mode 100644 src/server/routes/strategy.test.js create mode 100644 src/server/routes/user.test.js diff --git a/.github/workflows/test-bug-server.yml b/.github/workflows/test-bug-server.yml new file mode 100644 index 000000000..9d7364c1d --- /dev/null +++ b/.github/workflows/test-bug-server.yml @@ -0,0 +1,38 @@ +name: BUG Server Tests + +permissions: + contents: read + +on: + push: + branches: + - main + paths: + - "src/server/**" + - "config/**" + - "package.json" + - "package-lock.json" + - ".github/workflows/test-bug-server.yml" + pull_request: + paths: + - "src/server/**" + - "config/**" + - "package.json" + - "package-lock.json" + - ".github/workflows/test-bug-server.yml" + +jobs: + server-tests: + name: Server Tests + runs-on: ubuntu-latest + steps: + - name: checkout repository + uses: actions/checkout@v6 + + - name: setup node + uses: actions/setup-node@v6 + with: + node-version: 22 + + - name: run dockerized server tests + run: npm run test:server diff --git a/.github/workflows/module-container-tests.yml b/.github/workflows/test-module-container.yml similarity index 99% rename from .github/workflows/module-container-tests.yml rename to .github/workflows/test-module-container.yml index 2dcbe44f4..1065374e1 100644 --- a/.github/workflows/module-container-tests.yml +++ b/.github/workflows/test-module-container.yml @@ -10,7 +10,7 @@ on: paths: - "src/modules/**" - "src/server/core/**" - - ".github/workflows/module-container-tests.yml" + - ".github/workflows/test-module-container.yml" jobs: discover-modules: diff --git a/.gitignore b/.gitignore index b16eb1268..394dccee3 100755 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ node_modules Dockerfile* !/src/server/Dockerfile !/src/server/Dockerfile.dev +!/src/server/Dockerfile.test !/src/server/services/dockerfile-write.js # global environment files diff --git a/package.json b/package.json index ea2b7542e..dabeb2f56 100644 --- a/package.json +++ b/package.json @@ -1,237 +1,238 @@ { - "name": "bug-monorepo", - "version": "3.1.93", - "description": "Broadcast Universal Gateway", - "private": true, - "workspaces": [ - "src/client" - ], - "scripts": { - "predev": "node src/client/scripts/generateMuiIcons.mjs", - "dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"", - "dev:server": "nodemon ./src/server/bin/bug-dev.js", - "dev:client": "npm run dev -w src/client", - "build:client": "npm run build -w src/client", - "start": "node ./src/server/bin/bug-prod.js", - "install-all": "npm install", - "modulebuilder": "node ./src/modulebuilder/cli.mjs", - "format:check": "prettier --check .", - "format:write": "prettier --write .", - "docs:serve": "cd docs && node scripts/generateModules.js && RUBYOPT='-W0' bundle exec jekyll serve --livereload --config _config.yml,_config.local.yml" - }, - "nodemonConfig": { - "ignore": [ - "src/client/*", - "src/config/*", - "**/node_modules/*", - "**/container/*", - "**/docs/*", - "dist/*" + "name": "bug-monorepo", + "version": "3.1.93", + "description": "Broadcast Universal Gateway", + "private": true, + "workspaces": [ + "src/client" ], - "watch": [ - "src/server" - ], - "ext": "js,mjs,cjs,json" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/bbc/bug.git" - }, - "author": "Geoff House", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/bbc/bug/issues" - }, - "dependencies": { - "@apidevtools/swagger-parser": "^10.1.0", - "@dnd-kit/core": "^2.1.2", - "@dnd-kit/sortable": "^2.0.1", - "@dnd-kit/utilities": "^2.0.0", - "@emotion/css": "^11.1.3", - "@emotion/react": "^11.14.0", - "@emotion/styled": "^11.14.1", - "@fortawesome/fontawesome-svg-core": "^6.7.2", - "@fortawesome/free-solid-svg-icons": "^6.7.2", - "@fortawesome/react-fontawesome": "^3.1.1", - "@iconify-icons/mdi": "^1.2.48", - "@iconify/react": "^6.0.2", - "@mdi/js": "^7.4.47", - "@monaco-editor/react": "^4.7.0", - "@mui/icons-material": "^7.3.6", - "@mui/material": "^7.3.6", - "@mui/system": "^7.3.6", - "@mui/x-date-pickers": "^8.22.0", - "@reduxjs/toolkit": "^2.11.2", - "@storybook/theming": "^6.4.18", - "@testing-library/jest-dom": "^5.11.4", - "ansi-to-html": "^0.7.2", - "axios": "^1.15.0", - "bcryptjs": "^2.4.3", - "change-case": "^4.1.2", - "cidr-regex": "^3.1.1", - "clsx": "^2.1.1", - "concurrently": "^7.2.1", - "connect-mongodb-session": "^3.1.1", - "convert-to-ranges": "^1.0.0", - "cookie-parser": "^1.4.6", - "cors": "^2.8.5", - "date-fns": "^4.1.0", - "delay": "^5.0.0", - "dockerode": "^4.0.8", - "dotenv": "^10.0.0", - "ejs": "^3.1.8", - "eslint-plugin-jsx-a11y": "^6.8.0", - "express": "^4.18.1", - "express-async-handler": "^1.2.0", - "express-fileupload": "^1.4.0", - "express-rate-limit": "^7.5.0", - "express-session": "^1.17.3", - "fs-extra": "^10.1.0", - "glob": "^13.0.0", - "helmet": "^5.1.0", - "html-react-parser": "^1.4.14", - "inquirer": "^9.0.0", - "ip-range-check": "^0.2.0", - "javascript-time-ago": "^2.3.10", - "leaflet": "^1.9.4", - "lodash": "^4.17.21", - "md5": "^2.3.0", - "module-alias": "^2.2.2", - "mongodb": "^4.6.0", - "morgan": "^1.10.0", - "nanoid": "^3.3.4", - "node-cache": "^5.1.2", - "node-schedule": "^2.1.1", - "notistack": "^3.0.2", - "openapi-types": "^10.0.0", - "passport": "^0.5.3", - "passport-http-header-strategy": "^1.1.0", - "passport-local": "^1.0.0", - "passport-openid": "^0.4.0", - "passport-saml": "^3.2.0", - "postcss": "^8.4.6", - "quill": "^2.0.3", - "react": "^18.3.1", - "react-clock": "^6.0.0", - "react-colorful": "^5.6.1", - "react-cookie": "^4.1.1", - "react-cool-dimensions": "^2.0.7", - "react-custom-scrollbars-2": "^4.5.0", - "react-dom": "^18.3.1", - "react-flow-renderer": "^10.3.5", - "react-hook-form": "^7.68.0", - "react-hotkeys-hook": "^3.3.1", - "react-iframe": "^1.8.0", - "react-leaflet": "^4.2.1", - "react-masonry-css": "^1.0.16", - "react-modal-hook": "^3.0.0", - "react-open-weather": "^1.1.3", - "react-player": "^2.11.0", - "react-quill-new": "^3.7.0", - "react-redux": "^9.2.0", - "react-router-dom": "6.22.3", - "react-sparklines": "^1.6.0", - "react-table": "^7.7.0", - "react-textfit": "^1.1.1", - "recharts": "^3.6.0", - "rgb-hex": "^4.0.1", - "serve-favicon": "^2.5.0", - "socket.io": "^4.5.1", - "socket.io-client": "^4.0.1", - "swagger-jsdoc": "^6.2.1", - "swagger-ui-express": "^4.4.0", - "systeminformation": "^5.16.1", - "tar": "^7.5.13", - "text-hex": "^1.0.0", - "timezones.json": "^1.6.1", - "use-async-effect": "^2.2.3", - "use-debounce": "^6.0.1", - "use-long-press": "^2.0.2", - "uuid": "^9.0.1", - "video.js": "^7.20.3", - "web-vitals": "^1.0.1", - "wildcard-match": "^5.1.2", - "winston": "^3.19.0", - "winston-daily-rotate-file": "^4.7.1", - "winston-mongodb": "^5.0.7" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "DELETEME_jest": { - "rootDir": ".", - "testEnvironment": "node", - "testPathIgnorePatterns": [ - "./node_modules/", - "./modulebuilder/", - "./modules/", - "./client/", - "./routes/icons.test.js" - ], - "setupFilesAfterEnv": [ - "jest-extended/all" - ], - "moduleNameMapper": { - "@modules/(.*)": "/modules/$1", - "@components/(.*)": "/src/client/components/$1", - "@core/(.*)": "/src/server/core/$1", - "@data/(.*)": "/src/server/data/$1", - "@redux/(.*)": "/src/client/src/redux/$1", - "@utils/(.*)": "/src/utils/$1", - "@hooks/(.*)": "/src/hooks/$1", - "@root/(.*)": "/$1", - "@bin/(.*)": "/bin/$1", - "@filters/(.*)": "/filters/$1", - "@routes/(.*)": "/routes/$1", - "@models/(.*)": "/models/$1", - "@workers/(.*)": "/workers/$1", - "@sockets/(.*)": "/sockets/$1", - "@services/(.*)": "/services/$1", - "@middleware/(.*)": "/middleware/$1" + "scripts": { + "predev": "node src/client/scripts/generateMuiIcons.mjs", + "dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"", + "dev:server": "nodemon ./src/server/bin/bug-dev.js", + "dev:client": "npm run dev -w src/client", + "build:client": "npm run build -w src/client", + "start": "node ./src/server/bin/bug-prod.js", + "install-all": "npm install", + "modulebuilder": "node ./src/modulebuilder/cli.mjs", + "format:check": "prettier --check .", + "format:write": "prettier --write .", + "docs:serve": "cd docs && node scripts/generateModules.js && RUBYOPT='-W0' bundle exec jekyll serve --livereload --config _config.yml,_config.local.yml", + "test:server": "docker build -f ./src/server/Dockerfile.test -t bug-server-test . && docker run --rm --name bug-server-test-run bug-server-test sh -lc 'if find ./src/server -path ./node_modules -prune -o -type f -name \"*.test.js\" -print | grep -q . || find ./src/server -path ./node_modules -prune -o -type f -name \"*.spec.js\" -print | grep -q .; then npx jest --runInBand --config ./src/server/jest.server.config.cjs; else echo \"No server tests found, skipping.\"; fi'" + }, + "nodemonConfig": { + "ignore": [ + "src/client/*", + "src/config/*", + "**/node_modules/*", + "**/container/*", + "**/docs/*", + "dist/*" + ], + "watch": [ + "src/server" + ], + "ext": "js,mjs,cjs,json" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/bbc/bug.git" + }, + "author": "Geoff House", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/bbc/bug/issues" + }, + "dependencies": { + "@apidevtools/swagger-parser": "^10.1.0", + "@dnd-kit/core": "^2.1.2", + "@dnd-kit/sortable": "^2.0.1", + "@dnd-kit/utilities": "^2.0.0", + "@emotion/css": "^11.1.3", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^3.1.1", + "@iconify-icons/mdi": "^1.2.48", + "@iconify/react": "^6.0.2", + "@mdi/js": "^7.4.47", + "@monaco-editor/react": "^4.7.0", + "@mui/icons-material": "^7.3.6", + "@mui/material": "^7.3.6", + "@mui/system": "^7.3.6", + "@mui/x-date-pickers": "^8.22.0", + "@reduxjs/toolkit": "^2.11.2", + "@storybook/theming": "^6.4.18", + "@testing-library/jest-dom": "^5.11.4", + "ansi-to-html": "^0.7.2", + "axios": "^1.15.0", + "bcryptjs": "^2.4.3", + "change-case": "^4.1.2", + "cidr-regex": "^3.1.1", + "clsx": "^2.1.1", + "concurrently": "^7.2.1", + "connect-mongodb-session": "^3.1.1", + "convert-to-ranges": "^1.0.0", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5", + "date-fns": "^4.1.0", + "delay": "^5.0.0", + "dockerode": "^4.0.8", + "dotenv": "^10.0.0", + "ejs": "^3.1.8", + "eslint-plugin-jsx-a11y": "^6.8.0", + "express": "^4.18.1", + "express-async-handler": "^1.2.0", + "express-fileupload": "^1.4.0", + "express-rate-limit": "^7.5.0", + "express-session": "^1.17.3", + "fs-extra": "^10.1.0", + "glob": "^13.0.0", + "helmet": "^5.1.0", + "html-react-parser": "^1.4.14", + "inquirer": "^9.0.0", + "ip-range-check": "^0.2.0", + "javascript-time-ago": "^2.3.10", + "leaflet": "^1.9.4", + "lodash": "^4.17.21", + "md5": "^2.3.0", + "module-alias": "^2.2.2", + "mongodb": "^4.6.0", + "morgan": "^1.10.0", + "nanoid": "^3.3.4", + "node-cache": "^5.1.2", + "node-schedule": "^2.1.1", + "notistack": "^3.0.2", + "openapi-types": "^10.0.0", + "passport": "^0.5.3", + "passport-http-header-strategy": "^1.1.0", + "passport-local": "^1.0.0", + "passport-openid": "^0.4.0", + "passport-saml": "^3.2.0", + "postcss": "^8.4.6", + "quill": "^2.0.3", + "react": "^18.3.1", + "react-clock": "^6.0.0", + "react-colorful": "^5.6.1", + "react-cookie": "^4.1.1", + "react-cool-dimensions": "^2.0.7", + "react-custom-scrollbars-2": "^4.5.0", + "react-dom": "^18.3.1", + "react-flow-renderer": "^10.3.5", + "react-hook-form": "^7.68.0", + "react-hotkeys-hook": "^3.3.1", + "react-iframe": "^1.8.0", + "react-leaflet": "^4.2.1", + "react-masonry-css": "^1.0.16", + "react-modal-hook": "^3.0.0", + "react-open-weather": "^1.1.3", + "react-player": "^2.11.0", + "react-quill-new": "^3.7.0", + "react-redux": "^9.2.0", + "react-router-dom": "6.22.3", + "react-sparklines": "^1.6.0", + "react-table": "^7.7.0", + "react-textfit": "^1.1.1", + "recharts": "^3.6.0", + "rgb-hex": "^4.0.1", + "serve-favicon": "^2.5.0", + "socket.io": "^4.5.1", + "socket.io-client": "^4.0.1", + "swagger-jsdoc": "^6.2.1", + "swagger-ui-express": "^4.4.0", + "systeminformation": "^5.16.1", + "tar": "^7.5.13", + "text-hex": "^1.0.0", + "timezones.json": "^1.6.1", + "use-async-effect": "^2.2.3", + "use-debounce": "^6.0.1", + "use-long-press": "^2.0.2", + "uuid": "^9.0.1", + "video.js": "^7.20.3", + "web-vitals": "^1.0.1", + "wildcard-match": "^5.1.2", + "winston": "^3.19.0", + "winston-daily-rotate-file": "^4.7.1", + "winston-mongodb": "^5.0.7" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "DELETEME_jest": { + "rootDir": ".", + "testEnvironment": "node", + "testPathIgnorePatterns": [ + "./node_modules/", + "./modulebuilder/", + "./modules/", + "./client/", + "./routes/icons.test.js" + ], + "setupFilesAfterEnv": [ + "jest-extended/all" + ], + "moduleNameMapper": { + "@modules/(.*)": "/modules/$1", + "@components/(.*)": "/src/client/components/$1", + "@core/(.*)": "/src/server/core/$1", + "@data/(.*)": "/src/server/data/$1", + "@redux/(.*)": "/src/client/src/redux/$1", + "@utils/(.*)": "/src/utils/$1", + "@hooks/(.*)": "/src/hooks/$1", + "@root/(.*)": "/$1", + "@bin/(.*)": "/bin/$1", + "@filters/(.*)": "/filters/$1", + "@routes/(.*)": "/routes/$1", + "@models/(.*)": "/models/$1", + "@workers/(.*)": "/workers/$1", + "@sockets/(.*)": "/sockets/$1", + "@services/(.*)": "/services/$1", + "@middleware/(.*)": "/middleware/$1" + } + }, + "_moduleAliases": { + "@root": "./src", + "@bin": "./src/server/bin/", + "@core": "./src/server/core/", + "@filters": "./src/server/filters/", + "@routes": "./src/server/routes/", + "@models": "./src/server/models/", + "@utils": "./src/server/utils/", + "@services": "./src/server/services/", + "@sockets": "./src/server/sockets/", + "@workers": "./src/server/workers/", + "@middleware": "./src/server/middleware/" + }, + "devDependencies": { + "@babel/core": "^7.14.3", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@storybook/addon-actions": "^6.5", + "@storybook/addon-essentials": "^6.5.16", + "@storybook/addon-links": "^6.5", + "@storybook/builder-vite": "^10.2.0", + "@storybook/builder-webpack5": "^6.5.16", + "@storybook/manager-webpack5": "^6.5.16", + "@storybook/react": "^10.3.5", + "@storybook/storybook-deployer": "^2.8.11", + "@vitejs/plugin-react": "^5.1.2", + "babel-plugin-bulk-import": "^2.0.0", + "jest": "^30.0.0", + "jest-extended": "^6.0.0", + "nodemon": "^3.1.10", + "prettier": "^3.7.4", + "supertest": "^7.1.1", + "vite": "^7.3.0", + "vite-plugin-svgr": "^4.5.0" + }, + "prettier": { + "trailingComma": "es5", + "tabWidth": 4, + "semi": true, + "singleQuote": false, + "printWidth": 120 } - }, - "_moduleAliases": { - "@root": "./src", - "@bin": "./src/server/bin/", - "@core": "./src/server/core/", - "@filters": "./src/server/filters/", - "@routes": "./src/server/routes/", - "@models": "./src/server/models/", - "@utils": "./src/server/utils/", - "@services": "./src/server/services/", - "@sockets": "./src/server/sockets/", - "@workers": "./src/server/workers/", - "@middleware": "./src/server/middleware/" - }, - "devDependencies": { - "@babel/core": "^7.14.3", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@storybook/addon-actions": "^6.5", - "@storybook/addon-essentials": "^6.5.16", - "@storybook/addon-links": "^6.5", - "@storybook/builder-vite": "^10.2.0", - "@storybook/builder-webpack5": "^6.5.16", - "@storybook/manager-webpack5": "^6.5.16", - "@storybook/react": "^10.3.5", - "@storybook/storybook-deployer": "^2.8.11", - "@vitejs/plugin-react": "^5.1.2", - "babel-plugin-bulk-import": "^2.0.0", - "jest": "^30.0.0", - "jest-extended": "^6.0.0", - "nodemon": "^3.1.10", - "prettier": "^3.7.4", - "supertest": "^7.1.1", - "vite": "^7.3.0", - "vite-plugin-svgr": "^4.5.0" - }, - "prettier": { - "trailingComma": "es5", - "tabWidth": 4, - "semi": true, - "singleQuote": false, - "printWidth": 120 - } -} \ No newline at end of file +} diff --git a/src/server/Dockerfile.test b/src/server/Dockerfile.test new file mode 100644 index 000000000..7dd91f961 --- /dev/null +++ b/src/server/Dockerfile.test @@ -0,0 +1,17 @@ +FROM node:22-alpine + +WORKDIR /home/node/bug + +# install dependencies from root workspace lockfile +COPY package*.json ./ +RUN npm ci --legacy-peer-deps + +# copy runtime config and only source needed by server tests +COPY config ./config +COPY src/server ./src/server +COPY src/modules ./src/modules +COPY src/client/public/icons/favicon.ico ./src/client/public/icons/favicon.ico + +ENV NODE_ENV=test + +CMD ["npm", "run", "test:server"] \ No newline at end of file diff --git a/src/server/jest.server.config.cjs b/src/server/jest.server.config.cjs new file mode 100644 index 000000000..e819619ff --- /dev/null +++ b/src/server/jest.server.config.cjs @@ -0,0 +1,31 @@ +module.exports = { + rootDir: ".", + testEnvironment: "node", + roots: [""], + testMatch: ["**/*.test.js", "**/*.spec.js"], + setupFilesAfterEnv: ["jest-extended/all"], + moduleNameMapper: { + "^@root$": "/..", + "^@root/(.*)$": "/../$1", + "^@bin$": "/bin", + "^@bin/(.*)$": "/bin/$1", + "^@core$": "/core", + "^@core/(.*)$": "/core/$1", + "^@filters$": "/filters", + "^@filters/(.*)$": "/filters/$1", + "^@routes$": "/routes", + "^@routes/(.*)$": "/routes/$1", + "^@models$": "/models", + "^@models/(.*)$": "/models/$1", + "^@utils$": "/utils", + "^@utils/(.*)$": "/utils/$1", + "^@services$": "/services", + "^@services/(.*)$": "/services/$1", + "^@sockets$": "/sockets", + "^@sockets/(.*)$": "/sockets/$1", + "^@workers$": "/workers", + "^@workers/(.*)$": "/workers/$1", + "^@middleware$": "/middleware", + "^@middleware/(.*)$": "/middleware/$1", + }, +}; \ No newline at end of file diff --git a/src/server/routes/icons.test.js b/src/server/routes/icons.test.js index b40d8afd8..5adf060ef 100644 --- a/src/server/routes/icons.test.js +++ b/src/server/routes/icons.test.js @@ -5,7 +5,7 @@ afterAll(async () => { await new Promise((resolve) => setTimeout(() => resolve(), 500)); }); -const testIcons = ["mdi-abacus", "access-alarm"]; +const testIcons = ["mdi-abacus", "AccessAlarm"]; describe("Test the '/api/icons/' endpoint", () => { test("Test the '/icons' getall route", async () => { diff --git a/src/server/routes/log.test.js b/src/server/routes/log.test.js new file mode 100644 index 000000000..029c484fe --- /dev/null +++ b/src/server/routes/log.test.js @@ -0,0 +1,21 @@ +const request = require("supertest"); + +jest.mock("@middleware/restrict", () => ({ + to: () => (req, res, next) => next(), +})); + +jest.mock("@services/docker-getlogs", () => { + const { Readable } = require("stream"); + return jest.fn(async () => Readable.from(["first line\n"])); +}); + +const system = require("@bin/api"); + +describe("Test the '/api/log/' endpoint", () => { + test("Test the '/:containerId' GET route", async () => { + const response = await request(system).get("/api/log/test-container"); + expect(response.statusCode).toBe(200); + expect(response.headers["content-type"]).toContain("text/event-stream"); + expect(response.text).toContain("event: log"); + }); +}); diff --git a/src/server/routes/login.test.js b/src/server/routes/login.test.js new file mode 100644 index 000000000..394679605 --- /dev/null +++ b/src/server/routes/login.test.js @@ -0,0 +1,44 @@ +const request = require("supertest"); +const passport = require("passport"); + +jest.mock("@services/strategy-list", () => jest.fn(async () => [])); +jest.mock("@services/user-get", () => jest.fn(async () => ({ id: "user-1", username: "tester" }))); + +const authenticateSpy = jest.spyOn(passport, "authenticate"); + +const system = require("@bin/api"); + +describe("Test the '/api/login/' endpoint", () => { + afterEach(() => { + authenticateSpy.mockReset(); + }); + + test("Test successful login", async () => { + authenticateSpy.mockImplementation((strategies, callback) => { + return (req, res, next) => callback(null, "user-1", null); + }); + + const response = await request(system) + .post("/api/login/") + .send({ username: "tester", password: "password" }) + .set("Content-Type", "application/json"); + + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + expect(response.body.data.username).toBe("tester"); + }); + + test("Test failed login", async () => { + authenticateSpy.mockImplementation((strategies, callback) => { + return (req, res, next) => callback(null, null, null); + }); + + const response = await request(system) + .post("/api/login/") + .send({ username: "tester", password: "bad" }) + .set("Content-Type", "application/json"); + + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("failure"); + }); +}); diff --git a/src/server/routes/logout.test.js b/src/server/routes/logout.test.js new file mode 100644 index 000000000..10ff5e4c0 --- /dev/null +++ b/src/server/routes/logout.test.js @@ -0,0 +1,13 @@ +const request = require("supertest"); + +jest.mock("@services/user-get", () => jest.fn(async () => ({ username: "tester" }))); + +const system = require("@bin/api"); + +describe("Test the '/api/logout/' endpoint", () => { + test("Test the '/' POST route", async () => { + const response = await request(system).post("/api/logout/"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); +}); diff --git a/src/server/routes/panel.test.js b/src/server/routes/panel.test.js index 1197fdaff..775ae9051 100644 --- a/src/server/routes/panel.test.js +++ b/src/server/routes/panel.test.js @@ -1,9 +1,19 @@ const request = require("supertest"); -const system = require("@bin/api"); const panelDelete = require("@services/panel-delete"); + +jest.mock("@services/docker-listimages", () => jest.fn(async () => [])); +jest.mock("@models/docker-container", () => ({ + get: jest.fn(async () => null), + list: jest.fn(async () => []), + set: jest.fn(async () => true), + setMultiple: jest.fn(async () => true), +})); + +const system = require("@bin/api"); +const testPanelId = `test-${Date.now()}-${Math.floor(Math.random() * 10000)}`; const testPanel = { module: "clock", - id: "test", + id: testPanelId, title: "test-title", group: "test-group", description: "test-description", diff --git a/src/server/routes/panelconfig.test.js b/src/server/routes/panelconfig.test.js index c1717a17e..6f0469aeb 100644 --- a/src/server/routes/panelconfig.test.js +++ b/src/server/routes/panelconfig.test.js @@ -1,17 +1,28 @@ const request = require("supertest"); -const system = require("@bin/api"); const panelDelete = require("@services/panel-delete"); const panelAdd = require("@services/panel-add"); + +jest.mock("@services/docker-listimages", () => jest.fn(async () => [])); +jest.mock("@models/docker-container", () => ({ + get: jest.fn(async () => null), + list: jest.fn(async () => []), + set: jest.fn(async () => true), + setMultiple: jest.fn(async () => true), +})); +jest.mock("@services/panelconfig-push", () => jest.fn(async () => true)); + +const system = require("@bin/api"); +const testPanelId = `test-${Date.now()}-${Math.floor(Math.random() * 10000)}`; const testPanel = { module: "clock", - id: "test", + id: testPanelId, title: "test-title", group: "test-group", description: "test-description", }; const newTestPanel = { module: "clock", - id: "test", + id: testPanelId, title: "test-title", group: "test-group", description: "new-test-description", diff --git a/src/server/routes/panelgroup.test.js b/src/server/routes/panelgroup.test.js new file mode 100644 index 000000000..342d5368b --- /dev/null +++ b/src/server/routes/panelgroup.test.js @@ -0,0 +1,26 @@ +const request = require("supertest"); + +jest.mock("@middleware/restrict", () => ({ + to: () => (req, res, next) => next(), +})); + +jest.mock("@services/panelgroup-rename", () => jest.fn(async () => true)); +jest.mock("@services/panelgroup-delete", () => jest.fn(async () => true)); + +const system = require("@bin/api"); + +describe("Test the '/api/panelgroup/' endpoint", () => { + test("Test the '/:groupName/:newGroupName' PUT route", async () => { + const response = await request(system).put("/api/panelgroup/oldGroup/newGroup"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + expect(response.body.data).toBeTrue(); + }); + + test("Test the '/:groupName' DELETE route", async () => { + const response = await request(system).delete("/api/panelgroup/newGroup"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + expect(response.body.data).toBeTrue(); + }); +}); diff --git a/src/server/routes/proxy.test.js b/src/server/routes/proxy.test.js new file mode 100644 index 000000000..92f1eb723 --- /dev/null +++ b/src/server/routes/proxy.test.js @@ -0,0 +1,27 @@ +const request = require("supertest"); + +jest.mock("@middleware/restrict", () => ({ + to: () => (req, res, next) => next(), +})); + +jest.mock("@models/panel-config", () => ({ + get: jest.fn(async () => ({ id: "test-panel", module: "clock" })), +})); + +jest.mock("axios", () => { + const { Readable } = require("stream"); + return jest.fn(async () => ({ + status: 200, + data: Readable.from(["ok"]), + })); +}); + +const system = require("@bin/api"); + +describe("Test the '/container/' endpoint", () => { + test("Test the '/:panelid/*' proxy route", async () => { + const response = await request(system).get("/container/test-panel/status"); + expect(response.statusCode).toBe(200); + expect(response.text).toBe("ok"); + }); +}); diff --git a/src/server/routes/strategy.test.js b/src/server/routes/strategy.test.js new file mode 100644 index 000000000..35c0f3030 --- /dev/null +++ b/src/server/routes/strategy.test.js @@ -0,0 +1,71 @@ +const request = require("supertest"); + +jest.mock("@middleware/restrict", () => ({ + to: () => (req, res, next) => next(), +})); + +jest.mock("@services/strategy-list", () => jest.fn(async () => [{ type: "local", enabled: true }])); +jest.mock("@services/strategy-listsafe", () => jest.fn(async () => [{ type: "local", enabled: true }])); +jest.mock("@services/strategy-reorder", () => jest.fn(async () => true)); +jest.mock("@services/strategy-get", () => jest.fn(async () => ({ type: "local", enabled: true }))); +jest.mock("@services/strategy-getsafe", () => jest.fn(async () => ({ type: "local", enabled: true }))); +jest.mock("@services/strategy-state", () => jest.fn(async () => true)); +jest.mock("@services/strategy-update", () => jest.fn(async () => true)); + +const system = require("@bin/api"); + +describe("Test the '/api/strategy/' endpoint", () => { + test("Test the '/' GET route", async () => { + const response = await request(system).get("/api/strategy/"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/safe/' GET route", async () => { + const response = await request(system).get("/api/strategy/safe/"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/reorder' POST route", async () => { + const response = await request(system) + .post("/api/strategy/reorder") + .send({ strategies: ["local"] }) + .set("Content-Type", "application/json"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/:type' GET route", async () => { + const response = await request(system).get("/api/strategy/local"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/safe/:type' GET route", async () => { + const response = await request(system).get("/api/strategy/safe/local"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/:type/enable' GET route", async () => { + const response = await request(system).get("/api/strategy/local/enable"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/:type/disable' GET route", async () => { + const response = await request(system).get("/api/strategy/local/disable"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/:type' PUT route", async () => { + const response = await request(system) + .put("/api/strategy/local") + .send({ enabled: true }) + .set("Content-Type", "application/json"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); +}); diff --git a/src/server/routes/user.test.js b/src/server/routes/user.test.js new file mode 100644 index 000000000..767af8527 --- /dev/null +++ b/src/server/routes/user.test.js @@ -0,0 +1,95 @@ +const request = require("supertest"); + +jest.mock("@middleware/restrict", () => ({ + to: () => (req, res, next) => next(), +})); + +jest.mock("@services/user-delete", () => jest.fn(async () => true)); +jest.mock("@services/user-set", () => jest.fn(async () => true)); +jest.mock("@services/user-update", () => jest.fn(async () => true)); +jest.mock("@services/user-list", () => jest.fn(async () => [{ id: "user-1", username: "tester" }])); +jest.mock("@services/user-get", () => jest.fn(async () => ({ id: "user-1", username: "tester" }))); +jest.mock("@services/user-enable", () => jest.fn(async () => true)); +jest.mock("@services/user-getproxyid", () => jest.fn(async () => ({ username: "tester" }))); + +const system = require("@bin/api"); + +describe("Test the '/api/user/' endpoint", () => { + test("Test the '/current' GET route", async () => { + const response = await request(system).get("/api/user/current"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/getproxyid' GET route", async () => { + const response = await request(system).get("/api/user/getproxyid"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/' GET route", async () => { + const response = await request(system).get("/api/user/"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/list/' POST route", async () => { + const response = await request(system) + .post("/api/user/list/") + .send({ sortField: "username", sortDirection: "asc", filters: {} }) + .set("Content-Type", "application/json"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/:id' GET route", async () => { + const response = await request(system).get("/api/user/user-1"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/:id/enable' GET route", async () => { + const response = await request(system).get("/api/user/user-1/enable"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/:id/disable' GET route", async () => { + const response = await request(system).get("/api/user/user-1/disable"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/' POST route", async () => { + const response = await request(system) + .post("/api/user/") + .send({ username: "newuser" }) + .set("Content-Type", "application/json"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/current' PUT route", async () => { + const response = await request(system) + .put("/api/user/current") + .send({ firstName: "Test" }) + .set("Content-Type", "application/json"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/:id' PUT route", async () => { + const response = await request(system) + .put("/api/user/user-1") + .send({ firstName: "Updated" }) + .set("Content-Type", "application/json"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); + + test("Test the '/:id' DELETE route", async () => { + const response = await request(system).delete("/api/user/user-1"); + expect(response.statusCode).toBe(200); + expect(response.body.status).toBe("success"); + }); +});