ServerCEScriptsTests.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  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. trackChanges: true,
  348. }
  349. beforeEach('downgrade userSP1', async function () {
  350. await userSP1.mongoUpdate({ $set: { features: serverPro1Features } })
  351. })
  352. beforeEach('downgrade userCustomTimeoutLower', async function () {
  353. run(
  354. `node modules/server-ce-scripts/scripts/change-compile-timeout --user-id=${userCustomTimeoutLower.id} --compile-timeout=42`
  355. )
  356. })
  357. beforeEach('upgrade userCustomTimeoutHigher', async function () {
  358. run(
  359. `node modules/server-ce-scripts/scripts/change-compile-timeout --user-id=${userCustomTimeoutHigher.id} --compile-timeout=360`
  360. )
  361. })
  362. async function getFeatures() {
  363. return [
  364. await userLatest.getFeatures(),
  365. await userSP1.getFeatures(),
  366. await userCustomTimeoutLower.getFeatures(),
  367. await userCustomTimeoutHigher.getFeatures(),
  368. ]
  369. }
  370. let initialFeatures
  371. beforeEach('collect initial features', async function () {
  372. initialFeatures = await getFeatures()
  373. })
  374. it('should have prepared the right features', async function () {
  375. expect(initialFeatures).to.deep.equal([
  376. Settings.defaultFeatures,
  377. serverPro1Features,
  378. Object.assign({}, Settings.defaultFeatures, {
  379. compileTimeout: 42,
  380. }),
  381. Object.assign({}, Settings.defaultFeatures, {
  382. compileTimeout: 360,
  383. }),
  384. ])
  385. })
  386. describe('dry-run', function () {
  387. let output
  388. beforeEach('run script', function () {
  389. output = run(
  390. `node modules/server-ce-scripts/scripts/upgrade-user-features`
  391. )
  392. })
  393. it('should update SP1 features', function () {
  394. expect(output).to.include(userSP1.id)
  395. })
  396. it('should update lowerTimeout features', function () {
  397. expect(output).to.include(userCustomTimeoutLower.id)
  398. })
  399. it('should not update latest features', function () {
  400. expect(output).to.not.include(userLatest.id)
  401. })
  402. it('should not update higherTimeout features', function () {
  403. expect(output).to.not.include(userCustomTimeoutHigher.id)
  404. })
  405. it('should not change any features in the db', async function () {
  406. expect(await getFeatures()).to.deep.equal(initialFeatures)
  407. })
  408. })
  409. describe('live run', function () {
  410. let output
  411. beforeEach('run script', function () {
  412. output = run(
  413. `node modules/server-ce-scripts/scripts/upgrade-user-features --dry-run=false`
  414. )
  415. })
  416. it('should update SP1 features', function () {
  417. expect(output).to.include(userSP1.id)
  418. })
  419. it('should update lowerTimeout features', function () {
  420. expect(output).to.include(userCustomTimeoutLower.id)
  421. })
  422. it('should not update latest features', function () {
  423. expect(output).to.not.include(userLatest.id)
  424. })
  425. it('should not update higherTimeout features', function () {
  426. expect(output).to.not.include(userCustomTimeoutHigher.id)
  427. })
  428. it('should update features in the db', async function () {
  429. expect(await getFeatures()).to.deep.equal([
  430. Settings.defaultFeatures,
  431. Settings.defaultFeatures,
  432. Settings.defaultFeatures,
  433. Object.assign({}, Settings.defaultFeatures, {
  434. compileTimeout: 360,
  435. }),
  436. ])
  437. })
  438. })
  439. })
  440. })