diff --git a/docker-compose.testing.yml b/docker-compose.testing.yml index b0c2cca..c1d2f00 100644 --- a/docker-compose.testing.yml +++ b/docker-compose.testing.yml @@ -1,14 +1,17 @@ version: '3' services: cypress: - image: cypress/included + build: + context: . + dockerfile: tests/Dockerfile networks: - internal volumes: - - ./tests:/e2e - working_dir: /e2e + - ./tests:/e2e/tests environment: - BASE_URL=http://app-test + - TESTING_USERNAME=alex + - TESTING_PASSWORD=Alex1234 depends_on: - app-test @@ -49,6 +52,9 @@ services: - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} - SURVEY_URL=${SURVEY_URL} - BUG_REPORT_URL=${BUG_REPORT_URL} + - TESTING=true + - TESTING_USERNAME=alex + - TESTING_PASSWORD=Alex1234 db-test: image: mongo diff --git a/docker-compose.yml b/docker-compose.yml index b2b3e9f..ac3b400 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -45,6 +45,9 @@ services: - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} + - TESTING=true + - TESTING_USERNAME=alex + - TESTING_PASSWORD=Alex1234 analytics: build: diff --git a/package.json b/package.json index e7724c5..b117891 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "format": "prettier --ignore-path .prettierignore --write app server", "test": "yarn node --experimental-vm-modules $(yarn bin jest)", "test:e2e": "yarn workspace tests test", + "test:open": "yarn workspace tests test:open", "analytics": "yarn workspace analytics dev", "setup": "yarn workspace server setup", "clean": "yarn workspace server clean" diff --git a/server/src/api/controllers/authController.js b/server/src/api/controllers/authController.js index 6ac6a1f..8ae7978 100644 --- a/server/src/api/controllers/authController.js +++ b/server/src/api/controllers/authController.js @@ -1,6 +1,7 @@ import { matchedData } from 'express-validator' import { User } from '@linux-odyssey/models' -import { genJWT, hashPassword } from '../../utils/auth.js' +import { genJWT } from '../../utils/auth.js' +import { createUser } from '../../models/userManager.js' import { asyncHandler } from '../../middleware/error.js' export const issueToken = asyncHandler(async (req, res) => { @@ -16,12 +17,7 @@ export const issueToken = asyncHandler(async (req, res) => { export const register = asyncHandler(async (req, res, next) => { const { username, password, email } = matchedData(req) - const user = new User({ - username, - email, - }) - user.hashedPassword = await hashPassword(password) - await user.save() + const user = await createUser(username, email, password) req.login(user, (err) => { if (err) { next(err) diff --git a/server/src/api/controllers/commandController.js b/server/src/api/controllers/commandController.js index fa8de98..c956117 100644 --- a/server/src/api/controllers/commandController.js +++ b/server/src/api/controllers/commandController.js @@ -2,7 +2,7 @@ import { Command, Session } from '@linux-odyssey/models' import { matchedData } from 'express-validator' import CommandHandler from '../../game/commandHandler.js' import { pushToSession } from '../socket.js' -import { finishSession } from '../../game/sessionManager.js' +import { finishSession } from '../../models/sessionManager.js' import { asyncHandler } from '../../middleware/error.js' const commandCompleteCallbacks = new Map() diff --git a/server/src/api/controllers/sessionController.js b/server/src/api/controllers/sessionController.js index 5ae0d53..05188fb 100644 --- a/server/src/api/controllers/sessionController.js +++ b/server/src/api/controllers/sessionController.js @@ -4,7 +4,7 @@ import { createNewSession, getOrCreateActiveSession, isQuestUnlocked, -} from '../../game/sessionManager.js' +} from '../../models/sessionManager.js' import { asyncHandler } from '../../middleware/error.js' function sessionSummary(session) { diff --git a/server/src/config.js b/server/src/config.js index 75f43d8..e9b5da0 100644 --- a/server/src/config.js +++ b/server/src/config.js @@ -40,6 +40,11 @@ const config = { trustedProxies: getTrustProxies('TRUSTED_PROXIES'), surveyUrl: getUrl('SURVEY_URL', ''), bugReportUrl: getUrl('BUG_REPORT_URL', ''), + testing: { + enabled: process.env.TESTING === 'true', + username: get('TESTING_USERNAME', ''), + password: get('TESTING_PASSWORD', ''), + }, } config.baseUrl = get('BASE_URL', `http://${config.host}:${config.port}`) diff --git a/server/src/index.js b/server/src/index.js index 52c2d88..e22f9cf 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -15,6 +15,7 @@ import errorHandler from './middleware/error.js' import { globalRateLimit } from './middleware/rateLimiter.js' import sessionMiddleware from './middleware/session.js' import expiryRemovalScheduler from './containers/expiryChecker.js' +import setupTest from './utils/test.js' async function main() { if (!config.secret) { @@ -33,6 +34,10 @@ async function main() { process.exit(1) } + if (config.testing.enabled) { + await setupTest() + } + await loadAndUpdateQuests() expiryRemovalScheduler() diff --git a/server/src/game/sessionManager.js b/server/src/models/sessionManager.js similarity index 98% rename from server/src/game/sessionManager.js rename to server/src/models/sessionManager.js index 19e79c8..b846dfb 100644 --- a/server/src/game/sessionManager.js +++ b/server/src/models/sessionManager.js @@ -1,6 +1,6 @@ import { Session, Quest, UserProfile } from '@linux-odyssey/models' import { createContainer, deleteContainer } from '../containers/docker.js' -import SessionHandler from './sessionHandler.js' +import SessionHandler from '../game/sessionHandler.js' async function deactivateSessions(user, quest) { const sessions = await Session.find({ diff --git a/server/src/models/userManager.js b/server/src/models/userManager.js new file mode 100644 index 0000000..aa877fb --- /dev/null +++ b/server/src/models/userManager.js @@ -0,0 +1,16 @@ +import { User, UserProfile } from '@linux-odyssey/models' +import { hashPassword } from '../utils/auth.js' + +export async function createUser(username, email, password) { + const user = new User({ + username, + email, + }) + user.hashedPassword = await hashPassword(password) + await user.save() + + const userProfile = new UserProfile({ + user, + }) + await userProfile.save() +} diff --git a/server/src/utils/test.js b/server/src/utils/test.js new file mode 100644 index 0000000..2c97278 --- /dev/null +++ b/server/src/utils/test.js @@ -0,0 +1,22 @@ +import { User } from '@linux-odyssey/models' +import config from '../config.js' +import { createUser } from '../models/userManager.js' + +export default async function setupTest() { + console.log('Setting up test account') + if (!(config.testing.username && config.testing.password)) { + throw new Error( + 'No testing account found! Please set TESTING_USERNAME and TESTING_PASSWORD in .env' + ) + } + const { username, password } = config.testing + const email = `${username}@example.com` + + if (await User.exists({ username })) { + console.log('Testing account already exists') + return + } + + await createUser(username, email, password) + console.log('Created testing account') +} diff --git a/tests/Dockerfile b/tests/Dockerfile new file mode 100644 index 0000000..da38a2c --- /dev/null +++ b/tests/Dockerfile @@ -0,0 +1,9 @@ +FROM cypress/included:13.5.1 + +WORKDIR /e2e +COPY package.json yarn.lock ./ +COPY tests/package.json ./tests/package.json +RUN yarn install --frozen-lockfile + +COPY tests ./tests +WORKDIR /e2e/tests \ No newline at end of file diff --git a/tests/cypress.config.js b/tests/cypress.config.js index 2642790..9057bb2 100644 --- a/tests/cypress.config.js +++ b/tests/cypress.config.js @@ -1,11 +1,19 @@ const { defineConfig } = require('cypress') const process = require('process') -require('dotenv').config() +require('dotenv').config({ path: '../.env' }) const getConfig = (key, defaultValue) => { return process.env[key] || defaultValue } +const getOrFail = (key) => { + const value = getConfig(key) + if (!value) { + throw new Error(`Missing required environment variable ${key}`) + } + return value +} + module.exports = defineConfig({ e2e: { baseUrl: getConfig('BASE_URL', 'http://localhost:5173'), @@ -14,9 +22,8 @@ module.exports = defineConfig({ pageLoadTimeout: 120000, }, env: { - ...process.env, - defaultAccount: process.env.default_USERNAME, - defaultPassword: process.env.default_PASSWORD, + defaultAccount: getOrFail('TESTING_USERNAME'), + defaultPassword: getOrFail('TESTING_PASSWORD'), // google_username: process.env.GOOGLE_USERNAME, // google_password: process.env.GOOGLE_PASSWORD, // google_client_id: process.env.GOOGLE_CLIENT_ID, diff --git a/tests/package.json b/tests/package.json index 2f71755..be6c38b 100644 --- a/tests/package.json +++ b/tests/package.json @@ -5,15 +5,12 @@ "main": "index.js", "license": "MIT", "scripts": { - "test": "cypress run" + "test": "cypress run", + "test:open": "cypress open" }, "dependencies": { - "cypress": "^13.2.0", - "dotenv": "^16.3.1", - "save": "^2.9.0" - }, - "devDependencies": { + "cypress": "^13.5.0", "@testing-library/cypress": "^10.0.1", - "cypress": "^13.5.0" + "dotenv": "^16.3.1" } } diff --git a/yarn.lock b/yarn.lock index d25b117..19a6782 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1467,11 +1467,6 @@ async@^3.2.0: resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== -async@^3.2.2: - version "3.2.5" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" - integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2345,11 +2340,6 @@ dotenv@^16.3.1: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== -duplexer@^0.1.1, duplexer@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" - integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== - eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -2786,19 +2776,6 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== -event-stream@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-4.0.1.tgz#4092808ec995d0dd75ea4580c1df6a74db2cde65" - integrity sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA== - dependencies: - duplexer "^0.1.1" - from "^0.1.7" - map-stream "0.0.7" - pause-stream "^0.0.11" - split "^1.0.1" - stream-combiner "^0.2.2" - through "^2.3.8" - eventemitter2@6.4.7: version "6.4.7" resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" @@ -3145,11 +3122,6 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== -from@^0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" - integrity sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g== - fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -4470,11 +4442,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.assign@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" - integrity sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw== - lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -4593,11 +4560,6 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -map-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8" - integrity sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -4663,11 +4625,6 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== -mingo@^6.1.0: - version "6.4.9" - resolved "https://registry.yarnpkg.com/mingo/-/mingo-6.4.9.tgz#715ad1487687259db04623b64a7ba63b564b5ddd" - integrity sha512-jsOiIEJ19UtTaWjNFN4CnuKWqELm2sINZcHqI/nmjezMvXthBIQOftJrlT85NuIDoSFc0vrbjD+GnrzEgYnPbA== - minimalistic-assert@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -5229,13 +5186,6 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== -pause-stream@^0.0.11: - version "0.0.11" - resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" - integrity sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A== - dependencies: - through "~2.3" - pause@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" @@ -5657,16 +5607,6 @@ saslprep@^1.0.3: dependencies: sparse-bitfield "^3.0.3" -save@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/save/-/save-2.9.0.tgz#6659375fadeaf58e4abc6a90ec6b0fb2e2232e0f" - integrity sha512-eg8+g8CjvehE/2C6EbLdtK1pINVD27pcJLj4M9PjWWhoeha/y5bWf4dp/0RF+OzbKTcG1bae9qi3PAqiR8CJTg== - dependencies: - async "^3.2.2" - event-stream "^4.0.1" - lodash.assign "^4.2.0" - mingo "^6.1.0" - semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" @@ -5894,13 +5834,6 @@ split-ca@^1.0.1: resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" integrity sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ== -split@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" - integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== - dependencies: - through "2" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -5951,14 +5884,6 @@ stop-iteration-iterator@^1.0.0: dependencies: internal-slot "^1.0.4" -stream-combiner@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.2.2.tgz#aec8cbac177b56b6f4fa479ced8c1912cee52858" - integrity sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ== - dependencies: - duplexer "~0.1.1" - through "~2.3.4" - string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -6211,7 +6136,7 @@ throttleit@^1.0.0: resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" integrity sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g== -through@2, through@^2.3.8, through@~2.3, through@~2.3.4: +through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==