ServerCEScriptsTests.mjs 23 KB

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