mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #3427 from overleaf/jpa-rewite-smoke-tests
[SmokeTests] rewrite GitOrigin-RevId: eda39db6b339d997f5669cb9bfca2aefe7d96699
This commit is contained in:
parent
e8e2264d7d
commit
cb9d207ba0
16 changed files with 470 additions and 314 deletions
|
@ -1,60 +1,17 @@
|
|||
/* eslint-disable
|
||||
handle-callback-err,
|
||||
max-len,
|
||||
no-path-concat,
|
||||
no-unused-vars,
|
||||
node/no-deprecated-api,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let HealthCheckController
|
||||
const Mocha = require('mocha')
|
||||
const Base = require('mocha/lib/reporters/base')
|
||||
const RedisWrapper = require('../../infrastructure/RedisWrapper')
|
||||
const rclient = RedisWrapper.client('health_check')
|
||||
const settings = require('settings-sharelatex')
|
||||
const logger = require('logger-sharelatex')
|
||||
const domain = require('domain')
|
||||
const UserGetter = require('../User/UserGetter')
|
||||
const {
|
||||
SmokeTestFailure,
|
||||
runSmokeTests
|
||||
} = require('./../../../../test/smoke/src/SmokeTests')
|
||||
|
||||
module.exports = HealthCheckController = {
|
||||
module.exports = {
|
||||
check(req, res, next) {
|
||||
if (next == null) {
|
||||
next = function(error) {}
|
||||
}
|
||||
const d = domain.create()
|
||||
d.on('error', error => logger.err({ err: error }, 'error in mocha'))
|
||||
return d.run(function() {
|
||||
const mocha = new Mocha({ reporter: Reporter(res), timeout: 10000 })
|
||||
mocha.addFile('test/smoke/src/SmokeTests.js')
|
||||
return mocha.run(function() {
|
||||
// TODO: combine this with the smoke-test-sharelatex module
|
||||
// we need to clean up all references to the smokeTest module
|
||||
// so it can be garbage collected. The only reference should
|
||||
// be in its parent, when it is loaded by mocha.addFile.
|
||||
const path = require.resolve(
|
||||
__dirname + '/../../../../test/smoke/src/SmokeTests.js'
|
||||
)
|
||||
const smokeTestModule = require.cache[path]
|
||||
if (smokeTestModule != null) {
|
||||
let idx
|
||||
const { parent } = smokeTestModule
|
||||
while ((idx = parent.children.indexOf(smokeTestModule)) !== -1) {
|
||||
parent.children.splice(idx, 1)
|
||||
}
|
||||
} else {
|
||||
logger.warn({ path }, 'smokeTestModule not defined')
|
||||
}
|
||||
// remove the smokeTest from the module cache
|
||||
return delete require.cache[path]
|
||||
})
|
||||
})
|
||||
// detach from express for cleaner stack traces
|
||||
setTimeout(() => runSmokeTestsDetached(req, res).catch(next))
|
||||
},
|
||||
|
||||
checkActiveHandles(req, res, next) {
|
||||
|
@ -129,38 +86,34 @@ module.exports = HealthCheckController = {
|
|||
}
|
||||
}
|
||||
|
||||
var Reporter = res =>
|
||||
function(runner) {
|
||||
Base.call(this, runner)
|
||||
|
||||
const tests = []
|
||||
const passes = []
|
||||
const failures = []
|
||||
|
||||
runner.on('test end', test => tests.push(test))
|
||||
runner.on('pass', test => passes.push(test))
|
||||
runner.on('fail', test => failures.push(test))
|
||||
|
||||
return runner.on('end', () => {
|
||||
const clean = test => ({
|
||||
title: test.fullTitle(),
|
||||
duration: test.duration,
|
||||
err: test.err,
|
||||
timedOut: test.timedOut
|
||||
})
|
||||
|
||||
const results = {
|
||||
stats: this.stats,
|
||||
failures: failures.map(clean),
|
||||
passes: passes.map(clean)
|
||||
}
|
||||
|
||||
res.contentType('application/json')
|
||||
if (failures.length > 0) {
|
||||
logger.err({ failures }, 'health check failed')
|
||||
return res.status(500).send(JSON.stringify(results, null, 2))
|
||||
} else {
|
||||
return res.status(200).send(JSON.stringify(results, null, 2))
|
||||
}
|
||||
})
|
||||
function prettyJSON(blob) {
|
||||
return JSON.stringify(blob, null, 2) + '\n'
|
||||
}
|
||||
async function runSmokeTestsDetached(req, res) {
|
||||
function isAborted() {
|
||||
return req.aborted
|
||||
}
|
||||
const stats = { start: new Date(), steps: [] }
|
||||
let status, response
|
||||
try {
|
||||
try {
|
||||
await runSmokeTests({ isAborted, stats })
|
||||
} finally {
|
||||
stats.end = new Date()
|
||||
stats.duration = stats.end - stats.start
|
||||
}
|
||||
status = 200
|
||||
response = { stats }
|
||||
} catch (e) {
|
||||
let err = e
|
||||
if (!(e instanceof SmokeTestFailure)) {
|
||||
err = new SmokeTestFailure('low level error', {}, e)
|
||||
}
|
||||
logger.err({ err, stats }, 'health check failed')
|
||||
status = 500
|
||||
response = { stats, error: err.message }
|
||||
}
|
||||
if (isAborted()) return
|
||||
res.contentType('application/json')
|
||||
res.status(status).send(prettyJSON(response))
|
||||
}
|
||||
|
|
|
@ -508,6 +508,7 @@ module.exports = settings =
|
|||
password: process.env['SMOKE_TEST_PASSWORD']
|
||||
projectId: process.env['SMOKE_TEST_PROJECT_ID']
|
||||
rateLimitSubject: process.env['SMOKE_TEST_RATE_LIMIT_SUBJECT'] or "127.0.0.1"
|
||||
stepTimeout: parseInt(process.env['SMOKE_TEST_STEP_TIMEOUT'] or "10000", 10)
|
||||
|
||||
appName: process.env['APP_NAME'] or "ShareLaTeX (Community Edition)"
|
||||
|
||||
|
|
65
services/web/package-lock.json
generated
65
services/web/package-lock.json
generated
|
@ -3836,9 +3836,9 @@
|
|||
}
|
||||
},
|
||||
"@overleaf/o-error": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@overleaf/o-error/-/o-error-3.1.0.tgz",
|
||||
"integrity": "sha512-TWJ80ozJ1LeugGTJyGQSPEuTkZ9LqZD7/ndLE6azKa03SU/mKV/FINcfk8atpVil8iv1hHQwzYZc35klplpMpQ=="
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@overleaf/o-error/-/o-error-3.2.0.tgz",
|
||||
"integrity": "sha512-H2HjuzoRBKNDy3pEZnVt+Pa6gPZPEENALXzvJy99ijOE5z6iirUkp2EMP/NTPfvyfUPz32cY04Jn10jjVowerg=="
|
||||
},
|
||||
"@overleaf/redis-wrapper": {
|
||||
"version": "2.0.0",
|
||||
|
@ -9825,6 +9825,7 @@
|
|||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
|
@ -11991,7 +11992,8 @@
|
|||
"browser-stdout": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
|
||||
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="
|
||||
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
|
||||
"dev": true
|
||||
},
|
||||
"browserify-aes": {
|
||||
"version": "1.2.0",
|
||||
|
@ -15074,7 +15076,8 @@
|
|||
"diff": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
|
||||
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA=="
|
||||
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
|
||||
"dev": true
|
||||
},
|
||||
"diffie-hellman": {
|
||||
"version": "5.0.3",
|
||||
|
@ -17578,6 +17581,7 @@
|
|||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz",
|
||||
"integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-buffer": "~2.0.3"
|
||||
},
|
||||
|
@ -17585,7 +17589,8 @@
|
|||
"is-buffer": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
|
||||
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
|
||||
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -19178,7 +19183,8 @@
|
|||
"growl": {
|
||||
"version": "1.10.5",
|
||||
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
|
||||
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA=="
|
||||
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
|
||||
"dev": true
|
||||
},
|
||||
"gtoken": {
|
||||
"version": "5.1.0",
|
||||
|
@ -19593,7 +19599,8 @@
|
|||
"he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"dev": true
|
||||
},
|
||||
"helmet": {
|
||||
"version": "3.22.0",
|
||||
|
@ -20997,7 +21004,8 @@
|
|||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"isobject": {
|
||||
"version": "4.0.0",
|
||||
|
@ -21566,6 +21574,7 @@
|
|||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
|
||||
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
|
@ -22949,6 +22958,7 @@
|
|||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
|
||||
"integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^2.0.1"
|
||||
},
|
||||
|
@ -22957,6 +22967,7 @@
|
|||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
|
@ -22965,6 +22976,7 @@
|
|||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
|
||||
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
|
@ -22974,12 +22986,14 @@
|
|||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
|
||||
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
|
@ -23942,6 +23956,7 @@
|
|||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz",
|
||||
"integrity": "sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-colors": "3.2.3",
|
||||
"browser-stdout": "1.3.1",
|
||||
|
@ -23971,17 +23986,20 @@
|
|||
"ansi-colors": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz",
|
||||
"integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw=="
|
||||
"integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==",
|
||||
"dev": true
|
||||
},
|
||||
"camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
|
@ -23990,6 +24008,7 @@
|
|||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
|
||||
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"locate-path": "^3.0.0"
|
||||
}
|
||||
|
@ -23998,6 +24017,7 @@
|
|||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
|
||||
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
|
@ -24011,6 +24031,7 @@
|
|||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-locate": "^3.0.0",
|
||||
"path-exists": "^3.0.0"
|
||||
|
@ -24020,6 +24041,7 @@
|
|||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
@ -24027,12 +24049,14 @@
|
|||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q=="
|
||||
"integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
|
@ -24040,12 +24064,14 @@
|
|||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
|
||||
"dev": true
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
|
||||
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-limit": "^2.0.0"
|
||||
}
|
||||
|
@ -24054,6 +24080,7 @@
|
|||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
|
||||
"integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
|
@ -24062,6 +24089,7 @@
|
|||
"version": "13.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
|
||||
"integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
|
@ -24570,6 +24598,7 @@
|
|||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz",
|
||||
"integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"object.getownpropertydescriptors": "^2.0.3",
|
||||
"semver": "^5.7.0"
|
||||
|
@ -24578,7 +24607,8 @@
|
|||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -33024,7 +33054,8 @@
|
|||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"dev": true
|
||||
},
|
||||
"srcset": {
|
||||
"version": "2.0.1",
|
||||
|
@ -37709,6 +37740,7 @@
|
|||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
|
@ -38149,6 +38181,7 @@
|
|||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz",
|
||||
"integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"flat": "^4.1.0",
|
||||
"lodash": "^4.17.15",
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
"@babel/preset-env": "^7.9.5",
|
||||
"@babel/preset-react": "^7.9.4",
|
||||
"@overleaf/metrics": "^3.4.1",
|
||||
"@overleaf/o-error": "^3.1.0",
|
||||
"@overleaf/o-error": "^3.2.0",
|
||||
"@overleaf/redis-wrapper": "^2.0.0",
|
||||
"@pollyjs/adapter-node-http": "^4.2.1",
|
||||
"@pollyjs/core": "^4.2.1",
|
||||
|
@ -100,7 +100,6 @@
|
|||
"method-override": "^2.3.3",
|
||||
"minimist": "1.2.5",
|
||||
"mmmagic": "^0.5.2",
|
||||
"mocha": "^6.2.2",
|
||||
"moment": "^2.24.0",
|
||||
"mongodb": "^3.6.0",
|
||||
"mongoose": "^5.10.7",
|
||||
|
@ -209,6 +208,7 @@
|
|||
"less-plugin-autoprefix": "^2.0.0",
|
||||
"mini-css-extract-plugin": "^0.8.0",
|
||||
"mkdirp": "0.5.1",
|
||||
"mocha": "^6.2.2",
|
||||
"mock-fs": "^4.11.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"nodemon": "^1.14.3",
|
||||
|
|
|
@ -19,11 +19,20 @@ describe('HealthCheckController', function() {
|
|||
})
|
||||
|
||||
async function performSmokeTestRequest() {
|
||||
const start = Date.now()
|
||||
const { response, body } = await user.doRequest('GET', {
|
||||
url: '/health_check/full',
|
||||
json: true
|
||||
})
|
||||
const end = Date.now()
|
||||
|
||||
expect(body).to.exist
|
||||
expect(body.stats).to.exist
|
||||
expect(Date.parse(body.stats.start)).to.be.within(start, start + 1000)
|
||||
expect(Date.parse(body.stats.end)).to.be.within(end - 1000, end)
|
||||
|
||||
expect(body.stats.duration).to.be.within(0, 10000)
|
||||
expect(body.stats.steps).to.be.instanceof(Array)
|
||||
return { response, body }
|
||||
}
|
||||
|
||||
|
@ -36,13 +45,30 @@ describe('HealthCheckController', function() {
|
|||
})
|
||||
})
|
||||
|
||||
describe('when the request is aborted', function() {
|
||||
it('should not crash', async function() {
|
||||
try {
|
||||
await user.doRequest('GET', {
|
||||
timeout: 1,
|
||||
url: '/health_check/full',
|
||||
json: true
|
||||
})
|
||||
} catch (err) {
|
||||
expect(err.code).to.equal('ESOCKETTIMEDOUT')
|
||||
return
|
||||
}
|
||||
expect.fail('expected request to fail with timeout error')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the project does not exist', function() {
|
||||
beforeEach(function() {
|
||||
Settings.smokeTest.projectId = '404'
|
||||
})
|
||||
it('should respond with a 500 ', async function() {
|
||||
const { response } = await performSmokeTestRequest()
|
||||
const { response, body } = await performSmokeTestRequest()
|
||||
|
||||
expect(body.error).to.equal('run.101_loadEditor failed')
|
||||
expect(response.statusCode).to.equal(500)
|
||||
})
|
||||
})
|
||||
|
@ -52,8 +78,9 @@ describe('HealthCheckController', function() {
|
|||
Settings.smokeTest.password = 'foo-bar'
|
||||
})
|
||||
it('should respond with a 500 with mismatching password', async function() {
|
||||
const { response } = await performSmokeTestRequest()
|
||||
const { response, body } = await performSmokeTestRequest()
|
||||
|
||||
expect(body.error).to.equal('run.002_login failed')
|
||||
expect(response.statusCode).to.equal(500)
|
||||
})
|
||||
})
|
||||
|
|
39
services/web/test/smoke/src/README.md
Normal file
39
services/web/test/smoke/src/README.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
# SmokeTests
|
||||
|
||||
For the SmokeTests we implemented a Mini-Framework that is tailored for our
|
||||
tooling, specifically OError, and does not need a large runner, such as mocha.
|
||||
|
||||
The SmokeTests are separated into individual `steps`.
|
||||
Each `step` can have a `run` function and a `cleanup` function.
|
||||
The former will run in sequence with the other steps, the later in reverse
|
||||
order from the finish, or the last failure.
|
||||
|
||||
```js
|
||||
async function run(ctx) {
|
||||
// do something
|
||||
}
|
||||
async function cleanup(ctx) {
|
||||
// cleanup something
|
||||
}
|
||||
module.exports = { cleanup, run }
|
||||
```
|
||||
|
||||
Steps will get called with a context object with common helpers and details:
|
||||
- `request` a promisified `request` module with defaults for `baseUrl`,
|
||||
`timeout` and internals for cookie handling.
|
||||
- `assertHasStatusCode` a helper for asserting response status codes, pass
|
||||
a response and desired status code. It will throw with OError context set.
|
||||
- `getCsrfTokenFor` a helper for retrieving CSRF tokens, pass an endpoint.
|
||||
- `processWithTimeout` a helper for awaiting Promises with a timeout, pass
|
||||
`{ work: Promise.resolve(), timeout: 42, message: 'foo timedout' }`
|
||||
- `stats` an object for performance tracking.
|
||||
- `timeout` the step timeout
|
||||
|
||||
Steps should handle timeouts locally to ensure appropriate cleanup of timed out
|
||||
actions.
|
||||
|
||||
Steps may pass values along to the next steps in returning an object with the
|
||||
desired fields from the `run` or `cleanup` function.
|
||||
The returned values will overwrite existing details in the `ctx`.
|
||||
|
||||
Alpha-numeric sorting of step filenames determines the processing sequence.
|
|
@ -1,215 +1,95 @@
|
|||
/* eslint-disable
|
||||
max-len,
|
||||
no-unused-vars,
|
||||
no-useless-escape,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const child = require('child_process')
|
||||
let fs = require('fs')
|
||||
const assert = require('assert')
|
||||
const chai = require('chai')
|
||||
if (Object.prototype.should == null) {
|
||||
chai.should()
|
||||
}
|
||||
const { expect } = chai
|
||||
const fs = require('fs')
|
||||
const Path = require('path')
|
||||
|
||||
const Settings = require('settings-sharelatex')
|
||||
let ownPort = Settings.internal.web.port || Settings.port || 3000
|
||||
const port = (Settings.web && Settings.web.web_router_port) || ownPort // send requests to web router if this is the api process
|
||||
const cookeFilePath = `/tmp/smoke-test-cookie-${ownPort}-to-${port}.txt`
|
||||
const buildUrl = path =>
|
||||
` -b ${cookeFilePath} --resolve 'smoke${
|
||||
Settings.cookieDomain
|
||||
}:${port}:127.0.0.1' http://smoke${Settings.cookieDomain}:${port}/${path}`
|
||||
const logger = require('logger-sharelatex')
|
||||
const LoginRateLimiter = require('../../../app/src/Features/Security/LoginRateLimiter.js')
|
||||
const RateLimiter = require('../../../app/src/infrastructure/RateLimiter.js')
|
||||
const { getCsrfTokenForFactory } = require('./support/Csrf')
|
||||
const { SmokeTestFailure } = require('./support/Errors')
|
||||
const {
|
||||
requestFactory,
|
||||
assertHasStatusCode
|
||||
} = require('./support/requestHelper')
|
||||
const { processWithTimeout } = require('./support/timeoutHelper')
|
||||
|
||||
// Change cookie to be non secure so curl will send it
|
||||
const convertCookieFile = function(callback) {
|
||||
fs = require('fs')
|
||||
return fs.readFile(cookeFilePath, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
const firstTrue = data.indexOf('TRUE')
|
||||
const secondTrue = data.indexOf('TRUE', firstTrue + 4)
|
||||
const result =
|
||||
data.slice(0, secondTrue) + 'FALSE' + data.slice(secondTrue + 4)
|
||||
return fs.writeFile(cookeFilePath, result, 'utf8', err => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
return callback()
|
||||
})
|
||||
const STEP_TIMEOUT = Settings.smokeTest.stepTimeout
|
||||
|
||||
const PATH_STEPS = Path.join(__dirname, './steps')
|
||||
const STEPS = fs
|
||||
.readdirSync(PATH_STEPS)
|
||||
.sort()
|
||||
.map(name => {
|
||||
const step = require(Path.join(PATH_STEPS, name))
|
||||
step.name = Path.basename(name, '.js')
|
||||
return step
|
||||
})
|
||||
|
||||
async function runSmokeTests({ isAborted, stats }) {
|
||||
let lastStep = stats.start
|
||||
function completeStep(key) {
|
||||
const step = Date.now()
|
||||
stats.steps.push({ [key]: step - lastStep })
|
||||
lastStep = step
|
||||
}
|
||||
|
||||
const request = requestFactory({ timeout: STEP_TIMEOUT })
|
||||
const getCsrfTokenFor = getCsrfTokenForFactory({ request })
|
||||
const ctx = {
|
||||
assertHasStatusCode,
|
||||
getCsrfTokenFor,
|
||||
processWithTimeout,
|
||||
request,
|
||||
stats,
|
||||
timeout: STEP_TIMEOUT
|
||||
}
|
||||
const cleanupSteps = []
|
||||
|
||||
async function runAndTrack(id, fn) {
|
||||
let result
|
||||
try {
|
||||
result = await fn(ctx)
|
||||
} catch (e) {
|
||||
throw new SmokeTestFailure(`${id} failed`, {}, e)
|
||||
} finally {
|
||||
completeStep(id)
|
||||
}
|
||||
Object.assign(ctx, result)
|
||||
}
|
||||
|
||||
completeStep('init')
|
||||
|
||||
let err
|
||||
try {
|
||||
for (const step of STEPS) {
|
||||
if (isAborted()) break
|
||||
|
||||
const { name, run, cleanup } = step
|
||||
if (cleanup) cleanupSteps.unshift({ name, cleanup })
|
||||
|
||||
await runAndTrack(`run.${name}`, run)
|
||||
}
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
|
||||
const cleanupErrors = []
|
||||
for (const step of cleanupSteps) {
|
||||
const { name, cleanup } = step
|
||||
|
||||
try {
|
||||
await runAndTrack(`cleanup.${name}`, cleanup)
|
||||
} catch (e) {
|
||||
// keep going with cleanup
|
||||
cleanupErrors.push(e)
|
||||
}
|
||||
}
|
||||
|
||||
if (err) throw err
|
||||
if (cleanupErrors.length) {
|
||||
if (cleanupErrors.length === 1) throw cleanupErrors[0]
|
||||
throw new SmokeTestFailure('multiple cleanup steps failed', {
|
||||
stats,
|
||||
cleanupErrors
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
describe('Opening', function() {
|
||||
before(function(done) {
|
||||
logger.log('smoke test: setup')
|
||||
LoginRateLimiter.recordSuccessfulLogin(Settings.smokeTest.user, err => {
|
||||
if (err != null) {
|
||||
logger.err({ err }, 'smoke test: error recoring successful login')
|
||||
return done(err)
|
||||
}
|
||||
return RateLimiter.clearRateLimit(
|
||||
'open-project',
|
||||
`${Settings.smokeTest.projectId}:${Settings.smokeTest.userId}`,
|
||||
err => {
|
||||
if (err != null) {
|
||||
logger.err(
|
||||
{ err },
|
||||
'smoke test: error clearing open-project rate limit'
|
||||
)
|
||||
return done(err)
|
||||
}
|
||||
return RateLimiter.clearRateLimit(
|
||||
'overleaf-login',
|
||||
Settings.smokeTest.rateLimitSubject,
|
||||
err => {
|
||||
if (err != null) {
|
||||
logger.err(
|
||||
{ err },
|
||||
'smoke test: error clearing overleaf-login rate limit'
|
||||
)
|
||||
return done(err)
|
||||
}
|
||||
return done()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
before(function(done) {
|
||||
logger.log('smoke test: hitting dev/csrf')
|
||||
let command = `\
|
||||
curl -H "X-Forwarded-Proto: https" -c ${cookeFilePath} ${buildUrl('dev/csrf')}\
|
||||
`
|
||||
child.exec(command, (err, stdout, stderr) => {
|
||||
if (err != null) {
|
||||
done(err)
|
||||
}
|
||||
const csrf = stdout
|
||||
logger.log('smoke test: converting cookie file 1')
|
||||
return convertCookieFile(err => {
|
||||
if (err != null) {
|
||||
return done(err)
|
||||
}
|
||||
logger.log('smoke test: hitting /login with csrf')
|
||||
command = `\
|
||||
curl -c ${cookeFilePath} -H "Content-Type: application/json" -H "X-Forwarded-Proto: https" -d '{"_csrf":"${csrf}", "email":"${
|
||||
Settings.smokeTest.user
|
||||
}", "password":"${Settings.smokeTest.password}"}' ${buildUrl('login')}\
|
||||
`
|
||||
return child.exec(command, err => {
|
||||
if (err != null) {
|
||||
return done(err)
|
||||
}
|
||||
logger.log('smoke test: finishing setup')
|
||||
return convertCookieFile(done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
after(function(done) {
|
||||
logger.log('smoke test: converting cookie file 2')
|
||||
convertCookieFile(err => {
|
||||
if (err != null) {
|
||||
return done(err)
|
||||
}
|
||||
logger.log('smoke test: cleaning up')
|
||||
let command = `\
|
||||
curl -H "X-Forwarded-Proto: https" -c ${cookeFilePath} ${buildUrl('dev/csrf')}\
|
||||
`
|
||||
return child.exec(command, (err, stdout, stderr) => {
|
||||
if (err != null) {
|
||||
done(err)
|
||||
}
|
||||
const csrf = stdout
|
||||
logger.log('smoke test: converting cookie file 3')
|
||||
return convertCookieFile(err => {
|
||||
if (err != null) {
|
||||
return done(err)
|
||||
}
|
||||
command = `\
|
||||
curl -H "Content-Type: application/json" -H "X-Forwarded-Proto: https" -d '{"_csrf":"${csrf}"}' -c ${cookeFilePath} ${buildUrl(
|
||||
'logout'
|
||||
)}\
|
||||
`
|
||||
return child.exec(command, (err, stdout, stderr) => {
|
||||
if (err != null) {
|
||||
return done(err)
|
||||
}
|
||||
return fs.unlink(cookeFilePath, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('a project', function(done) {
|
||||
logger.log('smoke test: Checking can load a project')
|
||||
this.timeout(4000)
|
||||
const command = `\
|
||||
curl -H "X-Forwarded-Proto: https" -v ${buildUrl(
|
||||
`project/${Settings.smokeTest.projectId}`
|
||||
)}\
|
||||
`
|
||||
return child.exec(command, (error, stdout, stderr) => {
|
||||
expect(error, 'smoke test: error in getting project').to.not.exist
|
||||
|
||||
const statusCodeMatch = !!stderr.match('200 OK')
|
||||
expect(
|
||||
statusCodeMatch,
|
||||
'smoke test: response code is not 200 getting project'
|
||||
).to.equal(true)
|
||||
|
||||
// Check that the project id is present in the javascript that loads up the project
|
||||
const match = !!stdout.match(
|
||||
`window.project_id = \"${Settings.smokeTest.projectId}\"`
|
||||
)
|
||||
expect(
|
||||
match,
|
||||
'smoke test: project page html does not have project_id'
|
||||
).to.equal(true)
|
||||
return done()
|
||||
})
|
||||
})
|
||||
|
||||
it('the project list', function(done) {
|
||||
logger.log('smoke test: Checking can load project list')
|
||||
this.timeout(4000)
|
||||
const command = `\
|
||||
curl -H "X-Forwarded-Proto: https" -v ${buildUrl('project')}\
|
||||
`
|
||||
return child.exec(command, (error, stdout, stderr) => {
|
||||
expect(error, 'smoke test: error returned in getting project list').to.not
|
||||
.exist
|
||||
expect(
|
||||
!!stderr.match('200 OK'),
|
||||
'smoke test: response code is not 200 getting project list'
|
||||
).to.equal(true)
|
||||
expect(
|
||||
!!stdout.match(
|
||||
'<title>Your Projects - .*, Online LaTeX Editor</title>'
|
||||
),
|
||||
'smoke test: body does not have correct title'
|
||||
).to.equal(true)
|
||||
expect(
|
||||
!!stdout.match('ProjectPageController'),
|
||||
'smoke test: body does not have correct angular controller'
|
||||
).to.equal(true)
|
||||
return done()
|
||||
})
|
||||
})
|
||||
})
|
||||
module.exports = { runSmokeTests, SmokeTestFailure }
|
||||
|
|
7
services/web/test/smoke/src/steps/000_getLoginCsrf.js
Normal file
7
services/web/test/smoke/src/steps/000_getLoginCsrf.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
async function run({ getCsrfTokenFor }) {
|
||||
const loginCsrfToken = await getCsrfTokenFor('/login')
|
||||
|
||||
return { loginCsrfToken }
|
||||
}
|
||||
|
||||
module.exports = { run }
|
36
services/web/test/smoke/src/steps/001_clearRateLimits.js
Normal file
36
services/web/test/smoke/src/steps/001_clearRateLimits.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
const OError = require('@overleaf/o-error')
|
||||
const Settings = require('settings-sharelatex')
|
||||
const RateLimiter = require('../../../../app/src/infrastructure/RateLimiter')
|
||||
|
||||
async function clearRateLimit(endpointName, subject) {
|
||||
try {
|
||||
await RateLimiter.promises.clearRateLimit(endpointName, subject)
|
||||
} catch (err) {
|
||||
throw new OError(
|
||||
'error clearing rate limit',
|
||||
{ endpointName, subject },
|
||||
err
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function clearLoginRateLimit() {
|
||||
await clearRateLimit('login', Settings.smokeTest.user)
|
||||
}
|
||||
|
||||
async function clearOpenProjectRateLimit() {
|
||||
await clearRateLimit(
|
||||
'open-project',
|
||||
`${Settings.smokeTest.projectId}:${Settings.smokeTest.userId}`
|
||||
)
|
||||
}
|
||||
|
||||
async function run({ processWithTimeout, timeout }) {
|
||||
await processWithTimeout({
|
||||
work: Promise.all([clearLoginRateLimit(), clearOpenProjectRateLimit()]),
|
||||
timeout,
|
||||
message: 'cleanupRateLimits timed out'
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { run }
|
35
services/web/test/smoke/src/steps/002_login.js
Normal file
35
services/web/test/smoke/src/steps/002_login.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
const Settings = require('settings-sharelatex')
|
||||
|
||||
async function run({ assertHasStatusCode, loginCsrfToken, request }) {
|
||||
const response = await request('/login', {
|
||||
method: 'POST',
|
||||
json: {
|
||||
_csrf: loginCsrfToken,
|
||||
email: Settings.smokeTest.user,
|
||||
password: Settings.smokeTest.password
|
||||
}
|
||||
})
|
||||
|
||||
const body = response.body
|
||||
// login success and login failure both receive a status code of 200
|
||||
// see the frontend logic on how to handle the response:
|
||||
// frontend/js/directives/asyncForm.js -> submitRequest
|
||||
if (body && body.message && body.message.type === 'error') {
|
||||
throw new Error(`login failed: ${body.message.text}`)
|
||||
}
|
||||
|
||||
assertHasStatusCode(response, 200)
|
||||
}
|
||||
|
||||
async function cleanup({ assertHasStatusCode, getCsrfTokenFor, request }) {
|
||||
const logoutCsrfToken = await getCsrfTokenFor('/logout')
|
||||
const response = await request('/logout', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-Token': logoutCsrfToken
|
||||
}
|
||||
})
|
||||
assertHasStatusCode(response, 302)
|
||||
}
|
||||
|
||||
module.exports = { cleanup, run }
|
|
@ -0,0 +1,17 @@
|
|||
const ANGULAR_PROJECT_CONTROLLER_REGEX = /controller="ProjectPageController"/
|
||||
const TITLE_REGEX = /<title>Your Projects - .*, Online LaTeX Editor<\/title>/
|
||||
|
||||
async function run({ request, assertHasStatusCode }) {
|
||||
const response = await request('/project')
|
||||
|
||||
assertHasStatusCode(response, 200)
|
||||
|
||||
if (!TITLE_REGEX.test(response.body)) {
|
||||
throw new Error('body does not have correct title')
|
||||
}
|
||||
if (!ANGULAR_PROJECT_CONTROLLER_REGEX.test(response.body)) {
|
||||
throw new Error('body does not have correct angular controller')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { run }
|
16
services/web/test/smoke/src/steps/101_loadEditor.js
Normal file
16
services/web/test/smoke/src/steps/101_loadEditor.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
const Settings = require('settings-sharelatex')
|
||||
|
||||
async function run({ assertHasStatusCode, request }) {
|
||||
const response = await request(`/project/${Settings.smokeTest.projectId}`)
|
||||
|
||||
assertHasStatusCode(response, 200)
|
||||
|
||||
const PROJECT_ID_REGEX = new RegExp(
|
||||
`window.project_id = "${Settings.smokeTest.projectId}"`
|
||||
)
|
||||
if (!PROJECT_ID_REGEX.test(response.body)) {
|
||||
throw new Error('project page html does not have project_id')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { run }
|
27
services/web/test/smoke/src/support/Csrf.js
Normal file
27
services/web/test/smoke/src/support/Csrf.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
const OError = require('@overleaf/o-error')
|
||||
const { assertHasStatusCode } = require('./requestHelper')
|
||||
const CSRF_REGEX = /window.csrfToken = "(.+?)"/
|
||||
|
||||
function _parseCsrf(body) {
|
||||
const match = CSRF_REGEX.exec(body)
|
||||
if (!match) {
|
||||
throw new Error('Cannot find csrfToken in HTML')
|
||||
}
|
||||
return match[1]
|
||||
}
|
||||
|
||||
function getCsrfTokenForFactory({ request }) {
|
||||
return async function getCsrfTokenFor(endpoint) {
|
||||
try {
|
||||
const response = await request(endpoint)
|
||||
assertHasStatusCode(response, 200)
|
||||
return _parseCsrf(response.body)
|
||||
} catch (err) {
|
||||
throw new OError(`error fetching csrf token on ${endpoint}`, {}, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getCsrfTokenForFactory
|
||||
}
|
7
services/web/test/smoke/src/support/Errors.js
Normal file
7
services/web/test/smoke/src/support/Errors.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
const OError = require('@overleaf/o-error')
|
||||
|
||||
class SmokeTestFailure extends OError {}
|
||||
|
||||
module.exports = {
|
||||
SmokeTestFailure
|
||||
}
|
60
services/web/test/smoke/src/support/requestHelper.js
Normal file
60
services/web/test/smoke/src/support/requestHelper.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
const { Agent } = require('http')
|
||||
const { createConnection } = require('net')
|
||||
const { promisify } = require('util')
|
||||
|
||||
const OError = require('@overleaf/o-error')
|
||||
const request = require('request')
|
||||
const Settings = require('settings-sharelatex')
|
||||
|
||||
// send requests to web router if this is the api process
|
||||
const OWN_PORT = Settings.port || Settings.internal.web.port || 3000
|
||||
const PORT = (Settings.web && Settings.web.web_router_port) || OWN_PORT
|
||||
|
||||
// like the curl option `--resolve DOMAIN:PORT:127.0.0.1`
|
||||
class LocalhostAgent extends Agent {
|
||||
createConnection(options, callback) {
|
||||
return createConnection(PORT, '127.0.0.1', callback)
|
||||
}
|
||||
}
|
||||
|
||||
// degrade the 'HttpOnly; Secure;' flags of the cookie
|
||||
class InsecureCookieJar extends request.jar().constructor {
|
||||
setCookie(...args) {
|
||||
const cookie = super.setCookie(...args)
|
||||
cookie.secure = false
|
||||
cookie.httpOnly = false
|
||||
return cookie
|
||||
}
|
||||
}
|
||||
|
||||
function requestFactory({ timeout }) {
|
||||
return promisify(
|
||||
request.defaults({
|
||||
agent: new LocalhostAgent(),
|
||||
baseUrl: `http://smoke${Settings.cookieDomain}`,
|
||||
headers: {
|
||||
// emulate the header of a https proxy
|
||||
// express wont emit a 'Secure;' cookie on a plain-text connection.
|
||||
'X-Forwarded-Proto': 'https'
|
||||
},
|
||||
jar: new InsecureCookieJar(),
|
||||
timeout
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function assertHasStatusCode(response, expected) {
|
||||
const { statusCode: actual } = response
|
||||
if (actual !== expected) {
|
||||
throw new OError('unexpected response code', {
|
||||
url: response.request.uri.href,
|
||||
actual,
|
||||
expected
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
assertHasStatusCode,
|
||||
requestFactory
|
||||
}
|
18
services/web/test/smoke/src/support/timeoutHelper.js
Normal file
18
services/web/test/smoke/src/support/timeoutHelper.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
async function processWithTimeout({ work, timeout, message }) {
|
||||
let workDeadLine
|
||||
function checkInResults() {
|
||||
clearTimeout(workDeadLine)
|
||||
}
|
||||
await Promise.race([
|
||||
new Promise((resolve, reject) => {
|
||||
workDeadLine = setTimeout(() => {
|
||||
reject(new Error(message))
|
||||
}, timeout)
|
||||
}),
|
||||
work.finally(checkInResults)
|
||||
])
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
processWithTimeout
|
||||
}
|
Loading…
Reference in a new issue