ServerCEScriptsTests.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. const Settings = require('@overleaf/settings')
  2. const { execSync } = require('child_process')
  3. const { expect } = require('chai')
  4. const { db } = require('../../../../../app/src/infrastructure/mongodb')
  5. const User = require('../../../../../test/acceptance/src/helpers/User').promises
  6. const fs = require('fs')
  7. /**
  8. * @param {string} cmd
  9. * @return {string}
  10. */
  11. function run(cmd) {
  12. // https://nodejs.org/docs/latest-v12.x/api/child_process.html#child_process_child_process_execsync_command_options
  13. // > stderr by default will be output to the parent process' stderr
  14. // > unless stdio is specified.
  15. // https://nodejs.org/docs/latest-v12.x/api/child_process.html#child_process_options_stdio
  16. // Pipe stdin from /dev/null, store stdout, pipe stderr to /dev/null.
  17. return execSync(cmd, {
  18. stdio: ['ignore', 'pipe', 'pipe'],
  19. }).toString()
  20. }
  21. async function getUser(email) {
  22. return db.users.findOne({ email }, { projection: { _id: 0, isAdmin: 1 } })
  23. }
  24. describe('ServerCEScripts', function () {
  25. describe('check-mongodb', function () {
  26. it('should exit with code 0 on success', function () {
  27. run('node modules/server-ce-scripts/scripts/check-mongodb')
  28. })
  29. it('should exit with code 1 on error', function () {
  30. try {
  31. run(
  32. 'MONGO_SERVER_SELECTION_TIMEOUT=1' +
  33. 'MONGO_CONNECTION_STRING=mongodb://127.0.0.1:4242 ' +
  34. 'node modules/server-ce-scripts/scripts/check-mongodb'
  35. )
  36. } catch (e) {
  37. expect(e.status).to.equal(1)
  38. return
  39. }
  40. expect.fail('command should have failed')
  41. })
  42. })
  43. describe('check-redis', function () {
  44. it('should exit with code 0 on success', function () {
  45. run('node modules/server-ce-scripts/scripts/check-redis')
  46. })
  47. it('should exit with code 1 on error', function () {
  48. try {
  49. run('REDIS_PORT=42 node modules/server-ce-scripts/scripts/check-redis')
  50. } catch (e) {
  51. expect(e.status).to.equal(1)
  52. return
  53. }
  54. expect.fail('command should have failed')
  55. })
  56. })
  57. describe('create-user', function () {
  58. it('should exit with code 0 on success', function () {
  59. const out = run(
  60. 'node modules/server-ce-scripts/scripts/create-user --email=foo@bar.com'
  61. )
  62. expect(out).to.include('/user/activate?token=')
  63. })
  64. it('should create a regular user by default', async function () {
  65. run(
  66. 'node modules/server-ce-scripts/scripts/create-user --email=foo@bar.com'
  67. )
  68. expect(await getUser('foo@bar.com')).to.deep.equal({ isAdmin: false })
  69. })
  70. it('should create an admin user with --admin flag', async function () {
  71. run(
  72. 'node modules/server-ce-scripts/scripts/create-user --admin --email=foo@bar.com'
  73. )
  74. expect(await getUser('foo@bar.com')).to.deep.equal({ isAdmin: true })
  75. })
  76. it('should exit with code 1 on missing email', function () {
  77. try {
  78. run('node modules/server-ce-scripts/scripts/create-user')
  79. } catch (e) {
  80. expect(e.status).to.equal(1)
  81. return
  82. }
  83. expect.fail('command should have failed')
  84. })
  85. })
  86. describe('delete-user', function () {
  87. let user
  88. beforeEach(async function () {
  89. user = new User()
  90. await user.login()
  91. })
  92. it('should log missing user', function () {
  93. const email = 'does-not-exist@example.com'
  94. const out = run(
  95. 'node modules/server-ce-scripts/scripts/delete-user --email=' + email
  96. )
  97. expect(out).to.include('not in database, potentially already deleted')
  98. })
  99. it('should exit with code 0 on success', function () {
  100. const email = user.email
  101. run('node modules/server-ce-scripts/scripts/delete-user --email=' + email)
  102. })
  103. it('should have deleted the user on success', async function () {
  104. const email = user.email
  105. run('node modules/server-ce-scripts/scripts/delete-user --email=' + email)
  106. const dbEntry = await user.get()
  107. expect(dbEntry).to.not.exist
  108. const softDeletedEntry = await db.deletedUsers.findOne({
  109. 'user.email': email,
  110. })
  111. expect(softDeletedEntry).to.exist
  112. expect(softDeletedEntry.deleterData.deleterIpAddress).to.equal('0.0.0.0')
  113. })
  114. it('should exit with code 1 on missing email', function () {
  115. try {
  116. run('node modules/server-ce-scripts/scripts/delete-user')
  117. } catch (e) {
  118. expect(e.status).to.equal(1)
  119. return
  120. }
  121. expect.fail('command should have failed')
  122. })
  123. })
  124. describe('migrate-user-emails', function () {
  125. let usersToMigrate
  126. let otherUsers
  127. let csv
  128. let csvfail
  129. beforeEach(async function () {
  130. // set up some users to migrate and others to leave alone
  131. usersToMigrate = []
  132. otherUsers = []
  133. for (let i = 0; i < 2; i++) {
  134. const user = new User()
  135. await user.login()
  136. usersToMigrate.push(user)
  137. }
  138. for (let i = 0; i < 2; i++) {
  139. const user = new User()
  140. await user.login()
  141. otherUsers.push(user)
  142. }
  143. // write the migration csv to a temporary file
  144. const id = usersToMigrate[0]._id
  145. csv = `/tmp/migration-${id}.csv`
  146. const rows = []
  147. for (const user of usersToMigrate) {
  148. rows.push(`${user.email},new-${user.email}`)
  149. }
  150. fs.writeFileSync(csv, rows.join('\n'))
  151. // also write a csv with a user that doesn't exist
  152. csvfail = `/tmp/migration-fail-${id}.csv`
  153. fs.writeFileSync(
  154. csvfail,
  155. [
  156. 'nouser@example.com,nouser@other.example.com',
  157. ...rows,
  158. 'foo@example.com,bar@example.com',
  159. ].join('\n')
  160. )
  161. })
  162. afterEach(function () {
  163. // clean up the temporary files
  164. fs.unlinkSync(csv)
  165. fs.unlinkSync(csvfail)
  166. })
  167. it('should do a dry run by default', async function () {
  168. run(
  169. `node modules/server-ce-scripts/scripts/migrate-user-emails.js ${csv}`
  170. )
  171. for (const user of usersToMigrate) {
  172. const dbEntry = await user.get()
  173. expect(dbEntry.email).to.equal(user.email)
  174. }
  175. for (const user of otherUsers) {
  176. const dbEntry = await user.get()
  177. expect(dbEntry.email).to.equal(user.email)
  178. }
  179. })
  180. it('should exit with code 0 when successfully migrating user emails', function () {
  181. run(
  182. `node modules/server-ce-scripts/scripts/migrate-user-emails.js --commit ${csv}`
  183. )
  184. })
  185. it('should migrate the user emails with the --commit option', async function () {
  186. run(
  187. `node modules/server-ce-scripts/scripts/migrate-user-emails.js --commit ${csv}`
  188. )
  189. for (const user of usersToMigrate) {
  190. const dbEntry = await user.get()
  191. expect(dbEntry.email).to.equal(`new-${user.email}`)
  192. expect(dbEntry.emails).to.have.lengthOf(1)
  193. expect(dbEntry.emails[0].email).to.equal(`new-${user.email}`)
  194. expect(dbEntry.emails[0].reversedHostname).to.equal('moc.elpmaxe')
  195. expect(dbEntry.emails[0].createdAt).to.eql(user.emails[0].createdAt)
  196. }
  197. })
  198. it('should leave other user emails unchanged', async function () {
  199. run(
  200. `node modules/server-ce-scripts/scripts/migrate-user-emails.js --commit ${csv}`
  201. )
  202. for (const user of otherUsers) {
  203. const dbEntry = await user.get()
  204. expect(dbEntry.email).to.equal(user.email)
  205. }
  206. })
  207. it('should exit with code 1 when there are failures migrating user emails', function () {
  208. try {
  209. run(
  210. `node modules/server-ce-scripts/scripts/migrate-user-emails.js --commit ${csvfail}`
  211. )
  212. } catch (e) {
  213. expect(e.status).to.equal(1)
  214. return
  215. }
  216. expect.fail('command should have failed')
  217. })
  218. it('should migrate other users when there are failures with the --continue option', async function () {
  219. try {
  220. run(
  221. `node modules/server-ce-scripts/scripts/migrate-user-emails.js --commit ${csvfail}`
  222. )
  223. } catch (e) {
  224. expect(e.status).to.equal(1)
  225. run(
  226. `node modules/server-ce-scripts/scripts/migrate-user-emails.js --commit --continue ${csvfail}`
  227. )
  228. for (const user of usersToMigrate) {
  229. const dbEntry = await user.get()
  230. expect(dbEntry.email).to.equal(`new-${user.email}`)
  231. expect(dbEntry.emails).to.have.lengthOf(1)
  232. expect(dbEntry.emails[0].email).to.equal(`new-${user.email}`)
  233. expect(dbEntry.emails[0].reversedHostname).to.equal('moc.elpmaxe')
  234. expect(dbEntry.emails[0].createdAt).to.eql(user.emails[0].createdAt)
  235. }
  236. return
  237. }
  238. expect.fail('command should have failed')
  239. })
  240. })
  241. describe('rename-tag', function () {
  242. let user
  243. beforeEach(async function () {
  244. user = new User()
  245. await user.login()
  246. })
  247. async function createTag(name) {
  248. await user.doRequest('POST', { url: '/tag', json: { name } })
  249. }
  250. async function getTagNames() {
  251. const { body } = await user.doRequest('GET', { url: '/tag', json: true })
  252. return body.map(tag => tag.name)
  253. }
  254. it('should rename a tag', async function () {
  255. const oldName = 'before'
  256. const newName = 'after'
  257. await createTag(oldName)
  258. expect(await getTagNames()).to.deep.equal([oldName])
  259. run(
  260. `node modules/server-ce-scripts/scripts/rename-tag --user-id=${user.id} --old-name=${oldName} --new-name=${newName}`
  261. )
  262. expect(await getTagNames()).to.deep.equal([newName])
  263. })
  264. })
  265. describe('change-compile-timeout', function () {
  266. let userA, userB
  267. beforeEach('login', async function () {
  268. userA = new User()
  269. await userA.login()
  270. userB = new User()
  271. await userB.login()
  272. })
  273. async function getCompileTimeout(user) {
  274. const { compileTimeout } = await user.getFeatures()
  275. return compileTimeout
  276. }
  277. let userATimeout, userBTimeout
  278. beforeEach('fetch current state', async function () {
  279. userATimeout = await getCompileTimeout(userA)
  280. userBTimeout = await getCompileTimeout(userB)
  281. })
  282. describe('happy path', function () {
  283. let newUserATimeout
  284. beforeEach('run script on user a', function () {
  285. newUserATimeout = userATimeout - 1
  286. run(
  287. `node modules/server-ce-scripts/scripts/change-compile-timeout --user-id=${userA.id} --compile-timeout=${newUserATimeout}`
  288. )
  289. })
  290. it('should change the timeout for user a', async function () {
  291. const actual = await getCompileTimeout(userA)
  292. expect(actual).to.not.equal(userATimeout)
  293. expect(actual).to.equal(newUserATimeout)
  294. })
  295. it('should leave the timeout for user b as is', async function () {
  296. expect(await getCompileTimeout(userB)).to.equal(userBTimeout)
  297. })
  298. })
  299. describe('bad options', function () {
  300. it('should reject zero timeout', async function () {
  301. try {
  302. run(
  303. `node modules/server-ce-scripts/scripts/change-compile-timeout --user-id=${userA.id} --compile-timeout=0`
  304. )
  305. expect.fail('should error out')
  306. } catch (err) {
  307. expect(err.stderr.toString()).to.include('positive number of seconds')
  308. }
  309. expect(await getCompileTimeout(userA)).to.equal(userATimeout)
  310. expect(await getCompileTimeout(userB)).to.equal(userBTimeout)
  311. })
  312. it('should reject a 20min timeout', async function () {
  313. try {
  314. run(
  315. `node modules/server-ce-scripts/scripts/change-compile-timeout --user-id=${userA.id} --compile-timeout=1200`
  316. )
  317. expect.fail('should error out')
  318. } catch (err) {
  319. expect(err.stderr.toString()).to.include('below 10 minutes')
  320. }
  321. expect(await getCompileTimeout(userA)).to.equal(userATimeout)
  322. expect(await getCompileTimeout(userB)).to.equal(userBTimeout)
  323. })
  324. })
  325. })
  326. describe('upgrade-user-features', function () {
  327. let userLatest, userSP1, userCustomTimeoutLower, userCustomTimeoutHigher
  328. beforeEach('create users', async function () {
  329. userLatest = new User()
  330. userSP1 = new User()
  331. userCustomTimeoutLower = new User()
  332. userCustomTimeoutHigher = new User()
  333. await Promise.all([
  334. userLatest.ensureUserExists(),
  335. userSP1.ensureUserExists(),
  336. userCustomTimeoutLower.ensureUserExists(),
  337. userCustomTimeoutHigher.ensureUserExists(),
  338. ])
  339. })
  340. const serverPro1Features = {
  341. collaborators: -1,
  342. dropbox: true,
  343. versioning: true,
  344. compileTimeout: 180,
  345. compileGroup: 'standard',
  346. references: true,
  347. templates: true,
  348. trackChanges: true,
  349. }
  350. beforeEach('downgrade userSP1', async function () {
  351. await userSP1.mongoUpdate({ $set: { features: serverPro1Features } })
  352. })
  353. beforeEach('downgrade userCustomTimeoutLower', async function () {
  354. run(
  355. `node modules/server-ce-scripts/scripts/change-compile-timeout --user-id=${userCustomTimeoutLower.id} --compile-timeout=42`
  356. )
  357. })
  358. beforeEach('upgrade userCustomTimeoutHigher', async function () {
  359. run(
  360. `node modules/server-ce-scripts/scripts/change-compile-timeout --user-id=${userCustomTimeoutHigher.id} --compile-timeout=360`
  361. )
  362. })
  363. async function getFeatures() {
  364. return [
  365. await userLatest.getFeatures(),
  366. await userSP1.getFeatures(),
  367. await userCustomTimeoutLower.getFeatures(),
  368. await userCustomTimeoutHigher.getFeatures(),
  369. ]
  370. }
  371. let initialFeatures
  372. beforeEach('collect initial features', async function () {
  373. initialFeatures = await getFeatures()
  374. })
  375. it('should have prepared the right features', async function () {
  376. expect(initialFeatures).to.deep.equal([
  377. Settings.defaultFeatures,
  378. serverPro1Features,
  379. Object.assign({}, Settings.defaultFeatures, {
  380. compileTimeout: 42,
  381. }),
  382. Object.assign({}, Settings.defaultFeatures, {
  383. compileTimeout: 360,
  384. }),
  385. ])
  386. })
  387. describe('dry-run', function () {
  388. let output
  389. beforeEach('run script', function () {
  390. output = run(
  391. `node modules/server-ce-scripts/scripts/upgrade-user-features`
  392. )
  393. })
  394. it('should update SP1 features', function () {
  395. expect(output).to.include(userSP1.id)
  396. })
  397. it('should update lowerTimeout features', function () {
  398. expect(output).to.include(userCustomTimeoutLower.id)
  399. })
  400. it('should not update latest features', function () {
  401. expect(output).to.not.include(userLatest.id)
  402. })
  403. it('should not update higherTimeout features', function () {
  404. expect(output).to.not.include(userCustomTimeoutHigher.id)
  405. })
  406. it('should not change any features in the db', async function () {
  407. expect(await getFeatures()).to.deep.equal(initialFeatures)
  408. })
  409. })
  410. describe('live run', function () {
  411. let output
  412. beforeEach('run script', function () {
  413. output = run(
  414. `node modules/server-ce-scripts/scripts/upgrade-user-features --dry-run=false`
  415. )
  416. })
  417. it('should update SP1 features', function () {
  418. expect(output).to.include(userSP1.id)
  419. })
  420. it('should update lowerTimeout features', function () {
  421. expect(output).to.include(userCustomTimeoutLower.id)
  422. })
  423. it('should not update latest features', function () {
  424. expect(output).to.not.include(userLatest.id)
  425. })
  426. it('should not update higherTimeout features', function () {
  427. expect(output).to.not.include(userCustomTimeoutHigher.id)
  428. })
  429. it('should update features in the db', async function () {
  430. expect(await getFeatures()).to.deep.equal([
  431. Settings.defaultFeatures,
  432. Settings.defaultFeatures,
  433. Settings.defaultFeatures,
  434. Object.assign({}, Settings.defaultFeatures, {
  435. compileTimeout: 360,
  436. }),
  437. ])
  438. })
  439. })
  440. })
  441. })