| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- import { describe, expect, vi, beforeEach } from 'vitest'
- import sinon from 'sinon'
- import FormData from 'form-data'
- import { FileTooLargeError } from '../../../../app/src/Features/Errors/Errors.js'
- const MODULE_PATH =
- '../../../../app/src/Features/Uploads/DocumentConversionManager.mjs'
- describe('DocumentConversionManager', function () {
- beforeEach(async function (ctx) {
- ctx.fs = {
- createReadStream: sinon.stub().returns('mocked-read-stream'),
- createWriteStream: sinon.stub().returns('mocked-write-stream'),
- }
- ctx.fsPromises = {
- unlink: sinon.stub().resolves(),
- }
- ctx.fetchUtils = {
- fetchStreamWithResponse: sinon.stub().resolves(),
- }
- ctx.nodeStream = {
- pipeline: sinon.stub().resolves(),
- }
- ctx.CompileManager = {
- promises: {
- _getUserCompileLimits: sinon.stub().resolves({
- compileBackendClass: 'test-backend-class',
- compileGroup: 'test-compile-group',
- }),
- },
- }
- ctx.Settings = {
- maxUploadSize: 100,
- path: {
- dumpFolder: '/path/to/dump/folder',
- },
- apis: {
- clsi: {
- url: 'http://mock-clsi-url',
- },
- },
- }
- vi.doMock('node:fs', () => ({
- default: ctx.fs,
- }))
- vi.doMock('node:fs/promises', () => ({
- default: ctx.fsPromises,
- }))
- vi.doMock('@overleaf/fetch-utils', () => ({
- fetchStreamWithResponse: ctx.fetchUtils.fetchStreamWithResponse,
- }))
- vi.doMock('node:stream/promises', () => ({
- pipeline: ctx.nodeStream.pipeline,
- }))
- vi.doMock('@overleaf/settings', () => ({
- default: ctx.Settings,
- }))
- vi.doMock(
- '../../../../app/src/Features/Compile/CompileManager.mjs',
- () => ({
- default: ctx.CompileManager,
- })
- )
- ctx.DocumentConversionManager = (await import(MODULE_PATH)).default
- })
- describe('convertDocxToLaTeXZipArchive', function () {
- describe('successfully', function () {
- beforeEach(async function (ctx) {
- ctx.path = '/path/to/input.docx'
- ctx.userId = 'test-user-id'
- ctx.outputPath = '/path/to/output.zip'
- ctx.response = {
- headers: {
- get: sinon.stub().returns(null),
- },
- }
- ctx.response.headers.get.withArgs('Content-Length').returns('50')
- ctx.fetchUtils.fetchStreamWithResponse.resolves({
- stream: 'mocked-fetch-stream',
- response: ctx.response,
- })
- ctx.result =
- await ctx.DocumentConversionManager.promises.convertDocxToLaTeXZipArchive(
- ctx.path,
- ctx.userId
- )
- })
- it('should call fetchStreamWithResponse with the correct URL and form data', function (ctx) {
- const expectedUrl = new URL(ctx.Settings.apis.clsi.url)
- expectedUrl.pathname = '/convert/docx-to-latex'
- expectedUrl.searchParams.set(
- 'compileBackendClass',
- 'test-backend-class'
- )
- expectedUrl.searchParams.set('compileGroup', 'test-compile-group')
- sinon.assert.calledWith(
- ctx.fetchUtils.fetchStreamWithResponse,
- sinon.match(url => url.toString() === expectedUrl.toString()),
- {
- method: 'POST',
- body: sinon.match.instanceOf(FormData),
- signal: sinon.match.instanceOf(AbortSignal),
- }
- )
- })
- it('should pipe result into the output file', function (ctx) {
- sinon.assert.calledWith(
- ctx.nodeStream.pipeline,
- 'mocked-fetch-stream',
- 'mocked-write-stream'
- )
- })
- it('should return a path to the output file', function (ctx) {
- expect(ctx.result).to.match(
- /\/path\/to\/dump\/folder\/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.zip/
- )
- })
- })
- describe('when an error occurs during conversion', function () {
- beforeEach(async function (ctx) {
- ctx.path = '/path/to/input.docx'
- ctx.userId = 'test-user-id'
- ctx.fetchUtils.fetchStreamWithResponse.rejects(
- new Error('Conversion failed')
- )
- await expect(
- ctx.DocumentConversionManager.promises.convertDocxToLaTeXZipArchive(
- ctx.path,
- ctx.userId
- )
- ).to.be.rejectedWith('document conversion failed')
- })
- it('should attempt to clean up the output file', function (ctx) {
- sinon.assert.calledWith(
- ctx.fsPromises.unlink,
- sinon.match(
- /\/path\/to\/dump\/folder\/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.zip/
- )
- )
- })
- })
- describe('when the converted archive is too large', function () {
- beforeEach(async function (ctx) {
- ctx.path = '/path/to/input.docx'
- ctx.userId = 'test-user-id'
- ctx.stream = {
- destroy: sinon.stub(),
- }
- ctx.response = {
- headers: {
- get: sinon.stub(),
- },
- }
- ctx.response.headers.get.withArgs('Content-Length').returns('150')
- ctx.fetchUtils.fetchStreamWithResponse.resolves({
- stream: ctx.stream,
- response: ctx.response,
- })
- await expect(
- ctx.DocumentConversionManager.promises.convertDocxToLaTeXZipArchive(
- ctx.path,
- ctx.userId
- )
- ).to.be.rejectedWith(sinon.match.instanceOf(FileTooLargeError))
- })
- it('should abort the request', function (ctx) {
- expect(
- ctx.fetchUtils.fetchStreamWithResponse.firstCall.args[1].signal
- .aborted
- ).to.equal(true)
- })
- it('should destroy the response stream', function (ctx) {
- sinon.assert.calledOnce(ctx.stream.destroy)
- })
- it('should not write the oversized archive to disk', function (ctx) {
- sinon.assert.notCalled(ctx.fs.createWriteStream)
- sinon.assert.notCalled(ctx.nodeStream.pipeline)
- })
- it('should attempt to clean up the output path', function (ctx) {
- sinon.assert.calledWith(
- ctx.fsPromises.unlink,
- sinon.match(
- /\/path\/to\/dump\/folder\/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.zip/
- )
- )
- })
- })
- describe('when the Content-Length header is missing', function () {
- beforeEach(async function (ctx) {
- ctx.path = '/path/to/input.docx'
- ctx.userId = 'test-user-id'
- ctx.response = {
- headers: {
- get: sinon.stub().returns(null),
- },
- }
- ctx.fetchUtils.fetchStreamWithResponse.resolves({
- stream: 'mocked-fetch-stream',
- response: ctx.response,
- })
- await expect(
- ctx.DocumentConversionManager.promises.convertDocxToLaTeXZipArchive(
- ctx.path,
- ctx.userId
- )
- ).to.be.rejectedWith('document conversion failed')
- })
- it('should not write the archive to disk', function (ctx) {
- sinon.assert.notCalled(ctx.fs.createWriteStream)
- sinon.assert.notCalled(ctx.nodeStream.pipeline)
- })
- it('should attempt to clean up the output path', function (ctx) {
- sinon.assert.calledWith(
- ctx.fsPromises.unlink,
- sinon.match(
- /\/path\/to\/dump\/folder\/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.zip/
- )
- )
- })
- })
- })
- })
|