| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- import { vi, assert, expect, describe, afterEach, beforeEach, it } from 'vitest'
- import sinon from 'sinon'
- import path from 'node:path'
- const MODULE_PATH = path.join(
- import.meta.dirname,
- '../../../app/js/OutputFileArchiveManager'
- )
- describe('OutputFileArchiveManager', () => {
- const userId = 'user-id-123'
- const projectId = 'project-id-123'
- const buildId = 'build-id-123'
- afterEach(() => {
- sinon.restore()
- })
- beforeEach(async ctx => {
- ctx.OutputFileFinder = {
- promises: {
- findOutputFiles: sinon.stub().resolves({ outputFiles: [] }),
- },
- }
- ctx.OutputCacheManger = {
- path: sinon.stub().callsFake((build, path) => {
- return `${build}/${path}`
- }),
- }
- ctx.archive = {
- append: sinon.stub(),
- finalize: sinon.stub().resolves(),
- on: sinon.stub(),
- }
- ctx.archiver = sinon.stub().returns(ctx.archive)
- ctx.outputDir = '/output/dir'
- ctx.fs = {
- open: sinon.stub().callsFake(file => ({
- createReadStream: sinon.stub().returns(`handle: ${file}`),
- })),
- }
- vi.doMock('../../../app/js/OutputFileFinder', () => ({
- default: ctx.OutputFileFinder,
- }))
- vi.doMock('../../../app/js/OutputCacheManager', () => ({
- default: ctx.OutputCacheManger,
- }))
- vi.doMock('archiver', () => ({
- default: ctx.archiver,
- }))
- vi.doMock('node:fs/promises', () => ctx.fs)
- vi.doMock('@overleaf/settings', () => ({
- default: {
- path: {
- outputDir: ctx.outputDir,
- },
- },
- }))
- ctx.OutputFileArchiveManager = (await import(MODULE_PATH)).default
- })
- describe('when the output cache directory contains only exportable files', () => {
- beforeEach(async ctx => {
- ctx.OutputFileFinder.promises.findOutputFiles.resolves({
- outputFiles: [
- { path: 'file_1' },
- { path: 'file_2' },
- { path: 'file_3' },
- { path: 'file_4' },
- ],
- })
- await ctx.OutputFileArchiveManager.archiveFilesForBuild(
- projectId,
- userId,
- buildId
- )
- })
- it('creates a zip archive', ctx => {
- sinon.assert.calledWith(ctx.archiver, 'zip')
- })
- it('listens to errors from the archive', ctx => {
- sinon.assert.calledWith(ctx.archive.on, 'error', sinon.match.func)
- })
- it('adds all the output files to the archive', ctx => {
- expect(ctx.archive.append.callCount).to.equal(4)
- sinon.assert.calledWith(
- ctx.archive.append,
- `handle: ${ctx.outputDir}/${projectId}-${userId}/${buildId}/file_1`,
- sinon.match({ name: 'file_1' })
- )
- sinon.assert.calledWith(
- ctx.archive.append,
- `handle: ${ctx.outputDir}/${projectId}-${userId}/${buildId}/file_2`,
- sinon.match({ name: 'file_2' })
- )
- sinon.assert.calledWith(
- ctx.archive.append,
- `handle: ${ctx.outputDir}/${projectId}-${userId}/${buildId}/file_3`,
- sinon.match({ name: 'file_3' })
- )
- sinon.assert.calledWith(
- ctx.archive.append,
- `handle: ${ctx.outputDir}/${projectId}-${userId}/${buildId}/file_4`,
- sinon.match({ name: 'file_4' })
- )
- })
- it('finalizes the archive after all files are appended', ctx => {
- sinon.assert.called(ctx.archive.finalize)
- expect(ctx.archive.finalize.calledBefore(ctx.archive.append)).to.be.false
- })
- })
- describe('when the directory includes files ignored by web', () => {
- beforeEach(async ctx => {
- ctx.OutputFileFinder.promises.findOutputFiles.resolves({
- outputFiles: [
- { path: 'file_1' },
- { path: 'file_2' },
- { path: 'file_3' },
- { path: 'file_4' },
- { path: 'output.pdf' },
- ],
- })
- await ctx.OutputFileArchiveManager.archiveFilesForBuild(
- projectId,
- userId,
- buildId
- )
- })
- it('only includes the non-ignored files in the archive', ctx => {
- expect(ctx.archive.append.callCount).to.equal(4)
- sinon.assert.calledWith(
- ctx.archive.append,
- `handle: ${ctx.outputDir}/${projectId}-${userId}/${buildId}/file_1`,
- sinon.match({ name: 'file_1' })
- )
- sinon.assert.calledWith(
- ctx.archive.append,
- `handle: ${ctx.outputDir}/${projectId}-${userId}/${buildId}/file_2`,
- sinon.match({ name: 'file_2' })
- )
- sinon.assert.calledWith(
- ctx.archive.append,
- `handle: ${ctx.outputDir}/${projectId}-${userId}/${buildId}/file_3`,
- sinon.match({ name: 'file_3' })
- )
- sinon.assert.calledWith(
- ctx.archive.append,
- `handle: ${ctx.outputDir}/${projectId}-${userId}/${buildId}/file_4`,
- sinon.match({ name: 'file_4' })
- )
- })
- })
- describe('when one of the files is called output.pdf', () => {
- beforeEach(async ctx => {
- ctx.OutputFileFinder.promises.findOutputFiles.resolves({
- outputFiles: [
- { path: 'file_1' },
- { path: 'file_2' },
- { path: 'file_3' },
- { path: 'file_4' },
- { path: 'output.pdf' },
- ],
- })
- await ctx.OutputFileArchiveManager.archiveFilesForBuild(
- projectId,
- userId,
- buildId
- )
- })
- it('does not include that file in the archive', ctx => {
- expect(ctx.archive.append.callCount).to.equal(4)
- sinon.assert.calledWith(
- ctx.archive.append,
- `handle: ${ctx.outputDir}/${projectId}-${userId}/${buildId}/file_1`,
- sinon.match({ name: 'file_1' })
- )
- sinon.assert.calledWith(
- ctx.archive.append,
- `handle: ${ctx.outputDir}/${projectId}-${userId}/${buildId}/file_2`,
- sinon.match({ name: 'file_2' })
- )
- sinon.assert.calledWith(
- ctx.archive.append,
- `handle: ${ctx.outputDir}/${projectId}-${userId}/${buildId}/file_3`,
- sinon.match({ name: 'file_3' })
- )
- sinon.assert.calledWith(
- ctx.archive.append,
- `handle: ${ctx.outputDir}/${projectId}-${userId}/${buildId}/file_4`,
- sinon.match({ name: 'file_4' })
- )
- })
- })
- describe('when the output directory cannot be accessed', () => {
- beforeEach(async ctx => {
- ctx.OutputFileFinder.promises.findOutputFiles.rejects({
- code: 'ENOENT',
- })
- })
- it('rejects with a NotFoundError', async ctx => {
- try {
- await ctx.OutputFileArchiveManager.archiveFilesForBuild(
- projectId,
- userId,
- buildId
- )
- assert.fail('should have thrown a NotFoundError')
- } catch (err) {
- expect(err).to.haveOwnProperty('name', 'NotFoundError')
- }
- })
- it('does not create an archive', ctx => {
- expect(ctx.archiver.called).to.be.false
- })
- })
- })
|