diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml index 7ab58c410..a9c8c602e 100644 --- a/.github/workflows/backend-tests.yml +++ b/.github/workflows/backend-tests.yml @@ -30,9 +30,8 @@ jobs: - name: Install all dependencies and symlink for ep_etherpad-lite run: src/bin/installDeps.sh - # configures some settings and runs npm run test - name: Run the backend tests - run: src/tests/frontend/travis/runnerBackend.sh + run: cd src && npm test withplugins: # run on pushes to any branch @@ -84,6 +83,5 @@ jobs: - name: Install all dependencies and symlink for ep_etherpad-lite run: src/bin/installDeps.sh - # configures some settings and runs npm run test - name: Run the backend tests - run: src/tests/frontend/travis/runnerBackend.sh + run: cd src && npm test diff --git a/.travis.yml b/.travis.yml index 8517e2a89..99aece0b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,7 +64,7 @@ jobs: - "src/bin/installDeps.sh" - "cd src && npm install && cd -" script: - - "src/tests/frontend/travis/runnerBackend.sh" + - "cd src && npm test" - name: "Test the Dockerfile" install: - "cd src && npm install && cd -" @@ -107,7 +107,7 @@ jobs: - *install_plugins - "cd src && npm install && cd -" script: - - "src/tests/frontend/travis/runnerBackend.sh" + - "cd src && npm test" - name: "Test the Dockerfile" install: - "cd src && npm install && cd -" diff --git a/CHANGELOG.md b/CHANGELOG.md index e19aba752..a17488f32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 1.8.9 + +### Notable fixes + +* Fixed HTTP 400 error when importing via the UI. +* Fixed "Error: spawn npm ENOENT" crash on startup in Windows. + +### Notable enhancements + +* Removed some unnecessary arrow key handling logic. +* Dependency updates. + # 1.8.8 ### Security patches diff --git a/Dockerfile b/Dockerfile index e16e58178..f0fa3ceb7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,22 @@ LABEL maintainer="Etherpad team, https://github.com/ether/etherpad-lite" # ETHERPAD_PLUGINS="ep_codepad ep_author_neat" ARG ETHERPAD_PLUGINS= +# Control whether abiword will be installed, enabling exports to DOC/PDF/ODT formats. +# By default, it is not installed. +# If given any value, abiword will be installed. +# +# EXAMPLE: +# INSTALL_ABIWORD=true +ARG INSTALL_ABIWORD= + +# Control whether libreoffice will be installed, enabling exports to DOC/PDF/ODT formats. +# By default, it is not installed. +# If given any value, libreoffice will be installed. +# +# EXAMPLE: +# INSTALL_LIBREOFFICE=true +ARG INSTALL_SOFFICE= + # By default, Etherpad container is built and run in "production" mode. This is # leaner (development dependencies are not installed) and runs faster (among # other things, assets are minified & compressed). @@ -28,6 +44,13 @@ RUN useradd --uid 5001 --create-home etherpad RUN mkdir /opt/etherpad-lite && chown etherpad:0 /opt/etherpad-lite +# install abiword for DOC/PDF/ODT export +RUN [ -z "${INSTALL_ABIWORD}" ] || (apt update && apt -y install abiword && apt clean && rm -rf /var/lib/apt/lists/*) + +# install libreoffice for DOC/PDF/ODT export +# the mkdir is needed for configuration of openjdk-11-jre-headless, see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=863199 +RUN [ -z "${INSTALL_SOFFICE}" ] || (apt update && mkdir -p /usr/share/man/man1 && apt -y install libreoffice && apt clean && rm -rf /var/lib/apt/lists/*) + USER etherpad WORKDIR /opt/etherpad-lite @@ -51,4 +74,4 @@ COPY --chown=etherpad:0 ./settings.json.docker /opt/etherpad-lite/settings.json RUN chmod -R g=u . EXPOSE 9001 -CMD ["node", "--experimental-worker", "node_modules/ep_etherpad-lite/node/server.js"] +CMD ["node", "--experimental-worker", "src/node/server.js"] diff --git a/README.md b/README.md index 3f1e6fb0f..d2dcfbe20 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Update to the latest version with `git pull origin`, then run If cloning to a subdirectory within another project, you may need to do the following: -1. Start the server manually (e.g. `node/node_modules/ep_etherpad-lite/node/server.js`) +1. Start the server manually (e.g. `node src/node/server.js`) 2. Edit the db `filename` in `settings.json` to the relative directory with the file (e.g. `application/lib/etherpad-lite/var/dirty.db`) 3. Add auto-generated files to the main project `.gitignore` diff --git a/doc/docker.md b/doc/docker.md index e6c343dcd..b39c09026 100644 --- a/doc/docker.md +++ b/doc/docker.md @@ -29,6 +29,30 @@ The variable value has to be a space separated, double quoted list of plugin nam Some plugins will need personalized settings. Just refer to the previous section, and include them in your custom `settings.json.docker`. +### Rebuilding including export functionality for DOC/PDF/ODT + +If you want to be able to export your pads to DOC/PDF/ODT files, you can install +either Abiword or Libreoffice via setting a build variable. + +#### Via Abiword + +For installing Abiword, set the `INSTALL_ABIWORD` build variable to any value. + +Also, you will need to configure the path to the abiword executable +via setting the `abiword` property in `/settings.json.docker` to +`/usr/bin/abiword` or via setting the environment variable `ABIWORD` to +`/usr/bin/abiword`. + +#### Via Libreoffice + +For installing Libreoffice instead, set the `INSTALL_SOFFICE` build variable +to any value. + +Also, you will need to configure the path to the libreoffice executable +via setting the `soffice` property in `/settings.json.docker` to +`/usr/bin/soffice` or via setting the environment variable `SOFFICE` to +`/usr/bin/soffice`. + ### Examples Build a Docker image from the currently checked-out code: @@ -168,8 +192,8 @@ For the editor container, you can also make it full width by adding `full-width- | `IMPORT_MAX_FILE_SIZE` | maximum allowed file size when importing a pad, in bytes. | `52428800` (50 MB) | | `IMPORT_EXPORT_MAX_REQ_PER_IP` | maximum number of import/export calls per IP. | `10` | | `IMPORT_EXPORT_RATE_LIMIT_WINDOW` | the call rate for import/export requests will be estimated in this time window (in milliseconds) | `90000` | -| `COMMIT_RATE_LIMIT_DURATION` | duration of the rate limit window for commits by individual users/IPs (in seconds) | `1` | -| `COMMIT_RATE_LIMIT_POINTS` | maximum number of changes per IP to allow during the rate limit window | `10` | +| `COMMIT_RATE_LIMIT_DURATION` | duration of the rate limit window for commits by individual users/IPs (in seconds) | `1` | +| `COMMIT_RATE_LIMIT_POINTS` | maximum number of changes per IP to allow during the rate limit window | `10` | | `SUPPRESS_ERRORS_IN_PAD_TEXT` | Should we suppress errors from being visible in the default Pad Text? | `false` | | `REQUIRE_SESSION` | If this option is enabled, a user must have a session to access pads. This effectively allows only group pads to be accessed. | `false` | | `EDIT_ONLY` | Users may edit pads but not create new ones. Pad creation is only via the API. This applies both to group pads and regular pads. | `false` | diff --git a/src/bin/buildForWindows.sh b/src/bin/buildForWindows.sh index d62ef810e..5fd2a4bde 100755 --- a/src/bin/buildForWindows.sh +++ b/src/bin/buildForWindows.sh @@ -7,7 +7,7 @@ fatal() { error "$@"; exit 1; } is_cmd() { command -v "$@" >/dev/null 2>&1; } # Move to the folder where ep-lite is installed -cd "$(dirname "$0")"/.. +cd "$(cd "${0%/*}" && pwd -P)/../.." # Is wget installed? is_cmd wget || fatal "Please install wget" @@ -54,7 +54,7 @@ rm -rf "$TMP_FOLDER"/src/node_modules/nodemailer/node_modules/mailcomposer/node_ log "create the zip..." cd "$TMP_FOLDER" -zip -9 -r "$START_FOLDER"/etherpad-lite-win.zip ./* +zip -9 -r "$START_FOLDER"/etherpad-lite-win.zip ./* -x var log "clean up..." rm -rf "$TMP_FOLDER" diff --git a/src/bin/cleanRun.sh b/src/bin/cleanRun.sh index e8f4bd0d4..d8407d92e 100755 --- a/src/bin/cleanRun.sh +++ b/src/bin/cleanRun.sh @@ -34,7 +34,6 @@ rm -rf src/node_modules src/bin/installDeps.sh "$@" || exit 1 #Move to the node folder and start -echo "Started Etherpad..." +echo "Starting Etherpad..." -SCRIPTPATH=$(pwd -P) -node $(compute_node_args) "${SCRIPTPATH}/node_modules/ep_etherpad-lite/node/server.js" "$@" +exec node $(compute_node_args) src/node/server.js "$@" diff --git a/src/bin/deb-src/sysroot/etc/init/etherpad.conf b/src/bin/deb-src/sysroot/etc/init/etherpad.conf index aab40bca8..82706654d 100644 --- a/src/bin/deb-src/sysroot/etc/init/etherpad.conf +++ b/src/bin/deb-src/sysroot/etc/init/etherpad.conf @@ -20,7 +20,7 @@ end script script cd $EPHOME/ - exec su -s /bin/sh -c 'exec "$0" "$@"' $EPUSER -- node node_modules/ep_etherpad-lite/node/server.js \ + exec su -s /bin/sh -c 'exec "$0" "$@"' $EPUSER -- node src/node/server.js \ >> $EPLOGS/access.log \ 2>> $EPLOGS/error.log echo "Etherpad is running on http://localhost:9001 - To change settings edit /opt/etherpad/settings.json" diff --git a/src/bin/debugRun.sh b/src/bin/debugRun.sh index f418f4f64..fd5c34b2c 100755 --- a/src/bin/debugRun.sh +++ b/src/bin/debugRun.sh @@ -16,4 +16,4 @@ echo "Open 'chrome://inspect' on Chrome to start debugging." # Use 0.0.0.0 to allow external connections to the debugger # (ex: running Etherpad on a docker container). Use default port # (9229) -node $(compute_node_args) --inspect=0.0.0.0:9229 node_modules/ep_etherpad-lite/node/server.js "$@" +exec node $(compute_node_args) --inspect=0.0.0.0:9229 src/node/server.js "$@" diff --git a/src/bin/doc/package-lock.json b/src/bin/doc/package-lock.json index eb5526135..2058974ec 100644 --- a/src/bin/doc/package-lock.json +++ b/src/bin/doc/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "marked": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz", - "integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.1.1.tgz", + "integrity": "sha512-mJzT8D2yPxoPh7h0UXkB+dBj4FykPJ2OIfxAWeIHrvoHDkFxukV/29QxoFQoPM6RLEwhIFdJpmKBlqVM3s2ZIw==" } } } diff --git a/src/bin/doc/package.json b/src/bin/doc/package.json index 2f027616c..f484a5c64 100644 --- a/src/bin/doc/package.json +++ b/src/bin/doc/package.json @@ -7,7 +7,7 @@ "node": ">=10.17.0" }, "dependencies": { - "marked": "0.8.2" + "marked": "1.1.1" }, "devDependencies": {}, "optionalDependencies": {}, diff --git a/src/bin/fastRun.sh b/src/bin/fastRun.sh index a782cafcb..ec53f20fe 100755 --- a/src/bin/fastRun.sh +++ b/src/bin/fastRun.sh @@ -19,4 +19,4 @@ cd "${MY_DIR}/../.." || exit 1 echo "Running directly, without checking/installing dependencies" # run Etherpad main class -node $(compute_node_args) "node_modules/ep_etherpad-lite/node/server.js" "$@" +exec node $(compute_node_args) src/node/server.js "$@" diff --git a/src/bin/plugins/lib/backend-tests.yml b/src/bin/plugins/lib/backend-tests.yml index f1d3a4af1..3cb7dad50 100644 --- a/src/bin/plugins/lib/backend-tests.yml +++ b/src/bin/plugins/lib/backend-tests.yml @@ -43,9 +43,8 @@ jobs: cd node_modules/${{github.event.repository.name}} npm ci - # configures some settings and runs npm run test - name: Run the backend tests - run: src/tests/frontend/travis/runnerBackend.sh + run: cd src && npm test -##ETHERPAD_NPM_V=1 +##ETHERPAD_NPM_V=2 ## NPM configuration automatically created using src/bin/plugins/updateAllPluginsScript.sh diff --git a/src/bin/release.js b/src/bin/release.js index 42178caa9..b66bce4da 100644 --- a/src/bin/release.js +++ b/src/bin/release.js @@ -6,8 +6,12 @@ process.on('unhandledRejection', (err) => { throw err; }); const fs = require('fs'); const childProcess = require('child_process'); +const log4js = require('log4js'); +const path = require('path'); const semver = require('semver'); +log4js.replaceConsole(); + /* Usage @@ -25,10 +29,80 @@ if (!release) { throw new Error('No release type included'); } +const cwd = path.join(fs.realpathSync(__dirname), '../../'); +process.chdir(cwd); + +// Run command capturing stdout. Trailing newlines are stripped (like the shell does). +const runc = + (cmd, opts = {}) => childProcess.execSync(cmd, {encoding: 'utf8', ...opts}).replace(/\n+$/, ''); +// Run command without capturing stdout. +const run = (cmd, opts = {}) => childProcess.execSync(cmd, {stdio: 'inherit', ...opts}); + +const readJson = (filename) => JSON.parse(fs.readFileSync(filename, {encoding: 'utf8', flag: 'r'})); +const writeJson = (filename, obj) => { + let json = JSON.stringify(obj, null, 2); + if (json !== '' && !json.endsWith('\n')) json += '\n'; + fs.writeFileSync(filename, json); +}; + +const assertWorkDirClean = (opts = {}) => { + opts.cwd = runc('git rev-parse --show-cdup', opts) || cwd; + const m = runc('git diff-files --name-status', opts); + if (m !== '') throw new Error(`modifications in working directory ${opts.cwd}:\n${m}`); + const u = runc('git ls-files -o --exclude-standard', opts); + if (u !== '') throw new Error(`untracked files in working directory ${opts.cwd}:\n${u}`); + const s = runc('git diff-index --cached --name-status HEAD', opts); + if (s !== '') throw new Error(`uncommitted changes in working directory ${opts.cwd}:\n${s}`); +}; + +const assertBranchCheckedOut = (branch, opts = {}) => { + const b = runc('git symbolic-ref HEAD', opts); + if (b !== `refs/heads/${branch}`) { + const d = opts.cwd ? path.resolve(cwd, opts.cwd) : cwd; + throw new Error(`${branch} must be checked out (cwd: ${d})`); + } +}; + +const assertUpstreamOk = (branch, opts = {}) => { + const upstream = runc(`git rev-parse --symbolic-full-name ${branch}@{u}`, opts); + if (!(new RegExp(`^refs/remotes/[^/]+/${branch}`)).test(upstream)) { + throw new Error(`${branch} should track origin/${branch}; see git branch --set-upstream-to`); + } + try { + run(`git merge-base --is-ancestor ${branch} ${branch}@{u}`); + } catch (err) { + if (err.status !== 1) throw err; + throw new Error(`${branch} is ahead of origin/${branch}; do you need to push?`); + } +}; + +const dirExists = (dir) => { + try { + return fs.statSync(dir).isDirectory(); + } catch (err) { + if (err.code !== 'ENOENT') throw err; + return false; + } +}; + +// Sanity checks for Etherpad repo. +assertWorkDirClean(); +assertBranchCheckedOut('develop'); +assertUpstreamOk('develop'); +assertUpstreamOk('master'); + +// Sanity checks for documentation repo. +if (!dirExists('../ether.github.com')) { + throw new Error('please clone documentation repo: ' + + '(cd .. && git clone git@github.com:ether/ether.github.com.git)'); +} +assertWorkDirClean({cwd: '../ether.github.com/'}); +assertBranchCheckedOut('master', {cwd: '../ether.github.com/'}); +assertUpstreamOk('master', {cwd: '../ether.github.com/'}); + const changelog = fs.readFileSync('CHANGELOG.md', {encoding: 'utf8', flag: 'r'}); -let packageJson = fs.readFileSync('./src/package.json', {encoding: 'utf8', flag: 'r'}); -packageJson = JSON.parse(packageJson); -const currentVersion = packageJson.version; +const pkg = readJson('./src/package.json'); +const currentVersion = pkg.version; const newVersion = semver.inc(currentVersion, release); if (!newVersion) { @@ -36,40 +110,95 @@ if (!newVersion) { throw new Error('Unable to generate new version from input'); } -const changelogIncludesVersion = changelog.indexOf(newVersion) !== -1; - -if (!changelogIncludesVersion) { +if (!changelog.startsWith(`# ${newVersion}\n`)) { throw new Error(`No changelog record for ${newVersion}, please create changelog record`); } -console.log('Okay looks good, lets create the package.json and package-lock.json'); +// //////////////////////////////////////////////////////////////////////////////////////////////// +// Done with sanity checks, now it's time to make changes. -packageJson.version = newVersion; +try { + console.log('Updating develop branch...'); + run('git pull --ff-only'); -fs.writeFileSync('src/package.json', JSON.stringify(packageJson, null, 2)); + console.log(`Bumping ${release} version (to ${newVersion})...`); + pkg.version = newVersion; -// run npm version `release` where release is patch, minor or major -childProcess.execSync('npm install --package-lock-only', {cwd: 'src/'}); -// run npm install --package-lock-only <-- required??? + writeJson('./src/package.json', pkg); -childProcess.execSync(`git checkout -b release/${newVersion}`); -childProcess.execSync('git add src/package.json'); -childProcess.execSync('git add src/package-lock.json'); -childProcess.execSync('git commit -m "bump version"'); -childProcess.execSync(`git push origin release/${newVersion}`); + // run npm version `release` where release is patch, minor or major + run('npm install --package-lock-only', {cwd: 'src/'}); + // run npm install --package-lock-only <-- required??? + // Many users will be using the latest LTS version of npm, and the latest LTS version of npm uses + // lockfileVersion 1. Enforce v1 so that users don't see a (benign) compatibility warning. + if (readJson('./src/package-lock.json').lockfileVersion !== 1) { + throw new Error('Please regenerate package-lock.json with npm v6.x.'); + } -childProcess.execSync('make docs'); -childProcess.execSync('git clone git@github.com:ether/ether.github.com.git'); -childProcess.execSync(`cp -R out/doc/ ether.github.com/doc/v${newVersion}`); + run('git add src/package.json'); + run('git add src/package-lock.json'); + run('git commit -m "bump version"'); + console.log('Switching to master...'); + run('git checkout master'); + console.log('Updating master branch...'); + run('git pull --ff-only'); + console.log('Merging develop into master...'); + run('git merge --no-ff --no-edit develop'); + console.log(`Creating ${newVersion} tag...`); + run(`git tag -s '${newVersion}' -m '${newVersion}'`); + console.log('Switching back to develop...'); + run('git checkout develop'); + console.log('Merging master into develop...'); + run('git merge --no-ff --no-edit master'); +} catch (err) { + console.error(err.toString()); + console.warn('Resetting repository...'); + console.warn('Resetting master...'); + run('git checkout -f master'); + run('git reset --hard @{u}'); + console.warn('Resetting develop...'); + run('git checkout -f develop'); + run('git reset --hard @{u}'); + console.warn(`Deleting ${newVersion} tag...`); + run(`git rev-parse -q --verify refs/tags/'${newVersion}' >/dev/null || exit 0; ` + + `git tag -d '${newVersion}'`); + throw err; +} -console.log('Once merged into master please run the following commands'); -console.log(`git tag -a ${newVersion} -m ${newVersion} && git push origin master`); -console.log(`cd ether.github.com && git add . && git commit -m '${newVersion} docs'`); -console.log('Build the windows zip'); +try { + console.log('Building documentation...'); + run('make docs'); + console.log('Updating ether.github.com master branch...'); + run('git pull --ff-only', {cwd: '../ether.github.com/'}); + console.log('Committing documentation...'); + run(`cp -R out/doc/ ../ether.github.com/doc/v'${newVersion}'`); + run(`rm -f latest && ln -s 'v${newVersion}' latest`, {cwd: '../ether.github.com/doc/'}); + run('git add .', {cwd: '../ether.github.com/'}); + run(`git commit -m '${newVersion} docs'`, {cwd: '../ether.github.com/'}); +} catch (err) { + console.error(err.toString()); + console.warn('Resetting repository...'); + console.warn('Resetting master...'); + run('git checkout -f master', {cwd: '../ether.github.com/'}); + run('git reset --hard @{u}', {cwd: '../ether.github.com/'}); + throw err; +} + +console.log('Done.'); +console.log('Review the new commits and the new tag:'); +console.log(' git log --graph --date-order --boundary --oneline --decorate develop@{u}..develop'); +console.log(` git show '${newVersion}'`); +console.log(' (cd ../ether.github.com && git show)'); +console.log('If everything looks good then push:'); +console.log(` git push origin master develop '${newVersion}'`); +console.log(' (cd ../ether.github.com && git push)'); +console.log('Create a Windows build:'); +console.log(' bin/buildForWindows.sh'); console.log('Visit https://github.com/ether/etherpad-lite/releases/new and create a new release ' + `with 'master' as the target and the version is ${newVersion}. Include the windows ` + 'zip as an asset'); -console.log(`Once the new docs are uploaded then modify the download - link on etherpad.org and then pull master onto develop`); +console.log('Once the new docs are uploaded then modify the download links (replace ' + + `${currentVersion} with ${newVersion} on etherpad.org and then pull master onto ` + + 'develop)'); console.log('Finally go public with an announcement via our comms channels :)'); diff --git a/src/bin/run.sh b/src/bin/run.sh index 1a2aa36a9..4f6993ff5 100755 --- a/src/bin/run.sh +++ b/src/bin/run.sh @@ -32,5 +32,4 @@ src/bin/installDeps.sh "$@" || exit 1 # Move to the node folder and start log "Starting Etherpad..." -SCRIPTPATH=$(pwd -P) -exec node $(compute_node_args) "$SCRIPTPATH/node_modules/ep_etherpad-lite/node/server.js" "$@" +exec node $(compute_node_args) src/node/server.js "$@" diff --git a/src/ep.json b/src/ep.json index 7e73b69c5..5642f8c12 100644 --- a/src/ep.json +++ b/src/ep.json @@ -84,7 +84,8 @@ "name": "socketio", "hooks": { "expressCloseServer": "ep_etherpad-lite/node/hooks/express/socketio", - "expressCreateServer": "ep_etherpad-lite/node/hooks/express/socketio" + "expressCreateServer": "ep_etherpad-lite/node/hooks/express/socketio", + "socketio": "ep_etherpad-lite/node/handler/PadMessageHandler" } }, { diff --git a/src/node/handler/ImportHandler.js b/src/node/handler/ImportHandler.js index 67bef01ce..d97ce91ef 100644 --- a/src/node/handler/ImportHandler.js +++ b/src/node/handler/ImportHandler.js @@ -103,20 +103,16 @@ const doImport = async (req, res, padId) => { // locally wrapped Promise, since form.parse requires a callback let srcFile = await new Promise((resolve, reject) => { form.parse(req, (err, fields, files) => { - if (err || files.file === undefined) { - // the upload failed, stop at this point - if (err) { - console.warn(`Uploading Error: ${err.stack}`); - } - + if (err != null) { + logger.warn(`Import failed due to form error: ${err.stack || err}`); // I hate doing indexOf here but I can't see anything to use... if (err && err.stack && err.stack.indexOf('maxFileSize') !== -1) { return reject(new ImportError('maxFileSize')); } - return reject(new ImportError('uploadFailed')); } - if (!files.file) { // might not be a graceful fix but it works + if (!files.file) { + logger.warn('Import failed because form had no file'); return reject(new ImportError('uploadFailed')); } resolve(files.file.path); @@ -140,7 +136,7 @@ const doImport = async (req, res, padId) => { srcFile = path.join(path.dirname(srcFile), `${path.basename(srcFile, fileEnding)}.txt`); await fs.rename(oldSrcFile, srcFile); } else { - console.warn('Not allowing unknown file type to be imported', fileEnding); + logger.warn(`Not allowing unknown file type to be imported: ${fileEnding}`); throw new ImportError('uploadFailed'); } } @@ -188,7 +184,7 @@ const doImport = async (req, res, padId) => { convertor.convertFile(srcFile, destFile, exportExtension, (err) => { // catch convert errors if (err) { - console.warn('Converting Error:', err); + logger.warn(`Converting Error: ${err.stack || err}`); return reject(new ImportError('convertFailed')); } resolve(); @@ -205,6 +201,7 @@ const doImport = async (req, res, padId) => { const isAscii = !Array.prototype.some.call(buf, (c) => (c > 240)); if (!isAscii) { + logger.warn('Attempt to import non-ASCII file'); throw new ImportError('uploadFailed'); } } @@ -230,8 +227,8 @@ const doImport = async (req, res, padId) => { if (importHandledByPlugin || useConvertor || fileIsHTML) { try { await importHtml.setPadHTML(pad, text); - } catch (e) { - logger.warn('Error importing, possibly caused by malformed HTML'); + } catch (err) { + logger.warn(`Error importing, possibly caused by malformed HTML: ${err.stack || err}`); } } else { await pad.setText(text); diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 7e290b9e3..c7ff1f661 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -40,10 +40,14 @@ const nodeify = require('nodeify'); const {RateLimiterMemory} = require('rate-limiter-flexible'); const webaccess = require('../hooks/express/webaccess'); -const rateLimiter = new RateLimiterMemory({ - points: settings.commitRateLimiting.points, - duration: settings.commitRateLimiting.duration, -}); +let rateLimiter; + +exports.socketio = () => { + // The rate limiter is created in this hook so that restarting the server resets the limiter. The + // settings.commitRateLimiting object is passed directly to the rate limiter so that the limits + // can be dynamically changed during runtime by modifying its properties. + rateLimiter = new RateLimiterMemory(settings.commitRateLimiting); +}; /** * A associative array that saves information about a session diff --git a/src/node/hooks/express/importexport.js b/src/node/hooks/express/importexport.js index 598629632..d6f287c6b 100644 --- a/src/node/hooks/express/importexport.js +++ b/src/node/hooks/express/importexport.js @@ -10,15 +10,15 @@ const rateLimit = require('express-rate-limit'); const securityManager = require('../../db/SecurityManager'); const webaccess = require('./webaccess'); -settings.importExportRateLimiting.onLimitReached = (req, res, options) => { - // when the rate limiter triggers, write a warning in the logs - console.warn('Import/Export rate limiter triggered on ' + - `"${req.originalUrl}" for IP address ${req.ip}`); -}; - -const limiter = rateLimit(settings.importExportRateLimiting); - exports.expressCreateServer = (hookName, args, cb) => { + settings.importExportRateLimiting.onLimitReached = (req, res, options) => { + // when the rate limiter triggers, write a warning in the logs + console.warn('Import/Export rate limiter triggered on ' + + `"${req.originalUrl}" for IP address ${req.ip}`); + }; + // The rate limiter is created in this hook so that restarting the server resets the limiter. + const limiter = rateLimit(settings.importExportRateLimiting); + // handle export requests args.app.use('/p/:pad/:rev?/export/:type', limiter); args.app.get('/p/:pad/:rev?/export/:type', (req, res, next) => { diff --git a/src/node/utils/run_cmd.js b/src/node/utils/run_cmd.js index f0d7c92d6..4b30af1fe 100644 --- a/src/node/utils/run_cmd.js +++ b/src/node/utils/run_cmd.js @@ -1,6 +1,6 @@ 'use strict'; -const childProcess = require('child_process'); +const spawn = require('cross-spawn'); const log4js = require('log4js'); const path = require('path'); const settings = require('./Settings'); @@ -28,14 +28,14 @@ const logLines = (readable, logLineFn) => { }; /** - * Similar to `util.promisify(childProcess.exec)`, except: + * Similar to `util.promisify(child_rocess.exec)`, except: * - `cwd` defaults to the Etherpad root directory. * - PATH is prefixed with src/node_modules/.bin so that utilities from installed dependencies * (e.g., npm) are preferred over system utilities. * - Output is passed to logger callback functions by default. See below for details. * * @param args Array of command-line arguments, where `args[0]` is the command to run. - * @param opts Optional options that will be passed to `childProcess.spawn()` with two extensions: + * @param opts Optional options that will be passed to `child_process.spawn()` with two extensions: * - `stdoutLogger`: Callback that is called each time a line of text is written to stdout (utf8 * is assumed). The line (without trailing newline) is passed as the only argument. If null, * stdout is not logged. If unset, defaults to no-op. Ignored if stdout is not a pipe. @@ -48,7 +48,7 @@ module.exports = exports = (args, opts = {}) => { logger.debug(`Executing command: ${args.join(' ')}`); const {stdoutLogger = () => {}, stderrLogger = () => {}} = opts; - // Avoid confusing childProcess.spawn() with our extensions. + // Avoid confusing child_process.spawn() with our extensions. opts = {...opts}; // Make a copy to avoid mutating the caller's copy. delete opts.stdoutLogger; delete opts.stderrLogger; @@ -70,7 +70,7 @@ module.exports = exports = (args, opts = {}) => { // process's `exit` handler so that we get a useful stack trace. const procFailedErr = new Error(`Command exited non-zero: ${args.join(' ')}`); - const proc = childProcess.spawn(args[0], args.slice(1), {cwd: settings.root, ...opts, env}); + const proc = spawn(args[0], args.slice(1), {cwd: settings.root, ...opts, env}); if (proc.stdout != null && stdoutLogger != null) logLines(proc.stdout, stdoutLogger); if (proc.stderr != null && stderrLogger != null) logLines(proc.stderr, stderrLogger); const p = new Promise((resolve, reject) => { diff --git a/src/package-lock.json b/src/package-lock.json index ee8aa7c34..a54e57b47 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -1,6 +1,6 @@ { "name": "ep_etherpad-lite", - "version": "1.8.8", + "version": "1.8.9", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1281,7 +1281,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1292,7 +1291,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -2028,9 +2026,9 @@ "integrity": "sha1-7Y8E6f0szsOgBVu20t/p2ZkS5+I=" }, "etherpad-yajsml": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/etherpad-yajsml/-/etherpad-yajsml-0.0.2.tgz", - "integrity": "sha1-HCTSaLCUduY30EnN2xxt+McptG4=" + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/etherpad-yajsml/-/etherpad-yajsml-0.0.4.tgz", + "integrity": "sha512-rxpEOMZmv6DOCQeaDo6tztneaKF9ZxbLo/+hQcV+hn0lNrxJZ7MKIPD2pTWWnNLj6gFFfs6QQ67RfMNWIr3fSA==" }, "event-target-shim": { "version": "5.0.1", @@ -2082,9 +2080,9 @@ } }, "express-rate-limit": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.1.1.tgz", - "integrity": "sha512-puA1zcCx/quwWUOU6pT6daCt6t7SweD9wKChKhb+KSgFMKRwS81C224hiSAUANw/gnSHiwEhgozM/2ezEBZPeA==" + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.2.3.tgz", + "integrity": "sha512-cjQH+oDrEPXxc569XvxhHC6QXqJiuBT6BhZ70X3bdAImcnHnTNMVuMAJaT0TXPoRiEErUrVPRcOTpZpM36VbOQ==" }, "express-session": { "version": "1.17.1", @@ -2245,9 +2243,9 @@ } }, "formidable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", - "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==" }, "forwarded": { "version": "0.1.2", @@ -3737,9 +3735,9 @@ "dev": true }, "npm": { - "version": "6.14.8", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.8.tgz", - "integrity": "sha512-HBZVBMYs5blsj94GTeQZel7s9odVuuSUHy1+AlZh7rPVux1os2ashvEGLy/STNK7vUjbrCg5Kq9/GXisJgdf6A==", + "version": "6.14.11", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.11.tgz", + "integrity": "sha512-1Zh7LjuIoEhIyjkBflSSGzfjuPQwDlghNloppjruOH5bmj9midT9qcNT0tRUZRR04shU9ekrxNy9+UTBrqeBpQ==", "requires": { "JSONStream": "^1.3.5", "abbrev": "~1.1.1", @@ -3778,7 +3776,7 @@ "infer-owner": "^1.0.4", "inflight": "~1.0.6", "inherits": "^2.0.4", - "ini": "^1.3.5", + "ini": "^1.3.8", "init-package-json": "^1.10.3", "is-cidr": "^3.0.0", "json-parse-better-errors": "^1.0.2", @@ -3821,10 +3819,10 @@ "npm-pick-manifest": "^3.0.2", "npm-profile": "^4.0.4", "npm-registry-fetch": "^4.0.7", - "npm-user-validate": "~1.0.0", + "npm-user-validate": "^1.0.1", "npmlog": "~4.1.2", "once": "~1.4.0", - "opener": "^1.5.1", + "opener": "^1.5.2", "osenv": "^0.1.5", "pacote": "^9.5.12", "path-is-inside": "~1.0.2", @@ -3892,16 +3890,6 @@ "humanize-ms": "^1.2.1" } }, - "ajv": { - "version": "5.5.2", - "bundled": true, - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, "ansi-align": { "version": "2.0.0", "bundled": true, @@ -4187,10 +4175,6 @@ "mkdirp": "~0.5.0" } }, - "co": { - "version": "4.6.0", - "bundled": true - }, "code-point-at": { "version": "1.1.0", "bundled": true @@ -4579,10 +4563,6 @@ "version": "1.3.0", "bundled": true }, - "fast-deep-equal": { - "version": "1.1.0", - "bundled": true - }, "fast-json-stable-stringify": { "version": "2.0.0", "bundled": true @@ -4867,11 +4847,31 @@ "bundled": true }, "har-validator": { - "version": "5.1.0", + "version": "5.1.5", "bundled": true, "requires": { - "ajv": "^5.3.0", + "ajv": "^6.12.3", "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "bundled": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "bundled": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "bundled": true + } } }, "has": { @@ -4976,7 +4976,7 @@ "bundled": true }, "ini": { - "version": "1.3.5", + "version": "1.3.8", "bundled": true }, "init-package-json": { @@ -5114,10 +5114,6 @@ "version": "0.2.3", "bundled": true }, - "json-schema-traverse": { - "version": "0.3.1", - "bundled": true - }, "json-stringify-safe": { "version": "5.0.1", "bundled": true @@ -5688,7 +5684,7 @@ } }, "npm-user-validate": { - "version": "1.0.0", + "version": "1.0.1", "bundled": true }, "npmlog": { @@ -5733,7 +5729,7 @@ } }, "opener": { - "version": "1.5.1", + "version": "1.5.2", "bundled": true }, "os-homedir": { @@ -6563,6 +6559,19 @@ "xdg-basedir": "^3.0.0" } }, + "uri-js": { + "version": "4.4.0", + "bundled": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "bundled": true + } + } + }, "url-parse-lax": { "version": "1.0.0", "bundled": true, @@ -7086,8 +7095,7 @@ "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "path-parse": { "version": "1.0.6", @@ -7536,9 +7544,9 @@ "integrity": "sha1-gRwwAxNoYTPvAAcSXjsO1wCXiBU=" }, "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "send": { "version": "0.17.1", @@ -7610,7 +7618,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -7618,8 +7625,7 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "signal-exit": { "version": "3.0.3", @@ -8292,9 +8298,9 @@ } }, "tinycon": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tinycon/-/tinycon-0.0.1.tgz", - "integrity": "sha1-beEM1SGaHxIdmgokssEbP7JN/+0=" + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/tinycon/-/tinycon-0.6.8.tgz", + "integrity": "sha1-59oiPj7gy/nbeWP6M1aZuyF3enM=" }, "to-array": { "version": "0.1.4", diff --git a/src/package.json b/src/package.json index 3a16262ea..757525c22 100644 --- a/src/package.json +++ b/src/package.json @@ -36,14 +36,15 @@ "cheerio": "0.22.0", "clean-css": "4.2.3", "cookie-parser": "1.4.5", + "cross-spawn": "^7.0.3", "ejs": "^3.1.6", "etherpad-require-kernel": "1.0.9", - "etherpad-yajsml": "0.0.2", + "etherpad-yajsml": "0.0.4", "express": "4.17.1", - "express-rate-limit": "5.1.1", + "express-rate-limit": "5.2.3", "express-session": "1.17.1", "find-root": "1.1.0", - "formidable": "1.2.1", + "formidable": "1.2.2", "http-errors": "1.8.0", "js-cookie": "^2.2.1", "jsonminify": "0.4.1", @@ -53,7 +54,7 @@ "measured-core": "1.51.1", "mime-types": "^2.1.27", "nodeify": "1.0.1", - "npm": "6.14.8", + "npm": "6.14.11", "openapi-backend": "2.4.1", "proxy-addr": "^2.0.6", "rate-limiter-flexible": "^2.1.4", @@ -62,12 +63,12 @@ "request": "2.88.2", "resolve": "1.19.0", "security": "1.0.0", - "semver": "5.6.0", + "semver": "5.7.1", "socket.io": "^2.4.1", "terser": "^4.7.0", "threads": "^1.4.0", "tiny-worker": "^2.3.0", - "tinycon": "0.0.1", + "tinycon": "0.6.8", "ueberdb2": "^1.2.5", "underscore": "1.12.0", "unorm": "1.4.1", @@ -236,6 +237,6 @@ "test": "mocha --timeout 120000 --recursive tests/backend/specs ../node_modules/ep_*/static/tests/backend/specs", "test-container": "mocha --timeout 5000 tests/container/specs/api" }, - "version": "1.8.8", + "version": "1.8.9", "license": "Apache-2.0" } diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index a11c489b5..b41f4f0ff 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -3197,28 +3197,6 @@ function Ace2Inner() { scroll.setScrollY(caretOffsetTop); }, 200); } - - // scroll to viewport when user presses arrow keys and caret is out of the viewport - if ((evt.which === 37 || evt.which === 38 || evt.which === 39 || evt.which === 40)) { - // we use arrowKeyWasReleased to avoid triggering the animation when a key - // is continuously pressed - // this makes the scroll smooth - if (!continuouslyPressingArrowKey(type)) { - // the caret position is not synchronized with the rep. - // For example, when an user presses arrow - // We use getSelection() instead of rep to get the caret position. - // This avoids errors like when down to scroll the pad without releasing the key. - // When the key is released the rep is not - // synchronized, so we don't get the right node where caret is. - const selection = getSelection(); - - if (selection) { - const arrowUp = evt.which === 38; - const innerHeight = getInnerHeight(); - scroll.scrollWhenPressArrowKeys(arrowUp, rep, innerHeight); - } - } - } } if (type === 'keydown') { @@ -3263,19 +3241,6 @@ function Ace2Inner() { }; let thisKeyDoesntTriggerNormalize = false; - let arrowKeyWasReleased = true; - const continuouslyPressingArrowKey = (type) => { - let firstTimeKeyIsContinuouslyPressed = false; - - if (type === 'keyup') { - arrowKeyWasReleased = true; - } else if (type === 'keydown' && arrowKeyWasReleased) { - firstTimeKeyIsContinuouslyPressed = true; - arrowKeyWasReleased = false; - } - - return !firstTimeKeyIsContinuouslyPressed; - }; const doUndoRedo = (which) => { // precond: normalized DOM diff --git a/src/static/js/pad_impexp.js b/src/static/js/pad_impexp.js index 60a51354f..8689b5b35 100644 --- a/src/static/js/pad_impexp.js +++ b/src/static/js/pad_impexp.js @@ -42,7 +42,7 @@ const padimpexp = (() => { $('#importmessagefail').fadeOut('fast'); }; - const fileInputSubmit = (e) => { + const fileInputSubmit = function (e) { e.preventDefault(); $('#importmessagefail').fadeOut('fast'); if (!window.confirm(html10n.get('pad.impexp.confirmimport'))) return; diff --git a/src/tests/backend/common.js b/src/tests/backend/common.js index 917d0c21a..aabc48a54 100644 --- a/src/tests/backend/common.js +++ b/src/tests/backend/common.js @@ -45,6 +45,8 @@ exports.init = async function () { // Start the Etherpad server on a random unused port. settings.port = 0; settings.ip = 'localhost'; + settings.importExportRateLimiting = {max: 0}; + settings.commitRateLimiting = {duration: 0.001, points: 1e6}; exports.httpServer = await server.start(); exports.baseUrl = `http://localhost:${exports.httpServer.address().port}`; exports.logger.debug(`HTTP server at ${exports.baseUrl}`); diff --git a/src/tests/backend/specs/api/characterEncoding.js b/src/tests/backend/specs/api/characterEncoding.js index 00183e12a..3080425ec 100644 --- a/src/tests/backend/specs/api/characterEncoding.js +++ b/src/tests/backend/specs/api/characterEncoding.js @@ -8,19 +8,21 @@ const common = require('../../common'); const fs = require('fs'); -const settings = require('../../../../node/utils/Settings'); -const supertest = require('supertest'); -const api = supertest(`http://${settings.ip}:${settings.port}`); +let agent; const apiKey = common.apiKey; let apiVersion = 1; const testPadId = makeid(); +const endPoint = (point, version) => `/api/${version || apiVersion}/${point}?apikey=${apiKey}`; + describe(__filename, function () { + before(async function () { agent = await common.init(); }); + describe('Connectivity For Character Encoding', function () { it('can connect', function (done) { this.timeout(250); - api.get('/api/') + agent.get('/api/') .expect('Content-Type', /json/) .expect(200, done); }); @@ -29,7 +31,7 @@ describe(__filename, function () { describe('API Versioning', function () { this.timeout(150); it('finds the version tag', function (done) { - api.get('/api/') + agent.get('/api/') .expect((res) => { apiVersion = res.body.currentVersion; if (!res.body.currentVersion) throw new Error('No version set in API'); @@ -45,7 +47,7 @@ describe(__filename, function () { // This is broken because Etherpad doesn't handle HTTP codes properly see #2343 // If your APIKey is password you deserve to fail all tests anyway const permErrorURL = `/api/${apiVersion}/createPad?apikey=password&padID=test`; - api.get(permErrorURL) + agent.get(permErrorURL) .expect(401, done); }); }); @@ -53,7 +55,7 @@ describe(__filename, function () { describe('createPad', function () { it('creates a new Pad', function (done) { this.timeout(150); - api.get(`${endPoint('createPad')}&padID=${testPadId}`) + agent.get(`${endPoint('createPad')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Unable to create new Pad'); }) @@ -66,7 +68,7 @@ describe(__filename, function () { it('Sets the HTML of a Pad attempting to weird utf8 encoded content', function (done) { this.timeout(1000); fs.readFile('tests/backend/specs/api/emojis.html', 'utf8', (err, html) => { - api.post(endPoint('setHTML')) + agent.post(endPoint('setHTML')) .send({ padID: testPadId, html, @@ -83,7 +85,7 @@ describe(__filename, function () { describe('getHTML', function () { it('get the HTML of Pad with emojis', function (done) { this.timeout(400); - api.get(`${endPoint('getHTML')}&padID=${testPadId}`) + agent.get(`${endPoint('getHTML')}&padID=${testPadId}`) .expect((res) => { if (res.body.data.html.indexOf('🇼') === -1) { throw new Error('Unable to get the HTML'); @@ -101,11 +103,6 @@ describe(__filename, function () { */ -var endPoint = function (point, version) { - version = version || apiVersion; - return `/api/${version}/${point}?apikey=${apiKey}`; -}; - function makeid() { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; diff --git a/src/tests/backend/specs/api/chat.js b/src/tests/backend/specs/api/chat.js index 570a8aaaa..17f5bf4ef 100644 --- a/src/tests/backend/specs/api/chat.js +++ b/src/tests/backend/specs/api/chat.js @@ -1,18 +1,22 @@ -const common = require('../../common'); -const settings = require('../../../../node/utils/Settings'); -const supertest = require('supertest'); +'use strict'; -const api = supertest(`http://${settings.ip}:${settings.port}`); +const common = require('../../common'); + +let agent; const apiKey = common.apiKey; let apiVersion = 1; let authorID = ''; const padID = makeid(); const timestamp = Date.now(); +const endPoint = (point) => `/api/${apiVersion}/${point}?apikey=${apiKey}`; + describe(__filename, function () { + before(async function () { agent = await common.init(); }); + describe('API Versioning', function () { it('errors if can not connect', function (done) { - api.get('/api/') + agent.get('/api/') .expect((res) => { apiVersion = res.body.currentVersion; if (!res.body.currentVersion) throw new Error('No version set in API'); @@ -37,7 +41,7 @@ describe(__filename, function () { describe('createPad', function () { this.timeout(400); it('creates a new Pad', function (done) { - api.get(`${endPoint('createPad')}&padID=${padID}`) + agent.get(`${endPoint('createPad')}&padID=${padID}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Unable to create new Pad'); }) @@ -49,9 +53,11 @@ describe(__filename, function () { describe('createAuthor', function () { this.timeout(100); it('Creates an author with a name set', function (done) { - api.get(endPoint('createAuthor')) + agent.get(endPoint('createAuthor')) .expect((res) => { - if (res.body.code !== 0 || !res.body.data.authorID) throw new Error('Unable to create author'); + if (res.body.code !== 0 || !res.body.data.authorID) { + throw new Error('Unable to create author'); + } authorID = res.body.data.authorID; // we will be this author for the rest of the tests }) .expect('Content-Type', /json/) @@ -62,7 +68,8 @@ describe(__filename, function () { describe('appendChatMessage', function () { this.timeout(100); it('Adds a chat message to the pad', function (done) { - api.get(`${endPoint('appendChatMessage')}&padID=${padID}&text=blalblalbha&authorID=${authorID}&time=${timestamp}`) + agent.get(`${endPoint('appendChatMessage')}&padID=${padID}&text=blalblalbha` + + `&authorID=${authorID}&time=${timestamp}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Unable to create chat message'); }) @@ -75,7 +82,7 @@ describe(__filename, function () { describe('getChatHead', function () { this.timeout(100); it('Gets the head of chat', function (done) { - api.get(`${endPoint('getChatHead')}&padID=${padID}`) + agent.get(`${endPoint('getChatHead')}&padID=${padID}`) .expect((res) => { if (res.body.data.chatHead !== 0) throw new Error('Chat Head Length is wrong'); @@ -89,9 +96,11 @@ describe(__filename, function () { describe('getChatHistory', function () { this.timeout(40); it('Gets Chat History of a Pad', function (done) { - api.get(`${endPoint('getChatHistory')}&padID=${padID}`) + agent.get(`${endPoint('getChatHistory')}&padID=${padID}`) .expect((res) => { - if (res.body.data.messages.length !== 1) throw new Error('Chat History Length is wrong'); + if (res.body.data.messages.length !== 1) { + throw new Error('Chat History Length is wrong'); + } if (res.body.code !== 0) throw new Error('Unable to get chat history'); }) .expect('Content-Type', /json/) @@ -100,10 +109,6 @@ describe(__filename, function () { }); }); -var endPoint = function (point) { - return `/api/${apiVersion}/${point}?apikey=${apiKey}`; -}; - function makeid() { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; diff --git a/src/tests/backend/specs/api/importexport.js b/src/tests/backend/specs/api/importexport.js index 960f019bd..6b2fb3ac9 100644 --- a/src/tests/backend/specs/api/importexport.js +++ b/src/tests/backend/specs/api/importexport.js @@ -7,13 +7,13 @@ */ const common = require('../../common'); -const settings = require('../../../container/loadSettings.js').loadSettings(); -const supertest = require('supertest'); -const api = supertest(`http://${settings.ip}:${settings.port}`); +let agent; const apiKey = common.apiKey; const apiVersion = 1; +const endPoint = (point, version) => `/api/${version || apiVersion}/${point}?apikey=${apiKey}`; + const testImports = { 'malformed': { input: '
  • wtf', @@ -226,6 +226,8 @@ const testImports = { }; describe(__filename, function () { + before(async function () { agent = await common.init(); }); + Object.keys(testImports).forEach((testName) => { describe(testName, function () { const testPadId = makeid(); @@ -237,7 +239,7 @@ describe(__filename, function () { } it('createPad', function (done) { this.timeout(200); - api.get(`${endPoint('createPad')}&padID=${testPadId}`) + agent.get(`${endPoint('createPad')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Unable to create new Pad'); }) @@ -247,7 +249,8 @@ describe(__filename, function () { it('setHTML', function (done) { this.timeout(150); - api.get(`${endPoint('setHTML')}&padID=${testPadId}&html=${encodeURIComponent(test.input)}`) + agent.get(`${endPoint('setHTML')}&padID=${testPadId}` + + `&html=${encodeURIComponent(test.input)}`) .expect((res) => { if (res.body.code !== 0) throw new Error(`Error:${testName}`); }) @@ -257,7 +260,7 @@ describe(__filename, function () { it('getHTML', function (done) { this.timeout(150); - api.get(`${endPoint('getHTML')}&padID=${testPadId}`) + agent.get(`${endPoint('getHTML')}&padID=${testPadId}`) .expect((res) => { const gotHtml = res.body.data.html; if (gotHtml !== test.wantHTML) { @@ -281,7 +284,7 @@ describe(__filename, function () { it('getText', function (done) { this.timeout(100); - api.get(`${endPoint('getText')}&padID=${testPadId}`) + agent.get(`${endPoint('getText')}&padID=${testPadId}`) .expect((res) => { const gotText = res.body.data.text; if (gotText !== test.wantText) { @@ -306,12 +309,6 @@ describe(__filename, function () { }); }); - -function endPoint(point, version) { - version = version || apiVersion; - return `/api/${version}/${point}?apikey=${apiKey}`; -} - function makeid() { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; diff --git a/src/tests/backend/specs/api/importexportGetPost.js b/src/tests/backend/specs/api/importexportGetPost.js index 86d7ee457..fbd5b944d 100644 --- a/src/tests/backend/specs/api/importexportGetPost.js +++ b/src/tests/backend/specs/api/importexportGetPost.js @@ -127,7 +127,7 @@ describe(__filename, function () { describe('Import/Export tests requiring AbiWord/LibreOffice', function () { - this.timeout(60000); + this.timeout(10000); before(async function () { if ((!settings.abiword || settings.abiword.indexOf('/') === -1) && @@ -139,7 +139,6 @@ describe(__filename, function () { // For some reason word import does not work in testing.. // TODO: fix support for .doc files.. it('Tries to import .doc that uses soffice or abiword', async function () { - this.timeout(10000); await agent.post(`/p/${testPadId}/import`) .attach('file', wordDoc, {filename: '/test.doc', contentType: 'application/msword'}) .expect(200) @@ -152,7 +151,6 @@ describe(__filename, function () { }); it('exports DOC', async function () { - this.timeout(3000); await agent.get(`/p/${testPadId}/export/doc`) .buffer(true).parse(superagent.parse['application/octet-stream']) .expect(200) @@ -160,7 +158,6 @@ describe(__filename, function () { }); it('Tries to import .docx that uses soffice or abiword', async function () { - this.timeout(3000); await agent.post(`/p/${testPadId}/import`) .attach('file', wordXDoc, { filename: '/test.docx', @@ -177,7 +174,6 @@ describe(__filename, function () { }); it('exports DOC from imported DOCX', async function () { - this.timeout(3000); await agent.get(`/p/${testPadId}/export/doc`) .buffer(true).parse(superagent.parse['application/octet-stream']) .expect(200) @@ -185,7 +181,6 @@ describe(__filename, function () { }); it('Tries to import .pdf that uses soffice or abiword', async function () { - this.timeout(3000); await agent.post(`/p/${testPadId}/import`) .attach('file', pdfDoc, {filename: '/test.pdf', contentType: 'application/pdf'}) .expect(200) @@ -198,7 +193,6 @@ describe(__filename, function () { }); it('exports PDF', async function () { - this.timeout(3000); await agent.get(`/p/${testPadId}/export/pdf`) .buffer(true).parse(superagent.parse['application/octet-stream']) .expect(200) @@ -206,7 +200,6 @@ describe(__filename, function () { }); it('Tries to import .odt that uses soffice or abiword', async function () { - this.timeout(3000); await agent.post(`/p/${testPadId}/import`) .attach('file', odtDoc, {filename: '/test.odt', contentType: 'application/odt'}) .expect(200) @@ -219,7 +212,6 @@ describe(__filename, function () { }); it('exports ODT', async function () { - this.timeout(3000); await agent.get(`/p/${testPadId}/export/odt`) .buffer(true).parse(superagent.parse['application/octet-stream']) .expect(200) diff --git a/src/tests/backend/specs/api/instance.js b/src/tests/backend/specs/api/instance.js index ab465033e..64a9ea5e0 100644 --- a/src/tests/backend/specs/api/instance.js +++ b/src/tests/backend/specs/api/instance.js @@ -1,22 +1,25 @@ +'use strict'; + /* * Tests for the instance-level APIs * * Section "GLOBAL FUNCTIONS" in src/node/db/API.js */ const common = require('../../common'); -const settings = require('../../../../node/utils/Settings'); -const supertest = require('supertest'); - -const api = supertest(`http://${settings.ip}:${settings.port}`); +let agent; const apiKey = common.apiKey; const apiVersion = '1.2.14'; +const endPoint = (point, version) => `/api/${version || apiVersion}/${point}?apikey=${apiKey}`; + describe(__filename, function () { + before(async function () { agent = await common.init(); }); + describe('Connectivity for instance-level API tests', function () { it('can connect', function (done) { this.timeout(150); - api.get('/api/') + agent.get('/api/') .expect('Content-Type', /json/) .expect(200, done); }); @@ -25,20 +28,25 @@ describe(__filename, function () { describe('getStats', function () { it('Gets the stats of a running instance', function (done) { this.timeout(100); - api.get(endPoint('getStats')) + agent.get(endPoint('getStats')) .expect((res) => { if (res.body.code !== 0) throw new Error('getStats() failed'); - if (!(('totalPads' in res.body.data) && (typeof res.body.data.totalPads === 'number'))) { - throw new Error(`Response to getStats() does not contain field totalPads, or it's not a number: ${JSON.stringify(res.body.data)}`); + if (!('totalPads' in res.body.data && typeof res.body.data.totalPads === 'number')) { + throw new Error('Response to getStats() does not contain field totalPads, or ' + + `it's not a number: ${JSON.stringify(res.body.data)}`); } - if (!(('totalSessions' in res.body.data) && (typeof res.body.data.totalSessions === 'number'))) { - throw new Error(`Response to getStats() does not contain field totalSessions, or it's not a number: ${JSON.stringify(res.body.data)}`); + if (!('totalSessions' in res.body.data && + typeof res.body.data.totalSessions === 'number')) { + throw new Error('Response to getStats() does not contain field totalSessions, or ' + + `it's not a number: ${JSON.stringify(res.body.data)}`); } - if (!(('totalActivePads' in res.body.data) && (typeof res.body.data.totalActivePads === 'number'))) { - throw new Error(`Response to getStats() does not contain field totalActivePads, or it's not a number: ${JSON.stringify(res.body.data)}`); + if (!('totalActivePads' in res.body.data && + typeof res.body.data.totalActivePads === 'number')) { + throw new Error('Response to getStats() does not contain field totalActivePads, or ' + + `it's not a number: ${JSON.stringify(res.body.data)}`); } }) .expect('Content-Type', /json/) @@ -46,8 +54,3 @@ describe(__filename, function () { }); }); }); - -var endPoint = function (point, version) { - version = version || apiVersion; - return `/api/${version}/${point}?apikey=${apiKey}`; -}; diff --git a/src/tests/backend/specs/api/pad.js b/src/tests/backend/specs/api/pad.js index c94ad810d..bb0ecdd9a 100644 --- a/src/tests/backend/specs/api/pad.js +++ b/src/tests/backend/specs/api/pad.js @@ -1,3 +1,5 @@ +'use strict'; + /* * ACHTUNG: there is a copied & modified version of this file in * /src/tests/container/specs/api/pad.js @@ -5,19 +7,19 @@ * TODO: unify those two files, and merge in a single one. */ +const assert = require('assert').strict; const async = require('async'); const common = require('../../common'); -const settings = require('../../../../node/utils/Settings'); -const supertest = require('supertest'); - -const api = supertest(`http://${settings.ip}:${settings.port}`); +let agent; const apiKey = common.apiKey; let apiVersion = 1; const testPadId = makeid(); let lastEdited = ''; const text = generateLongText(); +const endPoint = (point, version) => `/api/${version || apiVersion}/${point}?apikey=${apiKey}`; + /* * Html document with nested lists of different types, to test its import and * verify it is exported back correctly @@ -45,10 +47,12 @@ const ulSpaceHtml = '
    • one
    • const expectedSpaceHtml = '
      • one
      '; describe(__filename, function () { + before(async function () { agent = await common.init(); }); + describe('Connectivity', function () { it('can connect', function (done) { this.timeout(200); - api.get('/api/') + agent.get('/api/') .expect('Content-Type', /json/) .expect(200, done); }); @@ -57,7 +61,7 @@ describe(__filename, function () { describe('API Versioning', function () { it('finds the version tag', function (done) { this.timeout(150); - api.get('/api/') + agent.get('/api/') .expect((res) => { apiVersion = res.body.currentVersion; if (!res.body.currentVersion) throw new Error('No version set in API'); @@ -73,7 +77,7 @@ describe(__filename, function () { // This is broken because Etherpad doesn't handle HTTP codes properly see #2343 // If your APIKey is password you deserve to fail all tests anyway const permErrorURL = `/api/${apiVersion}/createPad?apikey=password&padID=test`; - api.get(permErrorURL) + agent.get(permErrorURL) .expect(401, done); }); }); @@ -123,7 +127,7 @@ describe(__filename, function () { describe('deletePad', function () { it('deletes a Pad', function (done) { this.timeout(150); - api.get(`${endPoint('deletePad')}&padID=${testPadId}`) + agent.get(`${endPoint('deletePad')}&padID=${testPadId}`) .expect('Content-Type', /json/) .expect(200, done); // @TODO: we shouldn't expect 200 here since the pad may not exist }); @@ -132,7 +136,7 @@ describe(__filename, function () { describe('createPad', function () { it('creates a new Pad', function (done) { this.timeout(150); - api.get(`${endPoint('createPad')}&padID=${testPadId}`) + agent.get(`${endPoint('createPad')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Unable to create new Pad'); }) @@ -144,7 +148,7 @@ describe(__filename, function () { describe('getRevisionsCount', function () { it('gets revision count of Pad', function (done) { this.timeout(150); - api.get(`${endPoint('getRevisionsCount')}&padID=${testPadId}`) + agent.get(`${endPoint('getRevisionsCount')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Unable to get Revision Count'); if (res.body.data.revisions !== 0) throw new Error('Incorrect Revision Count'); @@ -157,10 +161,12 @@ describe(__filename, function () { describe('getSavedRevisionsCount', function () { it('gets saved revisions count of Pad', function (done) { this.timeout(150); - api.get(`${endPoint('getSavedRevisionsCount')}&padID=${testPadId}`) + agent.get(`${endPoint('getSavedRevisionsCount')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Unable to get Saved Revisions Count'); - if (res.body.data.savedRevisions !== 0) throw new Error('Incorrect Saved Revisions Count'); + if (res.body.data.savedRevisions !== 0) { + throw new Error('Incorrect Saved Revisions Count'); + } }) .expect('Content-Type', /json/) .expect(200, done); @@ -170,10 +176,10 @@ describe(__filename, function () { describe('listSavedRevisions', function () { it('gets saved revision list of Pad', function (done) { this.timeout(150); - api.get(`${endPoint('listSavedRevisions')}&padID=${testPadId}`) + agent.get(`${endPoint('listSavedRevisions')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Unable to get Saved Revisions List'); - if (!res.body.data.savedRevisions.equals([])) throw new Error('Incorrect Saved Revisions List'); + assert.deepEqual(res.body.data.savedRevisions, []); }) .expect('Content-Type', /json/) .expect(200, done); @@ -183,7 +189,7 @@ describe(__filename, function () { describe('getHTML', function () { it('get the HTML of Pad', function (done) { this.timeout(150); - api.get(`${endPoint('getHTML')}&padID=${testPadId}`) + agent.get(`${endPoint('getHTML')}&padID=${testPadId}`) .expect((res) => { if (res.body.data.html.length <= 1) throw new Error('Unable to get the HTML'); }) @@ -195,7 +201,7 @@ describe(__filename, function () { describe('listAllPads', function () { it('list all pads', function (done) { this.timeout(150); - api.get(endPoint('listAllPads')) + agent.get(endPoint('listAllPads')) .expect((res) => { if (res.body.data.padIDs.includes(testPadId) !== true) { throw new Error('Unable to find pad in pad list'); @@ -209,7 +215,7 @@ describe(__filename, function () { describe('deletePad', function () { it('deletes a Pad', function (done) { this.timeout(150); - api.get(`${endPoint('deletePad')}&padID=${testPadId}`) + agent.get(`${endPoint('deletePad')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Pad Deletion failed'); }) @@ -221,7 +227,7 @@ describe(__filename, function () { describe('listAllPads', function () { it('list all pads', function (done) { this.timeout(150); - api.get(endPoint('listAllPads')) + agent.get(endPoint('listAllPads')) .expect((res) => { if (res.body.data.padIDs.includes(testPadId) !== false) { throw new Error('Test pad should not be in pads list'); @@ -235,7 +241,7 @@ describe(__filename, function () { describe('getHTML', function () { it('get the HTML of a Pad -- Should return a failure', function (done) { this.timeout(150); - api.get(`${endPoint('getHTML')}&padID=${testPadId}`) + agent.get(`${endPoint('getHTML')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 1) throw new Error('Pad deletion failed'); }) @@ -247,7 +253,7 @@ describe(__filename, function () { describe('createPad', function () { it('creates a new Pad with text', function (done) { this.timeout(200); - api.get(`${endPoint('createPad')}&padID=${testPadId}&text=testText`) + agent.get(`${endPoint('createPad')}&padID=${testPadId}&text=testText`) .expect((res) => { if (res.body.code !== 0) throw new Error('Pad Creation failed'); }) @@ -259,7 +265,7 @@ describe(__filename, function () { describe('getText', function () { it('gets the Pad text and expect it to be testText with \n which is a line break', function (done) { this.timeout(150); - api.get(`${endPoint('getText')}&padID=${testPadId}`) + agent.get(`${endPoint('getText')}&padID=${testPadId}`) .expect((res) => { if (res.body.data.text !== 'testText\n') throw new Error('Pad Creation with text'); }) @@ -271,7 +277,7 @@ describe(__filename, function () { describe('setText', function () { it('creates a new Pad with text', function (done) { this.timeout(200); - api.post(endPoint('setText')) + agent.post(endPoint('setText')) .send({ padID: testPadId, text: 'testTextTwo', @@ -287,7 +293,7 @@ describe(__filename, function () { describe('getText', function () { it('gets the Pad text', function (done) { this.timeout(150); - api.get(`${endPoint('getText')}&padID=${testPadId}`) + agent.get(`${endPoint('getText')}&padID=${testPadId}`) .expect((res) => { if (res.body.data.text !== 'testTextTwo\n') throw new Error('Setting Text'); }) @@ -299,7 +305,7 @@ describe(__filename, function () { describe('getRevisionsCount', function () { it('gets Revision Count of a Pad', function (done) { this.timeout(150); - api.get(`${endPoint('getRevisionsCount')}&padID=${testPadId}`) + agent.get(`${endPoint('getRevisionsCount')}&padID=${testPadId}`) .expect((res) => { if (res.body.data.revisions !== 1) throw new Error('Unable to get text revision count'); }) @@ -311,7 +317,7 @@ describe(__filename, function () { describe('saveRevision', function () { it('saves Revision', function (done) { this.timeout(150); - api.get(`${endPoint('saveRevision')}&padID=${testPadId}`) + agent.get(`${endPoint('saveRevision')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Unable to save Revision'); }) @@ -323,10 +329,12 @@ describe(__filename, function () { describe('getSavedRevisionsCount', function () { it('gets saved revisions count of Pad', function (done) { this.timeout(150); - api.get(`${endPoint('getSavedRevisionsCount')}&padID=${testPadId}`) + agent.get(`${endPoint('getSavedRevisionsCount')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Unable to get Saved Revisions Count'); - if (res.body.data.savedRevisions !== 1) throw new Error('Incorrect Saved Revisions Count'); + if (res.body.data.savedRevisions !== 1) { + throw new Error('Incorrect Saved Revisions Count'); + } }) .expect('Content-Type', /json/) .expect(200, done); @@ -336,10 +344,10 @@ describe(__filename, function () { describe('listSavedRevisions', function () { it('gets saved revision list of Pad', function (done) { this.timeout(150); - api.get(`${endPoint('listSavedRevisions')}&padID=${testPadId}`) + agent.get(`${endPoint('listSavedRevisions')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Unable to get Saved Revisions List'); - if (!res.body.data.savedRevisions.equals([1])) throw new Error('Incorrect Saved Revisions List'); + assert.deepEqual(res.body.data.savedRevisions, [1]); }) .expect('Content-Type', /json/) .expect(200, done); @@ -348,7 +356,7 @@ describe(__filename, function () { describe('padUsersCount', function () { it('gets User Count of a Pad', function (done) { this.timeout(150); - api.get(`${endPoint('padUsersCount')}&padID=${testPadId}`) + agent.get(`${endPoint('padUsersCount')}&padID=${testPadId}`) .expect((res) => { if (res.body.data.padUsersCount !== 0) throw new Error('Incorrect Pad User count'); }) @@ -360,7 +368,7 @@ describe(__filename, function () { describe('getReadOnlyID', function () { it('Gets the Read Only ID of a Pad', function (done) { this.timeout(150); - api.get(`${endPoint('getReadOnlyID')}&padID=${testPadId}`) + agent.get(`${endPoint('getReadOnlyID')}&padID=${testPadId}`) .expect((res) => { if (!res.body.data.readOnlyID) throw new Error('No Read Only ID for Pad'); }) @@ -372,9 +380,11 @@ describe(__filename, function () { describe('listAuthorsOfPad', function () { it('Get Authors of the Pad', function (done) { this.timeout(150); - api.get(`${endPoint('listAuthorsOfPad')}&padID=${testPadId}`) + agent.get(`${endPoint('listAuthorsOfPad')}&padID=${testPadId}`) .expect((res) => { - if (res.body.data.authorIDs.length !== 0) throw new Error('# of Authors of pad is not 0'); + if (res.body.data.authorIDs.length !== 0) { + throw new Error('# of Authors of pad is not 0'); + } }) .expect('Content-Type', /json/) .expect(200, done); @@ -384,7 +394,7 @@ describe(__filename, function () { describe('getLastEdited', function () { it('Get When Pad was left Edited', function (done) { this.timeout(150); - api.get(`${endPoint('getLastEdited')}&padID=${testPadId}`) + agent.get(`${endPoint('getLastEdited')}&padID=${testPadId}`) .expect((res) => { if (!res.body.data.lastEdited) { throw new Error('# of Authors of pad is not 0'); @@ -400,7 +410,7 @@ describe(__filename, function () { describe('setText', function () { it('creates a new Pad with text', function (done) { this.timeout(200); - api.post(endPoint('setText')) + agent.post(endPoint('setText')) .send({ padID: testPadId, text: 'testTextTwo', @@ -416,7 +426,7 @@ describe(__filename, function () { describe('getLastEdited', function () { it('Get When Pad was left Edited', function (done) { this.timeout(150); - api.get(`${endPoint('getLastEdited')}&padID=${testPadId}`) + agent.get(`${endPoint('getLastEdited')}&padID=${testPadId}`) .expect((res) => { if (res.body.data.lastEdited <= lastEdited) { throw new Error('Editing A Pad is not updating when it was last edited'); @@ -430,7 +440,7 @@ describe(__filename, function () { describe('padUsers', function () { it('gets User Count of a Pad', function (done) { this.timeout(150); - api.get(`${endPoint('padUsers')}&padID=${testPadId}`) + agent.get(`${endPoint('padUsers')}&padID=${testPadId}`) .expect((res) => { if (res.body.data.padUsers.length !== 0) throw new Error('Incorrect Pad Users'); }) @@ -442,7 +452,7 @@ describe(__filename, function () { describe('deletePad', function () { it('deletes a Pad', function (done) { this.timeout(150); - api.get(`${endPoint('deletePad')}&padID=${testPadId}`) + agent.get(`${endPoint('deletePad')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Pad Deletion failed'); }) @@ -451,14 +461,13 @@ describe(__filename, function () { }); }); - const originalPadId = testPadId; const newPadId = makeid(); const copiedPadId = makeid(); describe('createPad', function () { it('creates a new Pad with text', function (done) { this.timeout(200); - api.get(`${endPoint('createPad')}&padID=${testPadId}`) + agent.get(`${endPoint('createPad')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Pad Creation failed'); }) @@ -470,7 +479,7 @@ describe(__filename, function () { describe('setText', function () { it('Sets text on a pad Id', function (done) { this.timeout(150); - api.post(`${endPoint('setText')}&padID=${testPadId}`) + agent.post(`${endPoint('setText')}&padID=${testPadId}`) .field({text}) .expect((res) => { if (res.body.code !== 0) throw new Error('Pad Set Text failed'); @@ -483,7 +492,7 @@ describe(__filename, function () { describe('getText', function () { it('Gets text on a pad Id', function (done) { this.timeout(200); - api.get(`${endPoint('getText')}&padID=${testPadId}`) + agent.get(`${endPoint('getText')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Pad Get Text failed'); if (res.body.data.text !== `${text}\n`) throw new Error('Pad Text not set properly'); @@ -496,7 +505,7 @@ describe(__filename, function () { describe('setText', function () { it('Sets text on a pad Id including an explicit newline', function (done) { this.timeout(200); - api.post(`${endPoint('setText')}&padID=${testPadId}`) + agent.post(`${endPoint('setText')}&padID=${testPadId}`) .field({text: `${text}\n`}) .expect((res) => { if (res.body.code !== 0) throw new Error('Pad Set Text failed'); @@ -509,7 +518,7 @@ describe(__filename, function () { describe('getText', function () { it("Gets text on a pad Id and doesn't have an excess newline", function (done) { this.timeout(150); - api.get(`${endPoint('getText')}&padID=${testPadId}`) + agent.get(`${endPoint('getText')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Pad Get Text failed'); if (res.body.data.text !== `${text}\n`) throw new Error('Pad Text not set properly'); @@ -522,7 +531,7 @@ describe(__filename, function () { describe('getLastEdited', function () { it('Gets when pad was last edited', function (done) { this.timeout(150); - api.get(`${endPoint('getLastEdited')}&padID=${testPadId}`) + agent.get(`${endPoint('getLastEdited')}&padID=${testPadId}`) .expect((res) => { if (res.body.lastEdited === 0) throw new Error('Get Last Edited Failed'); }) @@ -534,7 +543,7 @@ describe(__filename, function () { describe('movePad', function () { it('Move a Pad to a different Pad ID', function (done) { this.timeout(200); - api.get(`${endPoint('movePad')}&sourceID=${testPadId}&destinationID=${newPadId}&force=true`) + agent.get(`${endPoint('movePad')}&sourceID=${testPadId}&destinationID=${newPadId}&force=true`) .expect((res) => { if (res.body.code !== 0) throw new Error('Moving Pad Failed'); }) @@ -546,7 +555,7 @@ describe(__filename, function () { describe('getText', function () { it('Gets text on a pad Id', function (done) { this.timeout(150); - api.get(`${endPoint('getText')}&padID=${newPadId}`) + agent.get(`${endPoint('getText')}&padID=${newPadId}`) .expect((res) => { if (res.body.data.text !== `${text}\n`) throw new Error('Pad Get Text failed'); }) @@ -558,7 +567,8 @@ describe(__filename, function () { describe('movePad', function () { it('Move a Pad to a different Pad ID', function (done) { this.timeout(200); - api.get(`${endPoint('movePad')}&sourceID=${newPadId}&destinationID=${testPadId}&force=false`) + agent.get(`${endPoint('movePad')}&sourceID=${newPadId}&destinationID=${testPadId}` + + '&force=false') .expect((res) => { if (res.body.code !== 0) throw new Error('Moving Pad Failed'); }) @@ -570,7 +580,7 @@ describe(__filename, function () { describe('getText', function () { it('Gets text on a pad Id', function (done) { this.timeout(150); - api.get(`${endPoint('getText')}&padID=${testPadId}`) + agent.get(`${endPoint('getText')}&padID=${testPadId}`) .expect((res) => { if (res.body.data.text !== `${text}\n`) throw new Error('Pad Get Text failed'); }) @@ -582,7 +592,7 @@ describe(__filename, function () { describe('getLastEdited', function () { it('Gets when pad was last edited', function (done) { this.timeout(150); - api.get(`${endPoint('getLastEdited')}&padID=${testPadId}`) + agent.get(`${endPoint('getLastEdited')}&padID=${testPadId}`) .expect((res) => { if (res.body.lastEdited === 0) throw new Error('Get Last Edited Failed'); }) @@ -594,7 +604,7 @@ describe(__filename, function () { describe('appendText', function () { it('Append text to a pad Id', function (done) { this.timeout(150); - api.get(`${endPoint('appendText', '1.2.13')}&padID=${testPadId}&text=hello`) + agent.get(`${endPoint('appendText', '1.2.13')}&padID=${testPadId}&text=hello`) .expect((res) => { if (res.body.code !== 0) throw new Error('Pad Append Text failed'); }) @@ -606,10 +616,12 @@ describe(__filename, function () { describe('getText', function () { it('Gets text on a pad Id', function (done) { this.timeout(150); - api.get(`${endPoint('getText')}&padID=${testPadId}`) + agent.get(`${endPoint('getText')}&padID=${testPadId}`) .expect((res) => { if (res.body.code !== 0) throw new Error('Pad Get Text failed'); - if (res.body.data.text !== `${text}hello\n`) throw new Error('Pad Text not set properly'); + if (res.body.data.text !== `${text}hello\n`) { + throw new Error('Pad Text not set properly'); + } }) .expect('Content-Type', /json/) .expect(200, done); @@ -621,13 +633,15 @@ describe(__filename, function () { it('Sets the HTML of a Pad attempting to pass ugly HTML', function (done) { this.timeout(200); const html = '
      Hello HTML
      '; - api.post(endPoint('setHTML')) + agent.post(endPoint('setHTML')) .send({ padID: testPadId, html, }) .expect((res) => { - if (res.body.code !== 0) throw new Error("Crappy HTML Can't be Imported[we weren't able to sanitize it']"); + if (res.body.code !== 0) { + throw new Error("Crappy HTML Can't be Imported[we weren't able to sanitize it']"); + } }) .expect('Content-Type', /json/) .expect(200, done); @@ -637,7 +651,7 @@ describe(__filename, function () { describe('setHTML', function () { it('Sets the HTML of a Pad with complex nested lists of different types', function (done) { this.timeout(200); - api.post(endPoint('setHTML')) + agent.post(endPoint('setHTML')) .send({ padID: testPadId, html: ulHtml, @@ -653,7 +667,7 @@ describe(__filename, function () { describe('getHTML', function () { it('Gets back the HTML of a Pad with complex nested lists of different types', function (done) { this.timeout(150); - api.get(`${endPoint('getHTML')}&padID=${testPadId}`) + agent.get(`${endPoint('getHTML')}&padID=${testPadId}`) .expect((res) => { const receivedHtml = res.body.data.html.replace('
      ', '').toLowerCase(); @@ -677,7 +691,7 @@ describe(__filename, function () { describe('setHTML', function () { it('Sets the HTML of a Pad with white space between list items', function (done) { this.timeout(200); - api.get(`${endPoint('setHTML')}&padID=${testPadId}&html=${ulSpaceHtml}`) + agent.get(`${endPoint('setHTML')}&padID=${testPadId}&html=${ulSpaceHtml}`) .expect((res) => { if (res.body.code !== 0) throw new Error('List HTML cant be imported'); }) @@ -689,7 +703,7 @@ describe(__filename, function () { describe('getHTML', function () { it('Gets back the HTML of a Pad with complex nested lists of different types', function (done) { this.timeout(150); - api.get(`${endPoint('getHTML')}&padID=${testPadId}`) + agent.get(`${endPoint('getHTML')}&padID=${testPadId}`) .expect((res) => { const receivedHtml = res.body.data.html.replace('
      ', '').toLowerCase(); if (receivedHtml !== expectedSpaceHtml) { @@ -716,7 +730,7 @@ describe(__filename, function () { async.map( badUrlChars, (badUrlChar, cb) => { - api.get(`${endPoint('createPad')}&padID=${badUrlChar}`) + agent.get(`${endPoint('createPad')}&padID=${badUrlChar}`) .expect((res) => { if (res.body.code !== 1) throw new Error('Pad with bad characters was created'); }) @@ -730,7 +744,8 @@ describe(__filename, function () { describe('copyPad', function () { it('copies the content of a existent pad', function (done) { this.timeout(200); - api.get(`${endPoint('copyPad')}&sourceID=${testPadId}&destinationID=${copiedPadId}&force=true`) + agent.get(`${endPoint('copyPad')}&sourceID=${testPadId}&destinationID=${copiedPadId}` + + '&force=true') .expect((res) => { if (res.body.code !== 0) throw new Error('Copy Pad Failed'); }) @@ -747,13 +762,14 @@ describe(__filename, function () { createNewPadWithHtml(sourcePadId, ulHtml, done); }); - beforeEach(function () { + beforeEach(async function () { newPad = makeid(); }); it('returns a successful response', function (done) { this.timeout(200); - api.get(`${endPoint('copyPadWithoutHistory')}&sourceID=${sourcePadId}&destinationID=${newPad}&force=false`) + agent.get(`${endPoint('copyPadWithoutHistory')}&sourceID=${sourcePadId}` + + `&destinationID=${newPad}&force=false`) .expect((res) => { if (res.body.code !== 0) throw new Error('Copy Pad Without History Failed'); }) @@ -764,14 +780,16 @@ describe(__filename, function () { // this test validates if the source pad's text and attributes are kept it('creates a new pad with the same content as the source pad', function (done) { this.timeout(200); - api.get(`${endPoint('copyPadWithoutHistory')}&sourceID=${sourcePadId}&destinationID=${newPad}&force=false`) + agent.get(`${endPoint('copyPadWithoutHistory')}&sourceID=${sourcePadId}` + + `&destinationID=${newPad}&force=false`) .expect((res) => { if (res.body.code !== 0) throw new Error('Copy Pad Without History Failed'); }) .end(() => { - api.get(`${endPoint('getHTML')}&padID=${newPad}`) + agent.get(`${endPoint('getHTML')}&padID=${newPad}`) .expect((res) => { - const receivedHtml = res.body.data.html.replace('

      ', '').toLowerCase(); + const receivedHtml = + res.body.data.html.replace('

      ', '').toLowerCase(); if (receivedHtml !== expectedHtml) { throw new Error(`HTML received from export is not the one we were expecting. @@ -794,7 +812,8 @@ describe(__filename, function () { const padWithNonExistentGroup = `notExistentGroup$${padId}`; it('throws an error', function (done) { this.timeout(150); - api.get(`${endPoint('copyPadWithoutHistory')}&sourceID=${sourcePadId}&destinationID=${padWithNonExistentGroup}&force=true`) + agent.get(`${endPoint('copyPadWithoutHistory')}&sourceID=${sourcePadId}&` + + `destinationID=${padWithNonExistentGroup}&force=true`) .expect((res) => { // code 1, it means an error has happened if (res.body.code !== 1) throw new Error('It should report an error'); @@ -813,7 +832,8 @@ describe(__filename, function () { context('and force is false', function () { it('throws an error', function (done) { this.timeout(150); - api.get(`${endPoint('copyPadWithoutHistory')}&sourceID=${sourcePadId}&destinationID=${padIdExistent}&force=false`) + agent.get(`${endPoint('copyPadWithoutHistory')}&sourceID=${sourcePadId}` + + `&destinationID=${padIdExistent}&force=false`) .expect((res) => { // code 1, it means an error has happened if (res.body.code !== 1) throw new Error('It should report an error'); @@ -825,10 +845,13 @@ describe(__filename, function () { context('and force is true', function () { it('returns a successful response', function (done) { this.timeout(200); - api.get(`${endPoint('copyPadWithoutHistory')}&sourceID=${sourcePadId}&destinationID=${padIdExistent}&force=true`) + agent.get(`${endPoint('copyPadWithoutHistory')}&sourceID=${sourcePadId}` + + `&destinationID=${padIdExistent}&force=true`) .expect((res) => { // code 1, it means an error has happened - if (res.body.code !== 0) throw new Error('Copy pad without history with force true failed'); + if (res.body.code !== 0) { + throw new Error('Copy pad without history with force true failed'); + } }) .expect(200, done); }); @@ -842,10 +865,10 @@ describe(__filename, function () { */ -var createNewPadWithHtml = function (padId, html, cb) { - api.get(`${endPoint('createPad')}&padID=${padId}`) +const createNewPadWithHtml = (padId, html, cb) => { + agent.get(`${endPoint('createPad')}&padID=${padId}`) .end(() => { - api.post(endPoint('setHTML')) + agent.post(endPoint('setHTML')) .send({ padID: padId, html, @@ -854,11 +877,6 @@ var createNewPadWithHtml = function (padId, html, cb) { }); }; -var endPoint = function (point, version) { - version = version || apiVersion; - return `/api/${version}/${point}?apikey=${apiKey}`; -}; - function makeid() { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; @@ -878,22 +896,3 @@ function generateLongText() { } return text; } - -// Need this to compare arrays (listSavedRevisions test) -Array.prototype.equals = function (array) { - // if the other array is a falsy value, return - if (!array) return false; - // compare lengths - can save a lot of time - if (this.length != array.length) return false; - for (let i = 0, l = this.length; i < l; i++) { - // Check if we have nested arrays - if (this[i] instanceof Array && array[i] instanceof Array) { - // recurse into the nested arrays - if (!this[i].equals(array[i])) return false; - } else if (this[i] != array[i]) { - // Warning - two different object instances will never be equal: {x:20} != {x:20} - return false; - } - } - return true; -}; diff --git a/src/tests/backend/specs/api/sessionsAndGroups.js b/src/tests/backend/specs/api/sessionsAndGroups.js index c3cc38752..238353d0d 100644 --- a/src/tests/backend/specs/api/sessionsAndGroups.js +++ b/src/tests/backend/specs/api/sessionsAndGroups.js @@ -1,10 +1,9 @@ +'use strict'; + const assert = require('assert').strict; const common = require('../../common'); -const settings = require('../../../../node/utils/Settings'); -const supertest = require('supertest'); - -const api = supertest(`http://${settings.ip}:${settings.port}`); +let agent; const apiKey = common.apiKey; let apiVersion = 1; let groupID = ''; @@ -12,11 +11,15 @@ let authorID = ''; let sessionID = ''; let padID = makeid(); +const endPoint = (point) => `/api/${apiVersion}/${point}?apikey=${apiKey}`; + describe(__filename, function () { + before(async function () { agent = await common.init(); }); + describe('API Versioning', function () { it('errors if can not connect', async function () { this.timeout(200); - await api.get('/api/') + await agent.get('/api/') .expect(200) .expect((res) => { assert(res.body.currentVersion); @@ -58,7 +61,7 @@ describe(__filename, function () { describe('API: Group creation and deletion', function () { it('createGroup', async function () { this.timeout(100); - await api.get(endPoint('createGroup')) + await agent.get(endPoint('createGroup')) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -70,7 +73,7 @@ describe(__filename, function () { it('listSessionsOfGroup for empty group', async function () { this.timeout(100); - await api.get(`${endPoint('listSessionsOfGroup')}&groupID=${groupID}`) + await agent.get(`${endPoint('listSessionsOfGroup')}&groupID=${groupID}`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -81,7 +84,7 @@ describe(__filename, function () { it('deleteGroup', async function () { this.timeout(100); - await api.get(`${endPoint('deleteGroup')}&groupID=${groupID}`) + await agent.get(`${endPoint('deleteGroup')}&groupID=${groupID}`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -91,7 +94,7 @@ describe(__filename, function () { it('createGroupIfNotExistsFor', async function () { this.timeout(100); - await api.get(`${endPoint('createGroupIfNotExistsFor')}&groupMapper=management`) + await agent.get(`${endPoint('createGroupIfNotExistsFor')}&groupMapper=management`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -104,7 +107,7 @@ describe(__filename, function () { // Creates a group, creates 2 sessions, 2 pads and then deletes the group. it('createGroup', async function () { this.timeout(100); - await api.get(endPoint('createGroup')) + await agent.get(endPoint('createGroup')) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -116,7 +119,7 @@ describe(__filename, function () { it('createAuthor', async function () { this.timeout(100); - await api.get(endPoint('createAuthor')) + await agent.get(endPoint('createAuthor')) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -128,8 +131,8 @@ describe(__filename, function () { it('createSession', async function () { this.timeout(100); - await api.get(`${endPoint('createSession') - }&authorID=${authorID}&groupID=${groupID}&validUntil=999999999999`) + await agent.get(`${endPoint('createSession')}&authorID=${authorID}&groupID=${groupID}` + + '&validUntil=999999999999') .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -141,8 +144,8 @@ describe(__filename, function () { it('createSession', async function () { this.timeout(100); - await api.get(`${endPoint('createSession') - }&authorID=${authorID}&groupID=${groupID}&validUntil=999999999999`) + await agent.get(`${endPoint('createSession')}&authorID=${authorID}&groupID=${groupID}` + + '&validUntil=999999999999') .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -154,7 +157,7 @@ describe(__filename, function () { it('createGroupPad', async function () { this.timeout(100); - await api.get(`${endPoint('createGroupPad')}&groupID=${groupID}&padName=x1234567`) + await agent.get(`${endPoint('createGroupPad')}&groupID=${groupID}&padName=x1234567`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -164,7 +167,7 @@ describe(__filename, function () { it('createGroupPad', async function () { this.timeout(100); - await api.get(`${endPoint('createGroupPad')}&groupID=${groupID}&padName=x12345678`) + await agent.get(`${endPoint('createGroupPad')}&groupID=${groupID}&padName=x12345678`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -174,7 +177,7 @@ describe(__filename, function () { it('deleteGroup', async function () { this.timeout(100); - await api.get(`${endPoint('deleteGroup')}&groupID=${groupID}`) + await agent.get(`${endPoint('deleteGroup')}&groupID=${groupID}`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -187,7 +190,7 @@ describe(__filename, function () { describe('API: Author creation', function () { it('createGroup', async function () { this.timeout(100); - await api.get(endPoint('createGroup')) + await agent.get(endPoint('createGroup')) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -199,7 +202,7 @@ describe(__filename, function () { it('createAuthor', async function () { this.timeout(100); - await api.get(endPoint('createAuthor')) + await agent.get(endPoint('createAuthor')) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -210,7 +213,7 @@ describe(__filename, function () { it('createAuthor with name', async function () { this.timeout(100); - await api.get(`${endPoint('createAuthor')}&name=john`) + await agent.get(`${endPoint('createAuthor')}&name=john`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -222,7 +225,7 @@ describe(__filename, function () { it('createAuthorIfNotExistsFor', async function () { this.timeout(100); - await api.get(`${endPoint('createAuthorIfNotExistsFor')}&authorMapper=chris`) + await agent.get(`${endPoint('createAuthorIfNotExistsFor')}&authorMapper=chris`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -233,7 +236,7 @@ describe(__filename, function () { it('getAuthorName', async function () { this.timeout(100); - await api.get(`${endPoint('getAuthorName')}&authorID=${authorID}`) + await agent.get(`${endPoint('getAuthorName')}&authorID=${authorID}`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -246,8 +249,8 @@ describe(__filename, function () { describe('API: Sessions', function () { it('createSession', async function () { this.timeout(100); - await api.get(`${endPoint('createSession') - }&authorID=${authorID}&groupID=${groupID}&validUntil=999999999999`) + await agent.get(`${endPoint('createSession')}&authorID=${authorID}&groupID=${groupID}` + + '&validUntil=999999999999') .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -259,7 +262,7 @@ describe(__filename, function () { it('getSessionInfo', async function () { this.timeout(100); - await api.get(`${endPoint('getSessionInfo')}&sessionID=${sessionID}`) + await agent.get(`${endPoint('getSessionInfo')}&sessionID=${sessionID}`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -272,7 +275,7 @@ describe(__filename, function () { it('listSessionsOfGroup', async function () { this.timeout(100); - await api.get(`${endPoint('listSessionsOfGroup')}&groupID=${groupID}`) + await agent.get(`${endPoint('listSessionsOfGroup')}&groupID=${groupID}`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -283,7 +286,7 @@ describe(__filename, function () { it('deleteSession', async function () { this.timeout(100); - await api.get(`${endPoint('deleteSession')}&sessionID=${sessionID}`) + await agent.get(`${endPoint('deleteSession')}&sessionID=${sessionID}`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -293,7 +296,7 @@ describe(__filename, function () { it('getSessionInfo of deleted session', async function () { this.timeout(100); - await api.get(`${endPoint('getSessionInfo')}&sessionID=${sessionID}`) + await agent.get(`${endPoint('getSessionInfo')}&sessionID=${sessionID}`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -305,7 +308,7 @@ describe(__filename, function () { describe('API: Group pad management', function () { it('listPads', async function () { this.timeout(100); - await api.get(`${endPoint('listPads')}&groupID=${groupID}`) + await agent.get(`${endPoint('listPads')}&groupID=${groupID}`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -316,7 +319,7 @@ describe(__filename, function () { it('createGroupPad', async function () { this.timeout(100); - await api.get(`${endPoint('createGroupPad')}&groupID=${groupID}&padName=${padID}`) + await agent.get(`${endPoint('createGroupPad')}&groupID=${groupID}&padName=${padID}`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -327,7 +330,7 @@ describe(__filename, function () { it('listPads after creating a group pad', async function () { this.timeout(100); - await api.get(`${endPoint('listPads')}&groupID=${groupID}`) + await agent.get(`${endPoint('listPads')}&groupID=${groupID}`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -340,7 +343,7 @@ describe(__filename, function () { describe('API: Pad security', function () { it('getPublicStatus', async function () { this.timeout(100); - await api.get(`${endPoint('getPublicStatus')}&padID=${padID}`) + await agent.get(`${endPoint('getPublicStatus')}&padID=${padID}`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -351,7 +354,7 @@ describe(__filename, function () { it('setPublicStatus', async function () { this.timeout(100); - await api.get(`${endPoint('setPublicStatus')}&padID=${padID}&publicStatus=true`) + await agent.get(`${endPoint('setPublicStatus')}&padID=${padID}&publicStatus=true`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -361,7 +364,7 @@ describe(__filename, function () { it('getPublicStatus after changing public status', async function () { this.timeout(100); - await api.get(`${endPoint('getPublicStatus')}&padID=${padID}`) + await agent.get(`${endPoint('getPublicStatus')}&padID=${padID}`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -378,7 +381,7 @@ describe(__filename, function () { describe('API: Misc', function () { it('listPadsOfAuthor', async function () { this.timeout(100); - await api.get(`${endPoint('listPadsOfAuthor')}&authorID=${authorID}`) + await agent.get(`${endPoint('listPadsOfAuthor')}&authorID=${authorID}`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -389,11 +392,6 @@ describe(__filename, function () { }); }); - -const endPoint = function (point) { - return `/api/${apiVersion}/${point}?apikey=${apiKey}`; -}; - function makeid() { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; diff --git a/src/tests/backend/specs/caching_middleware.js b/src/tests/backend/specs/caching_middleware.js index 151bb6282..a40ada7d8 100644 --- a/src/tests/backend/specs/caching_middleware.js +++ b/src/tests/backend/specs/caching_middleware.js @@ -1,3 +1,5 @@ +'use strict'; + /** * caching_middleware is responsible for serving everything under path `/javascripts/` * That includes packages as defined in `src/node/utils/tar.json` and probably also plugin code @@ -6,7 +8,6 @@ const common = require('../common'); const assert = require('assert').strict; -const url = require('url'); const queryString = require('querystring'); const settings = require('../../../node/utils/Settings'); @@ -23,7 +24,7 @@ let agent; */ function isPlaintextResponse(fileContent, resource) { // callback=require.define&v=1234 - const query = url.parse(resource).query; + const query = (new URL(resource, 'http://localhost')).search.slice(1); // require.define const jsonp = queryString.parse(query).callback; diff --git a/src/tests/frontend/specs/select_formatting_buttons.js b/src/tests/frontend/specs/select_formatting_buttons.js index 63be6f8e2..9d63265d8 100644 --- a/src/tests/frontend/specs/select_formatting_buttons.js +++ b/src/tests/frontend/specs/select_formatting_buttons.js @@ -44,14 +44,14 @@ describe('select formatting buttons when selection has style applied', function const testIfFormattingButtonIsDeselected = function (style) { it(`deselects the ${style} button`, function (done) { - this.timeout(50); + this.timeout(100); helper.waitFor(() => isButtonSelected(style) === false).done(done); }); }; const testIfFormattingButtonIsSelected = function (style) { it(`selects the ${style} button`, function (done) { - this.timeout(50); + this.timeout(100); helper.waitFor(() => isButtonSelected(style)).done(done); }); }; @@ -131,7 +131,7 @@ describe('select formatting buttons when selection has style applied', function context('when user applies a style and the selection does not change', function () { it('selects the style button', async function () { - this.timeout(50); + this.timeout(100); const style = STYLES[0]; // italic applyStyleOnLine(style, FIRST_LINE); await helper.waitForPromise(() => isButtonSelected(style) === true); diff --git a/src/tests/frontend/travis/adminrunner.sh b/src/tests/frontend/travis/adminrunner.sh index da20d2801..8f57ac6fb 100755 --- a/src/tests/frontend/travis/adminrunner.sh +++ b/src/tests/frontend/travis/adminrunner.sh @@ -14,7 +14,7 @@ MY_DIR=$(try cd "${0%/*}" && try pwd -P) || exit 1 try cd "${MY_DIR}/../../../.." log "Assuming src/bin/installDeps.sh has already been run" -node node_modules/ep_etherpad-lite/node/server.js --experimental-worker "${@}" & +node src/node/server.js --experimental-worker "${@}" & ep_pid=$! log "Waiting for Etherpad to accept connections (http://localhost:9001)..." diff --git a/src/tests/frontend/travis/runner.sh b/src/tests/frontend/travis/runner.sh index b19c2873f..5a16ccceb 100755 --- a/src/tests/frontend/travis/runner.sh +++ b/src/tests/frontend/travis/runner.sh @@ -14,7 +14,7 @@ MY_DIR=$(try cd "${0%/*}" && try pwd -P) || exit 1 try cd "${MY_DIR}/../../../.." log "Assuming src/bin/installDeps.sh has already been run" -node node_modules/ep_etherpad-lite/node/server.js --experimental-worker "${@}" & +node src/node/server.js --experimental-worker "${@}" & ep_pid=$! log "Waiting for Etherpad to accept connections (http://localhost:9001)..." diff --git a/src/tests/frontend/travis/runnerBackend.sh b/src/tests/frontend/travis/runnerBackend.sh index 8a2e1bab4..f12ff25c1 100755 --- a/src/tests/frontend/travis/runnerBackend.sh +++ b/src/tests/frontend/travis/runnerBackend.sh @@ -17,8 +17,9 @@ s!"max":[^,]*!"max": 100! s!"points":[^,]*!"points": 1000! ' settings.json.template >settings.json +log "Deprecation notice: runnerBackend.sh - Please use: cd src && npm test" log "Assuming src/bin/installDeps.sh has already been run" -node node_modules/ep_etherpad-lite/node/server.js "${@}" & +node src/node/server.js "${@}" & ep_pid=$! log "Waiting for Etherpad to accept connections (http://localhost:9001)..." diff --git a/src/tests/frontend/travis/runnerLoadTest.sh b/src/tests/frontend/travis/runnerLoadTest.sh index 3e9d7d406..3fce737bc 100755 --- a/src/tests/frontend/travis/runnerLoadTest.sh +++ b/src/tests/frontend/travis/runnerLoadTest.sh @@ -17,7 +17,7 @@ s!"points":[^,]*!"points": 1000! ' settings.json.template >settings.json log "Assuming src/bin/installDeps.sh has already been run" -node node_modules/ep_etherpad-lite/node/server.js "${@}" >/dev/null & +node src/node/server.js "${@}" >/dev/null & ep_pid=$! log "Waiting for Etherpad to accept connections (http://localhost:9001)..." diff --git a/src/web.config b/src/web.config index e057b9198..bd50a60c5 100644 --- a/src/web.config +++ b/src/web.config @@ -2,7 +2,7 @@ - + @@ -10,7 +10,7 @@