ServerCEScriptsTests.mjs 23 KB

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