ServerCEScriptsTests.mjs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744
  1. import { exec } from 'node:child_process'
  2. import fs from 'node:fs'
  3. import Settings from '@overleaf/settings'
  4. import { expect } from 'chai'
  5. import { db } from '../../../../../app/src/infrastructure/mongodb.js'
  6. import UserHelper from '../../../../../test/acceptance/src/helpers/User.mjs'
  7. const { promises: User } = UserHelper
  8. /**
  9. * @param {string} cmd
  10. * @return {Promise<string>}
  11. */
  12. async function run(cmd) {
  13. // https://nodejs.org/docs/latest-v12.x/api/child_process.html#child_process_child_process_execsync_command_options
  14. // > stderr by default will be output to the parent process' stderr
  15. // > unless stdio is specified.
  16. // https://nodejs.org/docs/latest-v12.x/api/child_process.html#child_process_options_stdio
  17. // Pipe stdin from /dev/null, store stdout, pipe stderr to /dev/null.
  18. return new Promise((resolve, reject) => {
  19. exec(
  20. cmd,
  21. {
  22. stdio: ['ignore', 'pipe', 'pipe'],
  23. env: {
  24. ...process.env,
  25. LOG_LEVEL: 'warn',
  26. },
  27. },
  28. (error, stdout) => {
  29. if (error) {
  30. reject(error)
  31. } else {
  32. resolve(stdout)
  33. }
  34. }
  35. )
  36. })
  37. }
  38. async function runAndExpectError(cmd, errorMessages, exitCode = 1) {
  39. try {
  40. await run(cmd)
  41. } catch (error) {
  42. expect(error.code).to.equal(exitCode)
  43. if (errorMessages) {
  44. errorMessages.forEach(errorMessage =>
  45. expect(error.message).to.include(errorMessage)
  46. )
  47. }
  48. return
  49. }
  50. expect.fail('command should have failed')
  51. }
  52. async function getUser(email) {
  53. return db.users.findOne({ email }, { projection: { _id: 0, isAdmin: 1 } })
  54. }
  55. describe('ServerCEScripts', function () {
  56. describe('check-mongodb', function () {
  57. it('should exit with code 0 on success', async function () {
  58. await run('node modules/server-ce-scripts/scripts/check-mongodb.mjs')
  59. })
  60. it('should exit with code 1 on error', async function () {
  61. await runAndExpectError(
  62. 'MONGO_SERVER_SELECTION_TIMEOUT=1 ' +
  63. 'MONGO_CONNECTION_STRING=mongodb://127.0.0.1:4242 ' +
  64. 'node modules/server-ce-scripts/scripts/check-mongodb.mjs'
  65. )
  66. })
  67. })
  68. describe('check-redis', function () {
  69. it('should exit with code 0 on success', async function () {
  70. await run('node modules/server-ce-scripts/scripts/check-redis.mjs')
  71. })
  72. it('should exit with code 1 on error', async function () {
  73. await runAndExpectError(
  74. 'REDIS_PORT=42 node modules/server-ce-scripts/scripts/check-redis.mjs'
  75. )
  76. })
  77. })
  78. describe('create-user', function () {
  79. it('should exit with code 0 on success', async function () {
  80. const out = await run(
  81. 'node modules/server-ce-scripts/scripts/create-user.js --email=foo@bar.com'
  82. )
  83. expect(out).to.include('/user/activate?token=')
  84. })
  85. it('should create a regular user by default', async function () {
  86. await run(
  87. 'node modules/server-ce-scripts/scripts/create-user.js --email=foo@bar.com'
  88. )
  89. expect(await getUser('foo@bar.com')).to.deep.equal({ isAdmin: false })
  90. })
  91. it('should also work with mjs version', async function () {
  92. const out = await run(
  93. 'node modules/server-ce-scripts/scripts/create-user.mjs --email=foo@bar.com'
  94. )
  95. expect(out).to.include('/user/activate?token=')
  96. expect(await getUser('foo@bar.com')).to.deep.equal({ isAdmin: false })
  97. })
  98. it('should create an admin user with --admin flag', async function () {
  99. await run(
  100. 'node modules/server-ce-scripts/scripts/create-user.js --admin --email=foo@bar.com'
  101. )
  102. expect(await getUser('foo@bar.com')).to.deep.equal({ isAdmin: true })
  103. })
  104. it('should exit with code 1 on missing email', async function () {
  105. await runAndExpectError(
  106. 'node modules/server-ce-scripts/scripts/create-user.js'
  107. )
  108. })
  109. })
  110. describe('delete-user', function () {
  111. let user
  112. beforeEach(async function () {
  113. user = new User()
  114. await user.login()
  115. })
  116. it('should log missing user', async function () {
  117. const email = 'does-not-exist@example.com'
  118. const out = await run(
  119. 'node modules/server-ce-scripts/scripts/delete-user.mjs --email=' +
  120. email
  121. )
  122. expect(out).to.include('not in database, potentially already deleted')
  123. })
  124. it('should exit with code 0 on success', async function () {
  125. const email = user.email
  126. await run(
  127. 'node modules/server-ce-scripts/scripts/delete-user.mjs --email=' +
  128. email
  129. )
  130. })
  131. it('should have deleted the user on success', async function () {
  132. const email = user.email
  133. await run(
  134. 'node modules/server-ce-scripts/scripts/delete-user.mjs --email=' +
  135. email
  136. )
  137. const dbEntry = await user.get()
  138. expect(dbEntry).to.not.exist
  139. const softDeletedEntry = await db.deletedUsers.findOne({
  140. 'user.email': email,
  141. })
  142. expect(softDeletedEntry).to.exist
  143. expect(softDeletedEntry.deleterData.deleterIpAddress).to.equal('0.0.0.0')
  144. })
  145. it('should exit with code 1 on missing email', async function () {
  146. await runAndExpectError(
  147. 'node modules/server-ce-scripts/scripts/delete-user.mjs'
  148. )
  149. })
  150. })
  151. describe('migrate-user-emails', function () {
  152. let usersToMigrate
  153. let otherUsers
  154. let csv
  155. let csvfail
  156. beforeEach(async function () {
  157. // set up some users to migrate and others to leave alone
  158. usersToMigrate = []
  159. otherUsers = []
  160. for (let i = 0; i < 2; i++) {
  161. const user = new User()
  162. await user.login()
  163. usersToMigrate.push(user)
  164. }
  165. for (let i = 0; i < 2; i++) {
  166. const user = new User()
  167. await user.login()
  168. otherUsers.push(user)
  169. }
  170. // write the migration csv to a temporary file
  171. const id = usersToMigrate[0]._id
  172. csv = `/tmp/migration-${id}.csv`
  173. const rows = []
  174. for (const user of usersToMigrate) {
  175. rows.push(`${user.email},new-${user.email}`)
  176. }
  177. fs.writeFileSync(csv, rows.join('\n'))
  178. // also write a csv with a user that doesn't exist
  179. csvfail = `/tmp/migration-fail-${id}.csv`
  180. fs.writeFileSync(
  181. csvfail,
  182. [
  183. 'nouser@example.com,nouser@other.example.com',
  184. ...rows,
  185. 'foo@example.com,bar@example.com',
  186. ].join('\n')
  187. )
  188. })
  189. afterEach(function () {
  190. // clean up the temporary files
  191. fs.unlinkSync(csv)
  192. fs.unlinkSync(csvfail)
  193. })
  194. it('should do a dry run by default', async function () {
  195. await run(
  196. `node modules/server-ce-scripts/scripts/migrate-user-emails.mjs ${csv}`
  197. )
  198. for (const user of usersToMigrate) {
  199. const dbEntry = await user.get()
  200. expect(dbEntry.email).to.equal(user.email)
  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 0 when successfully migrating user emails', async function () {
  208. await run(
  209. `node modules/server-ce-scripts/scripts/migrate-user-emails.mjs --commit ${csv}`
  210. )
  211. })
  212. it('should migrate the user emails with the --commit option', async function () {
  213. await run(
  214. `node modules/server-ce-scripts/scripts/migrate-user-emails.mjs --commit ${csv}`
  215. )
  216. for (const user of usersToMigrate) {
  217. const dbEntry = await user.get()
  218. expect(dbEntry.email).to.equal(`new-${user.email}`)
  219. expect(dbEntry.emails).to.have.lengthOf(1)
  220. expect(dbEntry.emails[0].email).to.equal(`new-${user.email}`)
  221. expect(dbEntry.emails[0].reversedHostname).to.equal('moc.elpmaxe')
  222. expect(dbEntry.emails[0].createdAt).to.eql(user.emails[0].createdAt)
  223. }
  224. })
  225. it('should leave other user emails unchanged', async function () {
  226. await run(
  227. `node modules/server-ce-scripts/scripts/migrate-user-emails.mjs --commit ${csv}`
  228. )
  229. for (const user of otherUsers) {
  230. const dbEntry = await user.get()
  231. expect(dbEntry.email).to.equal(user.email)
  232. }
  233. })
  234. it('should exit with code 1 when there are failures migrating user emails', async function () {
  235. await runAndExpectError(
  236. `node modules/server-ce-scripts/scripts/migrate-user-emails.mjs --commit ${csvfail}`
  237. )
  238. })
  239. it('should migrate other users when there are failures with the --continue option', async function () {
  240. await runAndExpectError(
  241. `node modules/server-ce-scripts/scripts/migrate-user-emails.mjs --commit ${csvfail}`
  242. )
  243. await run(
  244. `node modules/server-ce-scripts/scripts/migrate-user-emails.mjs --commit --continue ${csvfail}`
  245. )
  246. for (const user of usersToMigrate) {
  247. const dbEntry = await user.get()
  248. expect(dbEntry.email).to.equal(`new-${user.email}`)
  249. expect(dbEntry.emails).to.have.lengthOf(1)
  250. expect(dbEntry.emails[0].email).to.equal(`new-${user.email}`)
  251. expect(dbEntry.emails[0].reversedHostname).to.equal('moc.elpmaxe')
  252. expect(dbEntry.emails[0].createdAt).to.eql(user.emails[0].createdAt)
  253. }
  254. })
  255. })
  256. describe('rename-tag', function () {
  257. let user
  258. beforeEach(async function () {
  259. user = new User()
  260. await user.login()
  261. })
  262. async function createTag(name) {
  263. await user.doRequest('POST', { url: '/tag', json: { name } })
  264. }
  265. async function getTagNames() {
  266. const { body } = await user.doRequest('GET', { url: '/tag', json: true })
  267. return body.map(tag => tag.name)
  268. }
  269. it('should rename a tag', async function () {
  270. const oldName = 'before'
  271. const newName = 'after'
  272. await createTag(oldName)
  273. expect(await getTagNames()).to.deep.equal([oldName])
  274. await run(
  275. `node modules/server-ce-scripts/scripts/rename-tag.mjs --user-id=${user.id} --old-name=${oldName} --new-name=${newName}`
  276. )
  277. expect(await getTagNames()).to.deep.equal([newName])
  278. })
  279. })
  280. describe('change-compile-timeout', function () {
  281. let userA, userB
  282. beforeEach('login', async function () {
  283. userA = new User()
  284. await userA.login()
  285. userB = new User()
  286. await userB.login()
  287. })
  288. async function getCompileTimeout(user) {
  289. const { compileTimeout } = await user.getFeatures()
  290. return compileTimeout
  291. }
  292. let userATimeout, userBTimeout
  293. beforeEach('fetch current state', async function () {
  294. userATimeout = await getCompileTimeout(userA)
  295. userBTimeout = await getCompileTimeout(userB)
  296. })
  297. describe('happy path', function () {
  298. let newUserATimeout
  299. beforeEach('run script on user a', async function () {
  300. newUserATimeout = userATimeout - 1
  301. await run(
  302. `node modules/server-ce-scripts/scripts/change-compile-timeout.mjs --user-id=${userA.id} --compile-timeout=${newUserATimeout}`
  303. )
  304. })
  305. it('should change the timeout for user a', async function () {
  306. const actual = await getCompileTimeout(userA)
  307. expect(actual).to.not.equal(userATimeout)
  308. expect(actual).to.equal(newUserATimeout)
  309. })
  310. it('should leave the timeout for user b as is', async function () {
  311. expect(await getCompileTimeout(userB)).to.equal(userBTimeout)
  312. })
  313. })
  314. describe('bad options', function () {
  315. it('should reject zero timeout', async function () {
  316. await runAndExpectError(
  317. `node modules/server-ce-scripts/scripts/change-compile-timeout.mjs --user-id=${userA.id} --compile-timeout=0`,
  318. ['positive number of seconds'],
  319. 101
  320. )
  321. expect(await getCompileTimeout(userA)).to.equal(userATimeout)
  322. expect(await getCompileTimeout(userB)).to.equal(userBTimeout)
  323. })
  324. it('should reject a 20min timeout', async function () {
  325. await runAndExpectError(
  326. `node modules/server-ce-scripts/scripts/change-compile-timeout.mjs --user-id=${userA.id} --compile-timeout=1200`,
  327. ['below 10 minutes'],
  328. 101
  329. )
  330. expect(await getCompileTimeout(userA)).to.equal(userATimeout)
  331. expect(await getCompileTimeout(userB)).to.equal(userBTimeout)
  332. })
  333. })
  334. })
  335. describe('upgrade-user-features', function () {
  336. let userLatest, userSP1, userCustomTimeoutLower, userCustomTimeoutHigher
  337. beforeEach('create users', async function () {
  338. userLatest = new User()
  339. userSP1 = new User()
  340. userCustomTimeoutLower = new User()
  341. userCustomTimeoutHigher = new User()
  342. await Promise.all([
  343. userLatest.ensureUserExists(),
  344. userSP1.ensureUserExists(),
  345. userCustomTimeoutLower.ensureUserExists(),
  346. userCustomTimeoutHigher.ensureUserExists(),
  347. ])
  348. })
  349. const serverPro1Features = {
  350. collaborators: -1,
  351. dropbox: true,
  352. versioning: true,
  353. compileTimeout: 180,
  354. compileGroup: 'standard',
  355. references: true,
  356. trackChanges: true,
  357. }
  358. beforeEach('downgrade userSP1', async function () {
  359. await userSP1.mongoUpdate({ $set: { features: serverPro1Features } })
  360. })
  361. beforeEach('downgrade userCustomTimeoutLower', async function () {
  362. await run(
  363. `node modules/server-ce-scripts/scripts/change-compile-timeout.mjs --user-id=${userCustomTimeoutLower.id} --compile-timeout=42`
  364. )
  365. })
  366. beforeEach('upgrade userCustomTimeoutHigher', async function () {
  367. await run(
  368. `node modules/server-ce-scripts/scripts/change-compile-timeout.mjs --user-id=${userCustomTimeoutHigher.id} --compile-timeout=360`
  369. )
  370. })
  371. async function getFeatures() {
  372. return [
  373. await userLatest.getFeatures(),
  374. await userSP1.getFeatures(),
  375. await userCustomTimeoutLower.getFeatures(),
  376. await userCustomTimeoutHigher.getFeatures(),
  377. ]
  378. }
  379. let initialFeatures
  380. beforeEach('collect initial features', async function () {
  381. initialFeatures = await getFeatures()
  382. })
  383. it('should have prepared the right features', async function () {
  384. expect(initialFeatures).to.deep.equal([
  385. Settings.defaultFeatures,
  386. serverPro1Features,
  387. Object.assign({}, Settings.defaultFeatures, {
  388. compileTimeout: 42,
  389. }),
  390. Object.assign({}, Settings.defaultFeatures, {
  391. compileTimeout: 360,
  392. }),
  393. ])
  394. })
  395. describe('dry-run', function () {
  396. let output
  397. beforeEach('run script', async function () {
  398. output = await run(
  399. `node modules/server-ce-scripts/scripts/upgrade-user-features.mjs`
  400. )
  401. })
  402. it('should update SP1 features', function () {
  403. expect(output).to.include(userSP1.id)
  404. })
  405. it('should update lowerTimeout features', function () {
  406. expect(output).to.include(userCustomTimeoutLower.id)
  407. })
  408. it('should not update latest features', function () {
  409. expect(output).to.not.include(userLatest.id)
  410. })
  411. it('should not update higherTimeout features', function () {
  412. expect(output).to.not.include(userCustomTimeoutHigher.id)
  413. })
  414. it('should not change any features in the db', async function () {
  415. expect(await getFeatures()).to.deep.equal(initialFeatures)
  416. })
  417. })
  418. describe('live run', function () {
  419. let output
  420. beforeEach('run script', async function () {
  421. output = await run(
  422. `node modules/server-ce-scripts/scripts/upgrade-user-features.mjs --dry-run=false`
  423. )
  424. })
  425. it('should update SP1 features', function () {
  426. expect(output).to.include(userSP1.id)
  427. })
  428. it('should update lowerTimeout features', function () {
  429. expect(output).to.include(userCustomTimeoutLower.id)
  430. })
  431. it('should not update latest features', function () {
  432. expect(output).to.not.include(userLatest.id)
  433. })
  434. it('should not update higherTimeout features', function () {
  435. expect(output).to.not.include(userCustomTimeoutHigher.id)
  436. })
  437. it('should update features in the db', async function () {
  438. expect(await getFeatures()).to.deep.equal([
  439. Settings.defaultFeatures,
  440. Settings.defaultFeatures,
  441. Settings.defaultFeatures,
  442. Object.assign({}, Settings.defaultFeatures, {
  443. compileTimeout: 360,
  444. }),
  445. ])
  446. })
  447. })
  448. })
  449. describe('check-texlive-images', function () {
  450. const TEST_TL_IMAGE = 'sharelatex/texlive:2023'
  451. const TEST_TL_IMAGE_LIST =
  452. 'sharelatex/texlive:2021,sharelatex/texlive:2022,sharelatex/texlive:2023'
  453. let output
  454. function buildCheckTexLiveCmd({
  455. SANDBOXED_COMPILES,
  456. TEX_LIVE_DOCKER_IMAGE,
  457. ALL_TEX_LIVE_DOCKER_IMAGES,
  458. OVERLEAF_IS_SERVER_PRO = true,
  459. }) {
  460. let cmd = `SANDBOXED_COMPILES=${SANDBOXED_COMPILES ? 'true' : 'false'}`
  461. if (TEX_LIVE_DOCKER_IMAGE) {
  462. cmd += ` TEX_LIVE_DOCKER_IMAGE='${TEX_LIVE_DOCKER_IMAGE}'`
  463. }
  464. if (ALL_TEX_LIVE_DOCKER_IMAGES) {
  465. cmd += ` ALL_TEX_LIVE_DOCKER_IMAGES='${ALL_TEX_LIVE_DOCKER_IMAGES}'`
  466. }
  467. if (OVERLEAF_IS_SERVER_PRO === true) {
  468. cmd += ` OVERLEAF_IS_SERVER_PRO=${OVERLEAF_IS_SERVER_PRO}`
  469. }
  470. return (
  471. cmd + ' node modules/server-ce-scripts/scripts/check-texlive-images.mjs'
  472. )
  473. }
  474. beforeEach(async function () {
  475. const user = new User()
  476. await user.ensureUserExists()
  477. await user.login()
  478. await user.createProject('test-project')
  479. })
  480. describe('when running in CE', function () {
  481. beforeEach('run script', async function () {
  482. output = await run(
  483. buildCheckTexLiveCmd({ OVERLEAF_IS_SERVER_PRO: false })
  484. )
  485. })
  486. it('should skip checks', function () {
  487. expect(output).to.include(
  488. 'Running Overleaf Community Edition, skipping TexLive checks'
  489. )
  490. })
  491. })
  492. describe('when sandboxed compiles are disabled', function () {
  493. beforeEach('run script', async function () {
  494. output = await run(buildCheckTexLiveCmd({ SANDBOXED_COMPILES: false }))
  495. })
  496. it('should skip checks', function () {
  497. expect(output).to.include(
  498. 'Sandboxed compiles disabled, skipping TexLive checks'
  499. )
  500. })
  501. })
  502. describe('when texlive configuration is incorrect', function () {
  503. it('should fail when TEX_LIVE_DOCKER_IMAGE is not set', async function () {
  504. await runAndExpectError(
  505. buildCheckTexLiveCmd({
  506. SANDBOXED_COMPILES: true,
  507. ALL_TEX_LIVE_DOCKER_IMAGES: TEST_TL_IMAGE_LIST,
  508. }),
  509. [
  510. 'Sandboxed compiles require TEX_LIVE_DOCKER_IMAGE and ALL_TEX_LIVE_DOCKER_IMAGES being set',
  511. ]
  512. )
  513. })
  514. it('should fail when ALL_TEX_LIVE_DOCKER_IMAGES is not set', async function () {
  515. await runAndExpectError(
  516. buildCheckTexLiveCmd({
  517. SANDBOXED_COMPILES: true,
  518. TEX_LIVE_DOCKER_IMAGE: TEST_TL_IMAGE,
  519. }),
  520. [
  521. 'Sandboxed compiles require TEX_LIVE_DOCKER_IMAGE and ALL_TEX_LIVE_DOCKER_IMAGES being set',
  522. ]
  523. )
  524. })
  525. it('should fail when TEX_LIVE_DOCKER_IMAGE is not defined in ALL_TEX_LIVE_DOCKER_IMAGES', async function () {
  526. await runAndExpectError(
  527. buildCheckTexLiveCmd({
  528. SANDBOXED_COMPILES: true,
  529. TEX_LIVE_DOCKER_IMAGE: 'tl-1',
  530. ALL_TEX_LIVE_DOCKER_IMAGES: 'tl-2,tl-3',
  531. }),
  532. [
  533. 'TEX_LIVE_DOCKER_IMAGE must be included in ALL_TEX_LIVE_DOCKER_IMAGES',
  534. ]
  535. )
  536. })
  537. })
  538. describe(`when projects don't have 'imageName' set`, function () {
  539. beforeEach(async function () {
  540. await db.projects.updateMany({}, { $unset: { imageName: 1 } })
  541. })
  542. it('should fail and suggest running backfilling scripts', async function () {
  543. await runAndExpectError(
  544. buildCheckTexLiveCmd({
  545. SANDBOXED_COMPILES: true,
  546. TEX_LIVE_DOCKER_IMAGE: TEST_TL_IMAGE,
  547. ALL_TEX_LIVE_DOCKER_IMAGES: TEST_TL_IMAGE_LIST,
  548. }),
  549. [
  550. `'project.imageName' is not set for some projects`,
  551. `Set SKIP_TEX_LIVE_CHECK=true in config/variables.env, restart the instance and run 'bin/run-script scripts/backfill_project_image_name.mjs' to initialise TexLive image in existing projects`,
  552. ]
  553. )
  554. })
  555. })
  556. describe(`when projects have a null 'imageName'`, function () {
  557. beforeEach(async function () {
  558. await db.projects.updateMany({}, { $set: { imageName: null } })
  559. })
  560. it('should fail and suggest running backfilling scripts', async function () {
  561. await runAndExpectError(
  562. buildCheckTexLiveCmd({
  563. SANDBOXED_COMPILES: true,
  564. TEX_LIVE_DOCKER_IMAGE: TEST_TL_IMAGE,
  565. ALL_TEX_LIVE_DOCKER_IMAGES: TEST_TL_IMAGE_LIST,
  566. }),
  567. [
  568. `'project.imageName' is not set for some projects`,
  569. `Set SKIP_TEX_LIVE_CHECK=true in config/variables.env, restart the instance and run 'bin/run-script scripts/backfill_project_image_name.mjs' to initialise TexLive image in existing projects`,
  570. ]
  571. )
  572. })
  573. })
  574. describe('when TexLive ALL_TEX_LIVE_DOCKER_IMAGES are upgraded and used images are no longer available', function () {
  575. it('should suggest running a fixing script', async function () {
  576. await db.projects.updateMany({}, { $set: { imageName: TEST_TL_IMAGE } })
  577. await runAndExpectError(
  578. buildCheckTexLiveCmd({
  579. SANDBOXED_COMPILES: true,
  580. TEX_LIVE_DOCKER_IMAGE: 'tl-1',
  581. ALL_TEX_LIVE_DOCKER_IMAGES: 'tl-1,tl-2',
  582. }),
  583. [
  584. `Set SKIP_TEX_LIVE_CHECK=true in config/variables.env, restart the instance and run 'bin/run-script scripts/update_project_image_name.js <dangling_image> <new_image>' to update projects to a new image`,
  585. ]
  586. )
  587. })
  588. })
  589. describe('success scenarios', function () {
  590. beforeEach(async function () {
  591. await db.projects.updateMany({}, { $set: { imageName: TEST_TL_IMAGE } })
  592. })
  593. it('should succeed when there are no changes to the TexLive images', async function () {
  594. const output = await run(
  595. buildCheckTexLiveCmd({
  596. SANDBOXED_COMPILES: true,
  597. TEX_LIVE_DOCKER_IMAGE: TEST_TL_IMAGE,
  598. ALL_TEX_LIVE_DOCKER_IMAGES: TEST_TL_IMAGE_LIST,
  599. })
  600. )
  601. expect(output).to.include('Done.')
  602. })
  603. it('should succeed when there are valid changes to the TexLive images', async function () {
  604. const output = await run(
  605. buildCheckTexLiveCmd({
  606. SANDBOXED_COMPILES: true,
  607. TEX_LIVE_DOCKER_IMAGE: 'new-image',
  608. ALL_TEX_LIVE_DOCKER_IMAGES: TEST_TL_IMAGE_LIST + ',new-image',
  609. })
  610. )
  611. expect(output).to.include('Done.')
  612. })
  613. })
  614. })
  615. describe('transfer-all-projects-to-user', function () {
  616. let fromUser, projects
  617. beforeEach(async function () {
  618. fromUser = new User()
  619. await fromUser.login()
  620. projects = await Promise.all([
  621. fromUser.createProject('a'),
  622. fromUser.createProject('b'),
  623. fromUser.createProject('c'),
  624. ])
  625. })
  626. let toUser
  627. beforeEach(async function () {
  628. toUser = new User()
  629. await toUser.login()
  630. })
  631. it('should log missing user', async function () {
  632. const email = 'does-not-exist@example.com'
  633. await runAndExpectError(
  634. `node modules/server-ce-scripts/scripts/transfer-all-projects-to-user.mjs --from-user=${email}`,
  635. [`user with email --from-user=${email} does not exist`]
  636. )
  637. })
  638. it('should transfer projects by email', async function () {
  639. await run(
  640. `node modules/server-ce-scripts/scripts/transfer-all-projects-to-user.mjs --from-user=${fromUser.email} --to-user=${toUser.email}`
  641. )
  642. for (const projectId of projects) {
  643. expect(
  644. (await toUser.getProject(projectId)).owner_ref.toString()
  645. ).to.equal(toUser._id.toString())
  646. }
  647. })
  648. it('should transfer projects by id', async function () {
  649. await run(
  650. `node modules/server-ce-scripts/scripts/transfer-all-projects-to-user.mjs --from-user=${fromUser._id} --to-user=${toUser._id}`
  651. )
  652. for (const projectId of projects) {
  653. expect(
  654. (await toUser.getProject(projectId)).owner_ref.toString()
  655. ).to.equal(toUser._id.toString())
  656. }
  657. })
  658. })
  659. })