ServerCEScriptsTests.mjs 23 KB

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