ConversionTests.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import Client from './helpers/Client.js'
  2. import ClsiApp from './helpers/ClsiApp.js'
  3. import Path from 'node:path'
  4. import fs from 'node:fs'
  5. import { pipeline } from 'node:stream/promises'
  6. import { buffer } from 'node:stream/consumers'
  7. import yauzl from 'yauzl'
  8. import { expect } from 'chai'
  9. import { fetchStreamWithResponse } from '@overleaf/fetch-utils'
  10. import Settings from '@overleaf/settings'
  11. describe('Conversions', function () {
  12. describe('docx conversion', function () {
  13. before(async function () {
  14. await ClsiApp.ensureRunning()
  15. try {
  16. this.body = await Client.compile(this.project_id, this.request)
  17. } catch (error) {
  18. this.error = error
  19. }
  20. })
  21. it('should convert file to docx', async function () {
  22. const sourcePath = Path.join(
  23. import.meta.dirname,
  24. '../fixtures/conversion-source.docx'
  25. )
  26. const outputStream = fs.createWriteStream(
  27. '/tmp/clsi_acceptance_tests_' + crypto.randomUUID() + '.zip'
  28. )
  29. const stream = await Client.convertDocument(sourcePath, 'docx')
  30. await pipeline(stream, outputStream)
  31. await new Promise((resolve, reject) => {
  32. yauzl.open(outputStream.path, { lazyEntries: true }, (err, zipfile) => {
  33. if (err) {
  34. return reject(err)
  35. }
  36. zipfile.on('error', reject)
  37. zipfile.on('end', resolve)
  38. zipfile.readEntry()
  39. zipfile.on('entry', entry => {
  40. if (entry.fileName === 'main.tex') {
  41. zipfile.openReadStream(entry, (err, readStream) => {
  42. if (err) {
  43. return reject(err)
  44. }
  45. let data = ''
  46. readStream.on('data', chunk => {
  47. data += chunk.toString()
  48. })
  49. readStream.on('end', () => {
  50. try {
  51. expect(data).to.include('\\begin{document}')
  52. expect(data).to.include(
  53. '\\[x = \\frac{- b \\pm \\sqrt{b^{2} - 4ac}}{2a}\\]'
  54. )
  55. zipfile.readEntry()
  56. } catch (err) {
  57. reject(err)
  58. }
  59. })
  60. })
  61. } else if (entry.fileName === 'media/') {
  62. // Skip the media directory entry
  63. zipfile.readEntry()
  64. } else if (entry.fileName.startsWith('media/')) {
  65. expect(entry.fileName).to.equal('media/image1.png')
  66. zipfile.readEntry()
  67. } else {
  68. reject(new Error('Unexpected file in zip: ' + entry.fileName))
  69. }
  70. })
  71. })
  72. })
  73. })
  74. it('should fail when file is not a docx', async function () {
  75. const sourcePath = Path.join(
  76. import.meta.dirname,
  77. '../fixtures/minimal.pdf'
  78. )
  79. await expect(Client.convertDocument(sourcePath, 'docx')).to.eventually.be
  80. .rejected
  81. })
  82. })
  83. describe('project-to-document conversion (responseFormat=json)', function () {
  84. before(async function () {
  85. await ClsiApp.ensureRunning()
  86. })
  87. it('returns ids and serves the docx output via nginx', async function () {
  88. const projectId = Client.randomId()
  89. const userId = '0123456789abcdef01234567'
  90. const request = {
  91. rootResourcePath: 'main.tex',
  92. resources: [
  93. {
  94. path: 'main.tex',
  95. content: `\
  96. \\documentclass{article}
  97. \\begin{document}
  98. Hello world
  99. \\end{document}\
  100. `,
  101. },
  102. ],
  103. }
  104. const { conversionId, buildId, file } =
  105. await Client.convertProjectToDocument(
  106. projectId,
  107. userId,
  108. 'docx',
  109. request,
  110. 'json'
  111. )
  112. expect(conversionId).to.match(
  113. /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
  114. )
  115. expect(buildId).to.match(/^[0-9a-f]+-[0-9a-f]+$/)
  116. const downloadUrl = new URL(Settings.apis.clsi.downloadHost)
  117. downloadUrl.pathname = `/project/${conversionId}/build/${buildId}/output/${file}`
  118. const { stream, response } = await fetchStreamWithResponse(
  119. downloadUrl.href
  120. )
  121. expect(response.status).to.equal(200)
  122. const body = await buffer(stream)
  123. expect(body.length).to.be.greaterThan(0)
  124. // .docx is a zip archive — verify the PK\x03\x04 magic bytes
  125. expect(body[0]).to.equal(0x50)
  126. expect(body[1]).to.equal(0x4b)
  127. expect(body[2]).to.equal(0x03)
  128. expect(body[3]).to.equal(0x04)
  129. })
  130. })
  131. })