diff --git a/server-ce/.dockerignore b/server-ce/.dockerignore index 950b2edb84..5f4a265532 100644 --- a/server-ce/.dockerignore +++ b/server-ce/.dockerignore @@ -1,3 +1,4 @@ .DS_Store .git/ services/git-bridge +server-ce/test diff --git a/server-ce/Dockerfile b/server-ce/Dockerfile index aafc3c0903..36e00ae79d 100644 --- a/server-ce/Dockerfile +++ b/server-ce/Dockerfile @@ -15,11 +15,6 @@ ADD package.json package-lock.json /overleaf/ ADD libraries/ /overleaf/libraries/ ADD services/ /overleaf/services/ -# Store the revision -# ------------------ -ARG MONOREPO_REVISION -RUN echo "monorepo-server-ce,$MONOREPO_REVISION" > /var/www/revisions.txt - # Add npm patches # ----------------------- ADD patches/ /overleaf/patches @@ -115,3 +110,9 @@ ENV LOG_LEVEL "info" EXPOSE 80 ENTRYPOINT ["/sbin/my_init"] + +# Store the revision +# ------------------ +# This should be the last step to optimize docker image caching. +ARG MONOREPO_REVISION +RUN echo "monorepo-server-ce,$MONOREPO_REVISION" > /var/www/revisions.txt diff --git a/server-ce/test/Dockerfile b/server-ce/test/Dockerfile new file mode 100644 index 0000000000..3d00ca401f --- /dev/null +++ b/server-ce/test/Dockerfile @@ -0,0 +1,8 @@ +FROM node:18.20.2 +RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - \ +&& echo \ + "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ + > /etc/apt/sources.list.d/docker.list \ +&& apt-get update \ +&& apt-get install -y docker-ce-cli docker-compose-plugin \ +&& rm -rf /var/lib/apt/lists/* diff --git a/server-ce/test/Makefile b/server-ce/test/Makefile index c0e5f9fbb9..19dd67fb1c 100644 --- a/server-ce/test/Makefile +++ b/server-ce/test/Makefile @@ -1,13 +1,22 @@ all: test-e2e +# We are updating the docker compose config via the host-admin service. +# The host-admin service is running inside docker and has its own file-system layout. +# We need to have both file-system layouts agree on the path for the docker compose project. +# Notable the container labels com.docker.compose.project.working_dir and com.docker.compose.project.config_files need to match when creating containers from the docker host (how you started things) and from host-admin (how tests reconfigure the instance). +export PWD = $(shell pwd) + test-e2e: - docker compose down -v -t 0 - docker compose -f docker-compose.yml run --rm e2e - docker compose down -v -t 0 + docker compose up --build --no-log-prefix --exit-code-from=e2e e2e test-e2e-open: - docker compose down -v -t 0 - docker compose -f docker-compose.yml run --rm e2e-open - docker compose down -v -t 0 + docker compose up --build --no-log-prefix --exit-code-from=e2e-open e2e-open + +clean: + docker compose down --volumes --timeout 0 + +prefetch: + docker compose pull e2e mongo redis + docker compose build .PHONY: test-e2e test-e2e-open diff --git a/server-ce/test/accounts.spec.ts b/server-ce/test/accounts.spec.ts index 7638e0cfcb..adc12170ed 100644 --- a/server-ce/test/accounts.spec.ts +++ b/server-ce/test/accounts.spec.ts @@ -1,6 +1,10 @@ -import { login } from './helpers/login' +import { ensureUserExists, login } from './helpers/login' +import { startWith } from './helpers/config' describe('Accounts', function () { + startWith({}) + ensureUserExists({ email: 'user@example.com' }) + it('can log in and out', function () { login('user@example.com') cy.visit('/project') diff --git a/server-ce/test/create-and-compile-project.spec.ts b/server-ce/test/create-and-compile-project.spec.ts index 0070b0fbca..8ac7f2f620 100644 --- a/server-ce/test/create-and-compile-project.spec.ts +++ b/server-ce/test/create-and-compile-project.spec.ts @@ -1,12 +1,17 @@ -import { login } from './helpers/login' +import { ensureUserExists, login } from './helpers/login' import { createProject } from './helpers/project' +import { startWith } from './helpers/config' describe('Project creation and compilation', function () { + startWith({}) + ensureUserExists({ email: 'user@example.com' }) + ensureUserExists({ email: 'collaborator@example.com' }) + it('users can create project and compile it', function () { login('user@example.com') cy.visit('/project') // this is the first project created, the welcome screen is displayed instead of the project list - createProject('test-project', { isFirstProject: true }) + createProject('test-project') cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/) cy.findByText('\\maketitle').parent().click() cy.findByText('\\maketitle').parent().type('\n\\section{{}Test Section}') diff --git a/server-ce/test/customization.spec.ts b/server-ce/test/customization.spec.ts new file mode 100644 index 0000000000..b9c566e827 --- /dev/null +++ b/server-ce/test/customization.spec.ts @@ -0,0 +1,25 @@ +import { startWith } from './helpers/config' + +describe('Customization', () => { + startWith({ + vars: { + OVERLEAF_APP_NAME: 'CUSTOM APP NAME', + OVERLEAF_LEFT_FOOTER: JSON.stringify([{ text: 'CUSTOM LEFT FOOTER' }]), + OVERLEAF_RIGHT_FOOTER: JSON.stringify([{ text: 'CUSTOM RIGHT FOOTER' }]), + }, + }) + + it('should display custom name', () => { + cy.visit('/') + cy.get('nav').findByText('CUSTOM APP NAME') + }) + + it('should display custom left footer', () => { + cy.visit('/') + cy.get('footer').findByText('CUSTOM LEFT FOOTER') + }) + it('should display custom right footer', () => { + cy.visit('/') + cy.get('footer').findByText('CUSTOM RIGHT FOOTER') + }) +}) diff --git a/server-ce/test/cypress.config.js b/server-ce/test/cypress.config.js index 7f9259a0c4..050447f4ca 100644 --- a/server-ce/test/cypress.config.js +++ b/server-ce/test/cypress.config.js @@ -3,8 +3,9 @@ const { defineConfig } = require('cypress') const specPattern = process.env.SPEC_PATTERN || './**/*.spec.{js,ts,tsx}' module.exports = defineConfig({ + defaultCommandTimeout: 10_000, fixturesFolder: 'cypress/fixtures', - video: !!process.env.CI, + video: process.env.CYPRESS_VIDEO === 'true', screenshotsFolder: 'cypress/results', videosFolder: 'cypress/results', videoUploadOnPasses: false, diff --git a/server-ce/test/cypress/.gitignore b/server-ce/test/cypress/.gitignore index 68bcbc9609..9ab47dd2f2 100644 --- a/server-ce/test/cypress/.gitignore +++ b/server-ce/test/cypress/.gitignore @@ -1 +1,2 @@ -results/ \ No newline at end of file +downloads/ +results/ diff --git a/server-ce/test/docker-compose.yml b/server-ce/test/docker-compose.yml index a89991a48b..3a7b85a9f7 100644 --- a/server-ce/test/docker-compose.yml +++ b/server-ce/test/docker-compose.yml @@ -1,18 +1,18 @@ version: '2.2' services: sharelatex: - image: ${IMAGE_TAG:-sharelatex/sharelatex:latest} - container_name: sharelatex + image: ${IMAGE_TAG_CE:-sharelatex/sharelatex:latest} + # TODO(das7pad): increase timeout after speeding up the graceful shutdown procedure + # stop_grace_period: 60s + stop_grace_period: 0s depends_on: mongo: condition: service_healthy redis: condition: service_started - ports: - - 127.0.0.2:80:80 - links: - - mongo - - redis + host-admin: + # The host-admin service initiates the mongo replica set + condition: service_healthy environment: OVERLEAF_APP_NAME: Overleaf Community Edition OVERLEAF_MONGO_URL: mongodb://mongo/sharelatex?directConnection=true @@ -23,36 +23,27 @@ services: EMAIL_CONFIRMATION_DISABLED: 'true' healthcheck: test: curl --fail http://localhost:3000/status || exit 1 - interval: 10s + interval: 3s timeout: 10s retries: 10 - volumes: - - ./util/seed-mongo.sh:/etc/my_init.d/99_seed-mongo.sh - - ./util/seed-mongo.js:/overleaf/services/web/modules/server-ce-scripts/scripts/seed-mongo.js mongo: image: mongo:5.0.17 - container_name: mongo command: '--replSet overleaf' - expose: - - 27017 healthcheck: - # FIXME: silly hack to make sure replicaset is initialized - test: 'echo ''rs.initiate({ _id: "overleaf", members: [{ _id: 0, host: "mongo:27017" }] })'' | mongo localhost:27017/test --quiet' + test: echo 'db.stats().ok' | mongo localhost:27017/test --quiet interval: 10s timeout: 10s retries: 5 redis: image: redis:7.2.1 - container_name: redis - expose: - - 6379 e2e: image: cypress/included:13.6.6 - links: - - sharelatex + stop_grace_period: 0s + entrypoint: npm + command: run cypress:run working_dir: /e2e volumes: - ./:/e2e @@ -62,13 +53,14 @@ services: depends_on: sharelatex: condition: service_healthy + host-admin: + condition: service_healthy e2e-open: image: cypress/included:13.6.6 + stop_grace_period: 0s entrypoint: npm command: run cypress:open - links: - - sharelatex working_dir: /e2e volumes: - ./:/e2e @@ -81,3 +73,27 @@ services: depends_on: sharelatex: condition: service_healthy + host-admin: + condition: service_healthy + + host-admin: + build: . + entrypoint: ["node", "--watch", "host-admin.js"] + # See comment in Makefile regarding matching file paths + working_dir: $PWD + volumes: + - $PWD:$PWD + - /var/run/docker.sock:/var/run/docker.sock + stop_grace_period: 0s + environment: + PWD: + IMAGE_TAG_CE: ${IMAGE_TAG_CE:-sharelatex/sharelatex:latest} + IMAGE_TAG_PRO: ${IMAGE_TAG_PRO:-quay.io/sharelatex/sharelatex-pro:latest} + depends_on: + mongo: + condition: service_healthy + healthcheck: + test: curl --fail http://localhost/status || exit 1 + interval: 3s + timeout: 10s + retries: 10 diff --git a/server-ce/test/helpers/config.ts b/server-ce/test/helpers/config.ts new file mode 100644 index 0000000000..b9aeb874f5 --- /dev/null +++ b/server-ce/test/helpers/config.ts @@ -0,0 +1,16 @@ +import { reconfigure } from './hostAdminClient' + +let lastConfig: string + +export function startWith({ pro = false, version = 'latest', vars = {} }) { + before(async function () { + const cfg = JSON.stringify({ pro, version, vars }) + if (lastConfig === cfg) return + + this.timeout(100 * 1000) + await reconfigure({ pro, version, vars }) + lastConfig = cfg + }) +} + +export { reconfigure } diff --git a/server-ce/test/helpers/hostAdminClient.ts b/server-ce/test/helpers/hostAdminClient.ts new file mode 100644 index 0000000000..dec68f4f4c --- /dev/null +++ b/server-ce/test/helpers/hostAdminClient.ts @@ -0,0 +1,84 @@ +export async function setVars(vars = {}) { + return await fetchJSON('http://host-admin/set/vars', { + method: 'POST', + body: JSON.stringify({ vars, path: 'docker-compose.yml' }), + }) +} + +export async function setVersion({ pro = false, version = 'latest' }) { + return await fetchJSON('http://host-admin/set/version', { + method: 'POST', + body: JSON.stringify({ + pro, + version, + path: 'docker-compose.yml', + }), + }) +} + +export async function dockerCompose(cmd: string, ...args: string[]) { + return await fetchJSON(`http://host-admin/docker/compose/${cmd}`, { + method: 'POST', + body: JSON.stringify({ + args, + }), + }) +} + +export async function mongoInit() { + return await fetchJSON('http://host-admin/mongo/init', { + method: 'POST', + }) +} + +export async function reconfigure({ + pro = false, + version = 'latest', + vars = {}, +}) { + return await fetchJSON('http://host-admin/reconfigure', { + method: 'POST', + body: JSON.stringify({ + pro, + version, + vars, + }), + }) +} + +async function fetchJSON( + input: RequestInfo, + init?: RequestInit +): Promise<{ stdout: string; stderr: string }> { + if (init?.body) { + init.headers = { 'Content-Type': 'application/json' } + } + const res = await fetch(input, init) + const { error, stdout, stderr } = await res.json() + if (error) { + console.error(input, init, 'failed:', error) + const err = new Error(error.message) + Object.assign(err, error) + throw err + } + return { stdout, stderr } +} + +export async function runScript({ + cwd, + script, + args = [], +}: { + cwd: string + script: string + args?: string[] +}) { + return await fetchJSON('http://host-admin/run/script', { + method: 'POST', + body: JSON.stringify({ + cwd, + script, + args, + }), + }) +} diff --git a/server-ce/test/helpers/login.ts b/server-ce/test/helpers/login.ts index 9047916cad..35911840c1 100644 --- a/server-ce/test/helpers/login.ts +++ b/server-ce/test/helpers/login.ts @@ -1,4 +1,57 @@ -export function login(username: string, password = 'Passw0rd!') { +import { runScript } from './hostAdminClient' + +const DEFAULT_PASSWORD = 'Passw0rd!' + +const createdUsers = new Set() + +async function createMongoUser({ + email, + isAdmin = false, +}: { + email: string + isAdmin?: boolean +}) { + const t0 = Date.now() + const { stdout } = await runScript({ + cwd: 'services/web', + script: 'modules/server-ce-scripts/scripts/create-user.js', + args: [`--email=${email}`, `--admin=${isAdmin}`], + }) + const [url] = stdout.match(/\/user\/activate\?token=\S+/)! + const userId = new URL(url, location.origin).searchParams.get('user_id')! + const signupDate = parseInt(userId.slice(0, 8), 16) * 1000 + if (signupDate < t0) { + return { url, exists: true } + } + return { url, exists: false } +} + +export function ensureUserExists({ + email, + password = DEFAULT_PASSWORD, + isAdmin = false, +}: { + email: string + password?: string + isAdmin?: boolean +}) { + let url: string + let exists: boolean + before(async function () { + exists = createdUsers.has(email) + if (exists) return + ;({ url, exists } = await createMongoUser({ email, isAdmin })) + }) + before(function () { + if (exists) return + activateUser(url, password) + cy.then(() => { + createdUsers.add(email) + }) + }) +} + +export function login(username: string, password = DEFAULT_PASSWORD) { cy.session([username, password, new Date()], () => { cy.visit('/login') cy.get('input[name="email"]').type(username) @@ -7,3 +60,16 @@ export function login(username: string, password = 'Passw0rd!') { cy.url().should('contain', '/project') }) } + +export function activateUser(url: string, password = DEFAULT_PASSWORD) { + cy.session(url, () => { + cy.visit(url) + cy.url().then(url => { + if (url.includes('/login')) return + cy.url().should('contain', '/user/activate') + cy.get('input[name="password"]').type(password) + cy.findByRole('button', { name: 'Activate' }).click() + cy.url().should('contain', '/project') + }) + }) +} diff --git a/server-ce/test/helpers/project.ts b/server-ce/test/helpers/project.ts index 47d51bc4e5..ffed8d17ce 100644 --- a/server-ce/test/helpers/project.ts +++ b/server-ce/test/helpers/project.ts @@ -2,18 +2,13 @@ export function createProject( name: string, { type = 'Blank Project', - isFirstProject, }: { type?: 'Blank Project' | 'Example Project' - isFirstProject?: boolean } = {} ): Cypress.Chainable { - if (isFirstProject) { - cy.findByText('Create a new project').click() - } else { - // FIXME: This should be be a data-test-id shared between the welcome page and project list - cy.get('.new-project-button').first().click() - } + cy.findAllByRole('button') + .contains(/new project/i) + .click() // FIXME: This should only look in the left menu cy.findAllByText(type).first().click() cy.findByRole('dialog').within(() => { diff --git a/server-ce/test/host-admin.js b/server-ce/test/host-admin.js new file mode 100644 index 0000000000..54b6027c7c --- /dev/null +++ b/server-ce/test/host-admin.js @@ -0,0 +1,279 @@ +const fs = require('fs') +const { execFile } = require('child_process') +const express = require('express') +const bodyParser = require('body-parser') +const { + celebrate: validate, + Joi, + errors: handleValidationErrors, +} = require('celebrate') +const YAML = require('js-yaml') + +const FILES = { + DOCKER_COMPOSE: 'docker-compose.override.yml', +} +const IMAGES = { + CE: process.env.IMAGE_TAG_CE.replace(/:.+/, ''), + PRO: process.env.IMAGE_TAG_PRO.replace(/:.+/, ''), +} + +let mongoIsInitialized = false + +function readDockerComposeOverride() { + try { + return YAML.load(fs.readFileSync(FILES.DOCKER_COMPOSE, 'utf-8')) + } catch (error) { + if (error.code !== 'ENOENT') { + throw error + } + return { + services: { + sharelatex: { + environment: {}, + }, + }, + } + } +} + +function writeDockerComposeOverride(cfg) { + fs.writeFileSync(FILES.DOCKER_COMPOSE, YAML.dump(cfg)) +} + +const app = express() +app.get('/status', (req, res) => { + res.send('host-admin is up') +}) + +app.use(bodyParser.json()) +app.use((req, res, next) => { + // Basic access logs + console.log(req.method, req.url, req.body) + // Add CORS headers + res.setHeader('Access-Control-Allow-Origin', 'http://sharelatex') + res.setHeader('Access-Control-Allow-Headers', 'Content-Type') + next() +}) + +app.post( + '/run/script', + validate( + { + body: { + cwd: Joi.string().required(), + script: Joi.string().required(), + args: Joi.array().items(Joi.string()), + }, + }, + { allowUnknown: false } + ), + (req, res) => { + const { cwd, script, args } = req.body + + execFile( + 'docker', + [ + 'compose', + 'exec', + 'sharelatex', + 'bash', + '-c', + `source /etc/container_environment.sh && source /etc/overleaf/env.sh && cd ${JSON.stringify(cwd)} && node ${JSON.stringify(script)} ${args.map(a => JSON.stringify(a)).join(' ')}`, + ], + (error, stdout, stderr) => { + res.json({ + error, + stdout, + stderr, + }) + } + ) + } +) + +function setVersionDockerCompose({ pro, version }) { + const cfg = readDockerComposeOverride() + + cfg.services.sharelatex.image = `${pro ? IMAGES.PRO : IMAGES.CE}:${version}` + + writeDockerComposeOverride(cfg) +} + +app.post( + '/set/version', + validate( + { + body: { + pro: Joi.boolean(), + version: Joi.string().required(), + path: Joi.allow( + 'docker-compose.yml' + // When extending testing for Toolkit: + // 'config/version' + ), + }, + }, + { allowUnknown: false } + ), + (req, res) => { + const { pro, version } = req.body + if (req.body.path === 'docker-compose.yml') { + try { + setVersionDockerCompose({ pro, version }) + } catch (error) { + return res.json({ error }) + } + } + res.json({}) + } +) + +const allowedVars = Joi.object().keys({ + OVERLEAF_APP_NAME: Joi.string(), + OVERLEAF_LEFT_FOOTER: Joi.string(), + OVERLEAF_RIGHT_FOOTER: Joi.string(), +}) + +function setVarsDockerCompose({ vars }) { + const cfg = readDockerComposeOverride() + + cfg.services.sharelatex.environment = vars + + writeDockerComposeOverride(cfg) +} + +app.post( + '/set/vars', + validate( + { + body: { + vars: allowedVars, + path: Joi.allow( + 'docker-compose.yml' + // When extending the testing for Toolkit: + // 'overleaf.rc', 'variables.env' + ), + }, + }, + { allowUnknown: false } + ), + (req, res) => { + if (req.body.path === 'docker-compose.yml') { + const { vars } = req.body + try { + setVarsDockerCompose({ vars }) + } catch (error) { + return res.json({ error }) + } + } + res.json({}) + } +) + +app.post( + '/docker/compose/:cmd', + validate( + { + body: { + args: Joi.array().allow( + '--detach', + '--wait', + '--volumes', + '--timeout', + '0', + 'sharelatex', + 'mongo', + 'redis' + ), + }, + params: { + cmd: Joi.allow('up', 'stop', 'down', 'ps', 'logs'), + }, + }, + { allowUnknown: false } + ), + (req, res) => { + const { cmd } = req.params + const { args } = req.body + if (['stop', 'down'].includes(cmd)) { + mongoIsInitialized = false + } + execFile('docker', ['compose', cmd, ...args], (error, stdout, stderr) => { + res.json({ error, stdout, stderr }) + }) + } +) + +function mongoInit(callback) { + execFile( + 'docker', + [ + 'compose', + 'exec', + 'mongo', + 'mongo', + '--eval', + 'rs.initiate({ _id: "overleaf", members: [ { _id: 0, host: "mongo:27017" } ] })', + ], + (error, stdout, stderr) => { + if (!error) { + mongoIsInitialized = true + } + callback(error, stdout, stderr) + } + ) +} + +app.post('/mongo/init', (req, res) => { + mongoInit((error, stdout, stderr) => { + res.json({ error, stdout, stderr }) + }) +}) + +app.post( + '/reconfigure', + validate( + { + body: { + pro: Joi.boolean().required(), + version: Joi.string().required(), + vars: allowedVars, + }, + }, + { allowUnknown: false } + ), + (req, res) => { + const doMongoInit = mongoIsInitialized ? cb => cb() : mongoInit + doMongoInit((error, stdout, stderr) => { + if (error) return res.json({ error, stdout, stderr }) + + const { pro, version, vars } = req.body + try { + setVersionDockerCompose({ pro, version }) + setVarsDockerCompose({ vars }) + } catch (error) { + return res.json({ error }) + } + + execFile( + 'docker', + ['compose', 'up', '--detach', '--wait', 'sharelatex'], + (error, stdout, stderr) => { + res.json({ error, stdout, stderr }) + } + ) + }) + } +) + +app.use(handleValidationErrors()) + +// Init on startup +mongoInit(err => { + if (err) { + console.error('mongo init failed', err) + process.exit(1) + } + + app.listen(80) +}) diff --git a/server-ce/test/package-lock.json b/server-ce/test/package-lock.json index a2f1553bc3..5cef80cf2a 100644 --- a/server-ce/test/package-lock.json +++ b/server-ce/test/package-lock.json @@ -7,7 +7,11 @@ "name": "@overleaf/server-ce/test", "dependencies": { "@testing-library/cypress": "^10.0.1", + "body-parser": "^1.20.2", + "celebrate": "^15.0.3", "cypress": "13.6.6", + "express": "^4.19.2", + "js-yaml": "^4.1.0", "typescript": "^5.0.4" } }, @@ -221,6 +225,37 @@ "ms": "^2.1.1" } }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "node_modules/@testing-library/cypress": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/@testing-library/cypress/-/cypress-10.0.1.tgz", @@ -288,6 +323,18 @@ "@types/node": "*" } }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -363,6 +410,11 @@ } ] }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/aria-query": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", @@ -383,6 +435,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "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", @@ -491,6 +548,56 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -531,6 +638,14 @@ "node": "*" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cachedir": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", @@ -557,6 +672,16 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, + "node_modules/celebrate": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/celebrate/-/celebrate-15.0.3.tgz", + "integrity": "sha512-ToF8ILq/F0KhQ0CPtexP7Cu9GkqKJ91VKy3ZOCV24aaNWdm3QCHqnXAKfKHrtcM2B2zmPFe11p8WWsQkmq8k4g==", + "dependencies": { + "escape-html": "1.0.3", + "joi": "17.x.x", + "lodash": "4.17.x" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -695,6 +820,38 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -880,6 +1037,23 @@ "node": ">=0.4.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/dom-accessibility-api": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", @@ -894,11 +1068,24 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -938,6 +1125,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -946,6 +1138,14 @@ "node": ">=0.8.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/eventemitter2": { "version": "6.4.7", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", @@ -984,6 +1184,74 @@ "node": ">=4" } }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/express/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -1038,6 +1306,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -1067,6 +1365,22 @@ "node": ">= 0.12" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -1269,6 +1583,21 @@ "node": ">= 0.4" } }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/http-signature": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", @@ -1290,6 +1619,17 @@ "node": ">=8.12.0" } }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -1352,6 +1692,14 @@ "node": ">= 0.4" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -1633,11 +1981,34 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, + "node_modules/joi": { + "version": "17.13.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.1.tgz", + "integrity": "sha512-vaBlIKCyo4FCUtCm7Eu4QZd/q02bWcxfUO6YSXAZOWF6gzcLBeba8kwotUdYJjDLW8Cz8RywsSOqiNJZW0mNvg==", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -1802,11 +2173,43 @@ "lz-string": "bin/bin.js" } }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -1858,6 +2261,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -1917,6 +2328,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1958,6 +2380,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1974,6 +2404,11 @@ "node": ">=8" } }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -2035,6 +2470,18 @@ "node": ">= 0.6.0" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/proxy-from-env": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", @@ -2081,6 +2528,28 @@ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -2197,6 +2666,61 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/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==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/set-function-length": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", @@ -2224,6 +2748,11 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2298,6 +2827,14 @@ "node": ">=0.10.0" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -2373,6 +2910,14 @@ "node": ">=8.17.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tough-cookie": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", @@ -2427,6 +2972,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", @@ -2453,6 +3010,14 @@ "node": ">= 10.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/untildify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", @@ -2470,6 +3035,14 @@ "requires-port": "^1.0.0" } }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -2478,6 +3051,14 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", diff --git a/server-ce/test/package.json b/server-ce/test/package.json index 595f99fe40..53315c320c 100644 --- a/server-ce/test/package.json +++ b/server-ce/test/package.json @@ -10,7 +10,11 @@ }, "dependencies": { "@testing-library/cypress": "^10.0.1", + "body-parser": "^1.20.2", + "celebrate": "^15.0.3", "cypress": "13.6.6", + "express": "^4.19.2", + "js-yaml": "^4.1.0", "typescript": "^5.0.4" } } diff --git a/server-ce/test/util/seed-mongo.js b/server-ce/test/util/seed-mongo.js deleted file mode 100644 index fc503996c0..0000000000 --- a/server-ce/test/util/seed-mongo.js +++ /dev/null @@ -1,121 +0,0 @@ -const { ObjectId } = require('mongodb') -const { waitForDb, db } = require('../../../app/src/infrastructure/mongodb') - -waitForDb() - .then(async () => { - await seedUsers() - process.exit(0) - }) - .catch(err => { - console.error(err) - process.exit(1) - }) - -const DEFAULT_USER_PROPERTIES = { - staffAccess: { - publisherMetrics: false, - publisherManagement: false, - institutionMetrics: false, - institutionManagement: false, - groupMetrics: false, - groupManagement: false, - adminMetrics: false, - splitTestMetrics: false, - splitTestManagement: false, - }, - ace: { - mode: 'none', - theme: 'textmate', - overallTheme: '', - fontSize: 12, - autoComplete: true, - autoPairDelimiters: true, - spellCheckLanguage: 'en', - pdfViewer: 'pdfjs', - syntaxValidation: true, - }, - features: { - collaborators: -1, - versioning: true, - dropbox: true, - github: true, - gitBridge: true, - compileTimeout: 180, - compileGroup: 'standard', - templates: true, - references: true, - trackChanges: true, - }, - first_name: 'user', - role: '', - institution: '', - isAdmin: false, - lastLoginIp: '', - loginCount: 0, - holdingAccount: false, - must_reconfirm: false, - refered_users: [], - refered_user_count: 0, - alphaProgram: false, - betaProgram: false, - labsProgram: false, - awareOfV2: false, - samlIdentifiers: [], - thirdPartyIdentifiers: [], - - signUpDate: new Date('2023-11-02T11:36:40.151Z'), - featuresOverrides: [], - referal_id: 'scTS4kjjJENbfbjG', - __v: 0, - hashedPassword: - '$2a$12$nRvTj6U896uUnE.RFhnGKOyi/CvqBpfxezlqwyIPpezRa2xXLW7MO', -} - -async function seedUsers() { - const adminUser = { - ...DEFAULT_USER_PROPERTIES, - email: 'admin@example.com', - first_name: 'admin', - isAdmin: true, - emails: [ - { - email: 'admin@example.com', - reversedHostname: '', - _id: ObjectId('646ca54806d54400b74e77c6'), - createdAt: new Date('2023-05-23T11:36:40.494Z'), - }, - ], - } - - const user = { - ...DEFAULT_USER_PROPERTIES, - _id: ObjectId('6543cf90bbe1368944db04d7'), - emails: [ - { - email: 'user@example.com', - reversedHostname: '', - _id: ObjectId('6543c614d58b090b461f3549'), - createdAt: new Date('2023-11-21T11:36:40.494Z'), - }, - ], - email: 'user@example.com', - } - - const collaborator = { - ...DEFAULT_USER_PROPERTIES, - _id: ObjectId('6544e78c9b6e937424976b64'), - emails: [ - { - email: 'collaborator@example.com', - reversedHostname: '', - _id: ObjectId('6543c614d58b090b461f354a'), - createdAt: new Date('2023-11-21T11:36:40.494Z'), - }, - ], - email: 'collaborator@example.com', - } - - await db.users.insertOne(adminUser) - await db.users.insertOne(user) - await db.users.insertOne(collaborator) -} diff --git a/server-ce/test/util/seed-mongo.sh b/server-ce/test/util/seed-mongo.sh deleted file mode 100755 index dae68614e9..0000000000 --- a/server-ce/test/util/seed-mongo.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -set -e - -echo "Seeding mongo for e2e tests" -cd /overleaf/services/web -node modules/server-ce-scripts/scripts/seed-mongo -node modules/server-ce-scripts/scripts/check-redis -echo "mongo seeding complete" \ No newline at end of file