mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-07 09:51:37 +00:00
87 lines
3.1 KiB
JavaScript
87 lines
3.1 KiB
JavaScript
const { expect } = require('chai')
|
|
const sinon = require('sinon')
|
|
const { PassThrough } = require('stream')
|
|
const path = require('path')
|
|
const fs = require('fs')
|
|
const os = require('os')
|
|
const zipHelpers = require('../../../server/utils/zipHelpers')
|
|
const Logger = require('../../../server/Logger')
|
|
|
|
describe('zipHelpers', () => {
|
|
let tmpDir
|
|
|
|
beforeEach(() => {
|
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'abs-zip-test-'))
|
|
sinon.stub(Logger, 'info')
|
|
sinon.stub(Logger, 'debug')
|
|
})
|
|
|
|
afterEach(() => {
|
|
sinon.restore()
|
|
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
})
|
|
|
|
describe('zipDirectoryPipe', () => {
|
|
function makeMockRes() {
|
|
const stream = new PassThrough()
|
|
stream.attachment = sinon.spy()
|
|
// zipDirectoryPipe resolves on 'close'. PassThrough emits 'finish' but not 'close'
|
|
// automatically — destroy() triggers 'close', so bridge the two events here.
|
|
stream.on('finish', () => stream.destroy())
|
|
return stream
|
|
}
|
|
|
|
it('should call res.attachment with the provided filename', async () => {
|
|
fs.writeFileSync(path.join(tmpDir, 'book.mp3'), 'audio content')
|
|
const res = makeMockRes()
|
|
await zipHelpers.zipDirectoryPipe(tmpDir, 'my-book.zip', res)
|
|
expect(res.attachment.calledWith('my-book.zip')).to.be.true
|
|
})
|
|
|
|
it('should resolve and produce data for a non-empty directory', async () => {
|
|
fs.writeFileSync(path.join(tmpDir, 'chapter1.mp3'), 'audio data chapter 1')
|
|
fs.writeFileSync(path.join(tmpDir, 'chapter2.mp3'), 'audio data chapter 2')
|
|
|
|
const res = makeMockRes()
|
|
const chunks = []
|
|
res.on('data', (chunk) => chunks.push(chunk))
|
|
|
|
await zipHelpers.zipDirectoryPipe(tmpDir, 'book.zip', res)
|
|
const totalBytes = chunks.reduce((sum, c) => sum + c.length, 0)
|
|
expect(totalBytes).to.be.greaterThan(0)
|
|
})
|
|
|
|
it('should resolve for an empty directory', async () => {
|
|
const res = makeMockRes()
|
|
// Should not throw
|
|
await zipHelpers.zipDirectoryPipe(tmpDir, 'empty.zip', res)
|
|
})
|
|
|
|
it('should produce a valid zip containing the expected files', async () => {
|
|
fs.writeFileSync(path.join(tmpDir, 'test.txt'), 'Hello audiobookshelf')
|
|
const res = makeMockRes()
|
|
const chunks = []
|
|
res.on('data', (chunk) => chunks.push(chunk))
|
|
|
|
await zipHelpers.zipDirectoryPipe(tmpDir, 'archive.zip', res)
|
|
|
|
// ZIP files start with the PK signature (0x50 0x4B)
|
|
const combined = Buffer.concat(chunks)
|
|
expect(combined[0]).to.equal(0x50) // 'P'
|
|
expect(combined[1]).to.equal(0x4b) // 'K'
|
|
})
|
|
|
|
it('should resolve (not throw) when the source directory does not exist', async () => {
|
|
// archiver treats a missing directory as a warning (ENOENT), not a fatal error,
|
|
// so zipDirectoryPipe resolves with an empty archive rather than rejecting.
|
|
const res = makeMockRes()
|
|
let threw = false
|
|
try {
|
|
await zipHelpers.zipDirectoryPipe('/nonexistent/path/that/does/not/exist', 'fail.zip', res)
|
|
} catch (e) {
|
|
threw = true
|
|
}
|
|
expect(threw).to.be.false
|
|
})
|
|
})
|
|
})
|