UNPKG

zipster

Version:

TypeScript library built for Node backends to create ZIP files with password protection

377 lines (303 loc) 10.9 kB
import * as fs from 'fs' import * as os from 'os' import * as uuid from 'uuid' import * as Stream from 'stream' import { IOptions } from 'glob' import { createSandbox } from 'sinon' import { mockArchiver } from '../../mocks/MockArchiver' import { Formats, Options, Zipster, ZipsterError } from '../../../src' import { ArchiverFactory } from '../../../src/factories/ArchiverFactory' describe('Zipster', () => { const sandbox = createSandbox() const file = '/some/path/to/file.txt' const defaultFileName = 'xxxx-xxxx-xxxx-xxxx' const expectedOutputFile = `/some/path/to/${defaultFileName}.zip` const options: Options = { format: Formats.ZIP } const stream = new Stream() const buffer = new Buffer('') const error = new Error('Something strange is afoot.') let tmpdir: any let archiver: any let getArchiver: any let readFileSync: any let createWriteStream: any beforeEach(() => { archiver = mockArchiver(sandbox) tmpdir = sandbox.stub(os, 'tmpdir') readFileSync = sandbox.stub(fs, 'readFileSync') createWriteStream = sandbox.stub(fs, 'createWriteStream') getArchiver = sandbox.stub(ArchiverFactory, 'getArchiver') sandbox.stub(uuid, 'v4') .returns(defaultFileName as any) }) afterEach(() => sandbox.restore()) describe('#fromPath', () => { beforeEach(() => { tmpdir.onFirstCall() .returns('/some/path/to') createWriteStream.onFirstCall() .returns(stream) getArchiver.onFirstCall() .returns(archiver) archiver.pipe .onFirstCall() .returns() archiver.append .onFirstCall() .returns() archiver.finalize .onFirstCall() .resolves() }) it('resolves with the directory when the file is successfully zipped', () => { readFileSync.onFirstCall() .returns(buffer) return Zipster.fromPath(file, options) .should.become(expectedOutputFile) }) it('resolves and calls the dependencies appropriately', () => { const archiveData = { name: 'file.txt' } readFileSync.onFirstCall() .returns(buffer) return Zipster.fromPath(file, options) .should.become(expectedOutputFile) .then(() => { tmpdir.should.have.callCount(1) readFileSync.should.have.been.calledOnceWithExactly(file) createWriteStream.should.have.been.calledOnceWithExactly(expectedOutputFile) getArchiver.should.have.been.calledOnceWithExactly(options) archiver.pipe.should.have.been.calledOnceWithExactly(stream) archiver.append.should.have.been.calledOnceWithExactly(buffer, archiveData) archiver.finalize.should.have.callCount(1) }) }) it('rejects with a `ZipsterError` when an error is thrown', () => { tmpdir.onFirstCall() .returns('/some/path/to') readFileSync.onFirstCall() .throws(error) return Zipster.fromPath(file, options) .should.be.rejectedWith(ZipsterError, error.message) }) }) describe('#fromPaths', () => { const directories = [ file, '/some/path/to/other.csv' ] beforeEach(() => { tmpdir.onFirstCall() .returns('/some/path/to') createWriteStream.onFirstCall() .returns(stream) getArchiver.onFirstCall() .returns(archiver) archiver.pipe .returns() archiver.append .returns() archiver.finalize .resolves() }) it('resolves with the directory when the file is successfully zipped', () => { readFileSync.returns(buffer) return Zipster.fromPaths(directories, options) .should.become(expectedOutputFile) }) it('resolves with the configured directory when the file is successfully zipped', () => { const options: Options = { format: Formats.ZIP, output: { name: 'custom', path: '/foo/bar' } } const expectedDirectory = `${options.output.path}/${options.output.name}.zip` readFileSync.returns(buffer) return Zipster.fromPaths(directories, options) .should.become(expectedDirectory) }) it('resolves with the configured directory when the file is successfully zipped', () => { const options: Options = { format: Formats.ZIP, output: {} } readFileSync.returns(buffer) return Zipster.fromPaths(directories, options) .should.become(expectedOutputFile) }) it('resolves after calling the appropriate dependencies', () => { readFileSync.returns(buffer) return Zipster.fromPaths(directories, options) .should.be.fulfilled .then(() => { tmpdir.should.have.callCount(1) readFileSync.should.have.callCount(2) readFileSync.should.have.been.calledWithExactly(directories[0]) readFileSync.should.have.been.calledWithExactly(directories[1]) createWriteStream.should.have.been.calledOnceWithExactly(expectedOutputFile) getArchiver.should.have.been.calledOnceWithExactly(options) archiver.pipe.should.have.callCount(1) archiver.pipe.should.have.been.calledWithExactly(stream) archiver.append.should.have.callCount(2) archiver.append.should.have.been.calledWithExactly(buffer, { name: 'file.txt' }) archiver.append.should.have.been.calledWithExactly(buffer, { name: 'other.csv' }) archiver.finalize.should.have.callCount(1) }) }) it('rejects with a `ZipsterError` when an error is thrown', () => { readFileSync.onFirstCall() .throws(error) return Zipster.fromPaths(directories, options) .should.be.rejectedWith(ZipsterError, error.message) }) }) describe('#fromDirectory', () => { const path = '/some/path/to/my/directory' const expectedPath = `${path}/${defaultFileName}.zip` beforeEach(() => { tmpdir.onFirstCall() .returns(path) createWriteStream.onFirstCall() .returns(stream) getArchiver.onFirstCall() .returns(archiver) archiver.pipe .onFirstCall() .returns() archiver.directory .onFirstCall() .returns() }) it('resolves with the output directory of the zipped directory', () => { archiver.finalize .onFirstCall() .resolves() return Zipster.fromDirectory(path, options) .should.become(expectedPath) }) it('resolves and calls dependencies appropriately', () => { archiver.finalize .onFirstCall() .resolves() return Zipster.fromDirectory(path, options) .should.be.fulfilled .then(() => { tmpdir.should.have.callCount(1) createWriteStream.should.have.been.calledOnceWithExactly(expectedPath) getArchiver.should.have.been.calledOnceWithExactly(options) archiver.pipe.should.have.been.calledOnceWithExactly(stream) archiver.directory.should.have.been.calledOnceWithExactly(path, false) archiver.finalize.should.have.callCount(1) }) }) it('resolves with the configured output location and name', () => { const options: Options = { format: Formats.ZIP_ENCRYPTED, password: 'testing', output: { path: '/foo/bar', name: 'foobar' } } const expectedPath = `${options.output.path}/${options.output.name}.zip` archiver.finalize .onFirstCall() .resolves() return Zipster.fromDirectory(path, options) .should.become(expectedPath) .then(() => { getArchiver.should.have.been.calledOnceWithExactly(options) }) }) it('rejects with `ZipsterError` when the path is empty', () => { try { Zipster.fromDirectory(null as any, options) } catch (error) { error.should.be.instanceOf(ZipsterError) error.message.should.deep.equal('Path is required') return archiver.finalize.should.have.callCount(0) } }) it('rejects with `ZipsterError` when an error occurs', () => { archiver.finalize .onFirstCall() .rejects(error) return Zipster.fromDirectory(path, options) .should.be.rejectedWith(ZipsterError, error.message) }) }) describe('#fromPattern', () => { const pattern = 'foo*.txt' const path = '/path/to/my/file' const expectedOutputFile = `${path}/${defaultFileName}.zip` beforeEach(() => { tmpdir.onFirstCall() .returns(path) createWriteStream.onFirstCall() .returns(stream) getArchiver.onFirstCall() .returns(archiver) archiver.pipe .onFirstCall() .returns() archiver.glob .onFirstCall() .returns() }) it('resolves with the output location of the zipped files', () => { archiver.finalize .onFirstCall() .resolves() return Zipster.fromPattern(path, pattern, options) .should.become(expectedOutputFile) }) it('resolves and calls dependencies appropriately', () => { const globOptions: IOptions = { cwd: path } archiver.finalize .onFirstCall() .resolves() return Zipster.fromPattern(path, pattern, options) .should.be.fulfilled .then(() => { tmpdir.should.have.callCount(1) createWriteStream.should.have.been.calledOnceWithExactly(expectedOutputFile) getArchiver.should.have.been.calledOnceWithExactly(options) archiver.pipe.should.have.been.calledOnceWithExactly(stream) archiver.glob.should.have.been.calledOnceWithExactly(pattern, globOptions) archiver.finalize.should.have.callCount(1) }) }) it('rejects with `ZipsterError` when the path is empty', () => { try { Zipster.fromPattern(null as any, pattern, options) } catch (error) { error.should.be.instanceOf(ZipsterError) error.message.should.deep.equal('Path is required') return archiver.finalize.should.have.callCount(0) } }) it('rejects with `ZipsterError` when the pattern is empty', () => { try { Zipster.fromPattern(path, null as any, options) } catch (error) { error.should.be.instanceOf(ZipsterError) error.message.should.deep.equal('Pattern is required') return archiver.finalize.should.have.callCount(0) } }) it('rejects with `ZipsterError` when an error occurs finalizing the archive', () => { archiver.finalize .onFirstCall() .rejects(error) return Zipster.fromPattern(path, pattern, options) .should.be.rejectedWith(ZipsterError, error.message) }) }) })