ServerCEScriptsTests.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  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://localhost: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. })
  109. it('should exit with code 1 on missing email', function () {
  110. try {
  111. run('node modules/server-ce-scripts/scripts/delete-user')
  112. } catch (e) {
  113. expect(e.status).to.equal(1)
  114. return
  115. }
  116. expect.fail('command should have failed')
  117. })
  118. })
  119. describe('migrate-user-emails', function () {
  120. let usersToMigrate
  121. let otherUsers
  122. let csv
  123. let csvfail
  124. beforeEach(async function () {
  125. // set up some users to migrate and others to leave alone
  126. usersToMigrate = []
  127. otherUsers = []
  128. for (let i = 0; i < 2; i++) {
  129. const user = new User()
  130. await user.login()
  131. usersToMigrate.push(user)
  132. }
  133. for (let i = 0; i < 2; i++) {
  134. const user = new User()
  135. await user.login()
  136. otherUsers.push(user)
  137. }
  138. // write the migration csv to a temporary file
  139. const id = usersToMigrate[0]._id
  140. csv = `/tmp/migration-${id}.csv`
  141. const rows = []
  142. for (const user of usersToMigrate) {
  143. rows.push(`${user.email},new-${user.email}`)
  144. }
  145. fs.writeFileSync(csv, rows.join('\n'))
  146. // also write a csv with a user that doesn't exist
  147. csvfail = `/tmp/migration-fail-${id}.csv`
  148. fs.writeFileSync(
  149. csvfail,
  150. [
  151. 'nouser@example.com,nouser@other.example.com',
  152. ...rows,
  153. 'foo@example.com,bar@example.com',
  154. ].join('\n')
  155. )
  156. })
  157. afterEach(function () {
  158. // clean up the temporary files
  159. fs.unlinkSync(csv)
  160. fs.unlinkSync(csvfail)
  161. })
  162. it('should do a dry run by default', async function () {
  163. run(
  164. `node modules/server-ce-scripts/scripts/migrate-user-emails.js ${csv}`
  165. )
  166. for (const user of usersToMigrate) {
  167. const dbEntry = await user.get()
  168. expect(dbEntry.email).to.equal(user.email)
  169. }
  170. for (const user of otherUsers) {
  171. const dbEntry = await user.get()
  172. expect(dbEntry.email).to.equal(user.email)
  173. }
  174. })
  175. it('should exit with code 0 when successfully migrating user emails', function () {
  176. run(
  177. `node modules/server-ce-scripts/scripts/migrate-user-emails.js --commit ${csv}`
  178. )
  179. })
  180. it('should migrate the user emails with the --commit option', async function () {
  181. run(
  182. `node modules/server-ce-scripts/scripts/migrate-user-emails.js --commit ${csv}`
  183. )
  184. for (const user of usersToMigrate) {
  185. const dbEntry = await user.get()
  186. expect(dbEntry.email).to.equal(`new-${user.email}`)
  187. expect(dbEntry.emails).to.have.lengthOf(1)
  188. expect(dbEntry.emails[0].email).to.equal(`new-${user.email}`)
  189. expect(dbEntry.emails[0].reversedHostname).to.equal('moc.elpmaxe')
  190. expect(dbEntry.emails[0].createdAt).to.eql(user.emails[0].createdAt)
  191. }
  192. })
  193. it('should leave other user emails unchanged', async function () {
  194. run(
  195. `node modules/server-ce-scripts/scripts/migrate-user-emails.js --commit ${csv}`
  196. )
  197. for (const user of otherUsers) {
  198. const dbEntry = await user.get()
  199. expect(dbEntry.email).to.equal(user.email)
  200. }
  201. })
  202. it('should exit with code 1 when there are failures migrating user emails', function () {
  203. try {
  204. run(
  205. `node modules/server-ce-scripts/scripts/migrate-user-emails.js --commit ${csvfail}`
  206. )
  207. } catch (e) {
  208. expect(e.status).to.equal(1)
  209. return
  210. }
  211. expect.fail('command should have failed')
  212. })
  213. it('should migrate other users when there are failures with the --continue option', async function () {
  214. try {
  215. run(
  216. `node modules/server-ce-scripts/scripts/migrate-user-emails.js --commit ${csvfail}`
  217. )
  218. } catch (e) {
  219. expect(e.status).to.equal(1)
  220. run(
  221. `node modules/server-ce-scripts/scripts/migrate-user-emails.js --commit --continue ${csvfail}`
  222. )
  223. for (const user of usersToMigrate) {
  224. const dbEntry = await user.get()
  225. expect(dbEntry.email).to.equal(`new-${user.email}`)
  226. expect(dbEntry.emails).to.have.lengthOf(1)
  227. expect(dbEntry.emails[0].email).to.equal(`new-${user.email}`)
  228. expect(dbEntry.emails[0].reversedHostname).to.equal('moc.elpmaxe')
  229. expect(dbEntry.emails[0].createdAt).to.eql(user.emails[0].createdAt)
  230. }
  231. return
  232. }
  233. expect.fail('command should have failed')
  234. })
  235. })
  236. describe('rename-tag', function () {
  237. let user
  238. beforeEach(async function () {
  239. user = new User()
  240. await user.login()
  241. })
  242. async function createTag(name) {
  243. await user.doRequest('POST', { url: '/tag', json: { name } })
  244. }
  245. async function getTagNames() {
  246. const { body } = await user.doRequest('GET', { url: '/tag', json: true })
  247. return body.map(tag => tag.name)
  248. }
  249. it('should rename a tag', async function () {
  250. const oldName = 'before'
  251. const newName = 'after'
  252. await createTag(oldName)
  253. expect(await getTagNames()).to.deep.equal([oldName])
  254. run(
  255. `node modules/server-ce-scripts/scripts/rename-tag --user-id=${user.id} --old-name=${oldName} --new-name=${newName}`
  256. )
  257. expect(await getTagNames()).to.deep.equal([newName])
  258. })
  259. })
  260. describe('change-compile-timeout', function () {
  261. let userA, userB
  262. beforeEach('login', async function () {
  263. userA = new User()
  264. await userA.login()
  265. userB = new User()
  266. await userB.login()
  267. })
  268. async function getCompileTimeout(user) {
  269. const { compileTimeout } = await user.getFeatures()
  270. return compileTimeout
  271. }
  272. let userATimeout, userBTimeout
  273. beforeEach('fetch current state', async function () {
  274. userATimeout = await getCompileTimeout(userA)
  275. userBTimeout = await getCompileTimeout(userB)
  276. })
  277. describe('happy path', function () {
  278. let newUserATimeout
  279. beforeEach('run script on user a', function () {
  280. newUserATimeout = userATimeout - 1
  281. run(
  282. `node modules/server-ce-scripts/scripts/change-compile-timeout --user-id=${userA.id} --compile-timeout=${newUserATimeout}`
  283. )
  284. })
  285. it('should change the timeout for user a', async function () {
  286. const actual = await getCompileTimeout(userA)
  287. expect(actual).to.not.equal(userATimeout)
  288. expect(actual).to.equal(newUserATimeout)
  289. })
  290. it('should leave the timeout for user b as is', async function () {
  291. expect(await getCompileTimeout(userB)).to.equal(userBTimeout)
  292. })
  293. })
  294. describe('bad options', function () {
  295. it('should reject zero timeout', async function () {
  296. try {
  297. run(
  298. `node modules/server-ce-scripts/scripts/change-compile-timeout --user-id=${userA.id} --compile-timeout=0`
  299. )
  300. expect.fail('should error out')
  301. } catch (err) {
  302. expect(err.stderr.toString()).to.include('positive number of seconds')
  303. }
  304. expect(await getCompileTimeout(userA)).to.equal(userATimeout)
  305. expect(await getCompileTimeout(userB)).to.equal(userBTimeout)
  306. })
  307. it('should reject a 20min timeout', async function () {
  308. try {
  309. run(
  310. `node modules/server-ce-scripts/scripts/change-compile-timeout --user-id=${userA.id} --compile-timeout=1200`
  311. )
  312. expect.fail('should error out')
  313. } catch (err) {
  314. expect(err.stderr.toString()).to.include('below 10 minutes')
  315. }
  316. expect(await getCompileTimeout(userA)).to.equal(userATimeout)
  317. expect(await getCompileTimeout(userB)).to.equal(userBTimeout)
  318. })
  319. })
  320. })
  321. describe('upgrade-user-features', function () {
  322. let userLatest, userSP1, userCustomTimeoutLower, userCustomTimeoutHigher
  323. beforeEach('create users', async function () {
  324. userLatest = new User()
  325. userSP1 = new User()
  326. userCustomTimeoutLower = new User()
  327. userCustomTimeoutHigher = new User()
  328. await Promise.all([
  329. userLatest.ensureUserExists(),
  330. userSP1.ensureUserExists(),
  331. userCustomTimeoutLower.ensureUserExists(),
  332. userCustomTimeoutHigher.ensureUserExists(),
  333. ])
  334. })
  335. const serverPro1Features = {
  336. collaborators: -1,
  337. dropbox: true,
  338. versioning: true,
  339. compileTimeout: 180,
  340. compileGroup: 'standard',
  341. references: true,
  342. templates: true,
  343. trackChanges: true,
  344. }
  345. beforeEach('downgrade userSP1', async function () {
  346. await userSP1.mongoUpdate({ $set: { features: serverPro1Features } })
  347. })
  348. beforeEach('downgrade userCustomTimeoutLower', async function () {
  349. run(
  350. `node modules/server-ce-scripts/scripts/change-compile-timeout --user-id=${userCustomTimeoutLower.id} --compile-timeout=42`
  351. )
  352. })
  353. beforeEach('upgrade userCustomTimeoutHigher', async function () {
  354. run(
  355. `node modules/server-ce-scripts/scripts/change-compile-timeout --user-id=${userCustomTimeoutHigher.id} --compile-timeout=360`
  356. )
  357. })
  358. async function getFeatures() {
  359. return [
  360. await userLatest.getFeatures(),
  361. await userSP1.getFeatures(),
  362. await userCustomTimeoutLower.getFeatures(),
  363. await userCustomTimeoutHigher.getFeatures(),
  364. ]
  365. }
  366. let initialFeatures
  367. beforeEach('collect initial features', async function () {
  368. initialFeatures = await getFeatures()
  369. })
  370. it('should have prepared the right features', async function () {
  371. expect(initialFeatures).to.deep.equal([
  372. Settings.defaultFeatures,
  373. serverPro1Features,
  374. Object.assign({}, Settings.defaultFeatures, {
  375. compileTimeout: 42,
  376. }),
  377. Object.assign({}, Settings.defaultFeatures, {
  378. compileTimeout: 360,
  379. }),
  380. ])
  381. })
  382. describe('dry-run', function () {
  383. let output
  384. beforeEach('run script', function () {
  385. output = run(
  386. `node modules/server-ce-scripts/scripts/upgrade-user-features`
  387. )
  388. })
  389. it('should update SP1 features', function () {
  390. expect(output).to.include(userSP1.id)
  391. })
  392. it('should update lowerTimeout features', function () {
  393. expect(output).to.include(userCustomTimeoutLower.id)
  394. })
  395. it('should not update latest features', function () {
  396. expect(output).to.not.include(userLatest.id)
  397. })
  398. it('should not update higherTimeout features', function () {
  399. expect(output).to.not.include(userCustomTimeoutHigher.id)
  400. })
  401. it('should not change any features in the db', async function () {
  402. expect(await getFeatures()).to.deep.equal(initialFeatures)
  403. })
  404. })
  405. describe('live run', function () {
  406. let output
  407. beforeEach('run script', function () {
  408. output = run(
  409. `node modules/server-ce-scripts/scripts/upgrade-user-features --dry-run=false`
  410. )
  411. })
  412. it('should update SP1 features', function () {
  413. expect(output).to.include(userSP1.id)
  414. })
  415. it('should update lowerTimeout features', function () {
  416. expect(output).to.include(userCustomTimeoutLower.id)
  417. })
  418. it('should not update latest features', function () {
  419. expect(output).to.not.include(userLatest.id)
  420. })
  421. it('should not update higherTimeout features', function () {
  422. expect(output).to.not.include(userCustomTimeoutHigher.id)
  423. })
  424. it('should update features in the db', async function () {
  425. expect(await getFeatures()).to.deep.equal([
  426. Settings.defaultFeatures,
  427. Settings.defaultFeatures,
  428. Settings.defaultFeatures,
  429. Object.assign({}, Settings.defaultFeatures, {
  430. compileTimeout: 360,
  431. }),
  432. ])
  433. })
  434. })
  435. })
  436. })