mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-03-01 05:29:41 +00:00
148 lines
5.4 KiB
JavaScript
148 lines
5.4 KiB
JavaScript
const { expect } = require('chai')
|
|
const { Sequelize } = require('sequelize')
|
|
const sinon = require('sinon')
|
|
const fs = require('../../../server/libs/fsExtra')
|
|
const Path = require('path')
|
|
|
|
const Database = require('../../../server/Database')
|
|
const ApiRouter = require('../../../server/routers/ApiRouter')
|
|
const LibraryItemController = require('../../../server/controllers/LibraryItemController')
|
|
const ApiCacheManager = require('../../../server/managers/ApiCacheManager')
|
|
const Auth = require('../../../server/Auth')
|
|
const Logger = require('../../../server/Logger')
|
|
const LibraryItemScanner = require('../../../server/scanner/LibraryItemScanner')
|
|
|
|
describe('LibraryItemController Merge', () => {
|
|
/** @type {ApiRouter} */
|
|
let apiRouter
|
|
|
|
beforeEach(async () => {
|
|
global.ServerSettings = {}
|
|
Database.sequelize = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
|
|
Database.sequelize.uppercaseFirst = (str) => (str ? `${str[0].toUpperCase()}${str.substr(1)}` : '')
|
|
await Database.buildModels()
|
|
|
|
apiRouter = new ApiRouter({
|
|
auth: new Auth(),
|
|
apiCacheManager: new ApiCacheManager()
|
|
})
|
|
|
|
sinon.stub(Logger, 'info')
|
|
sinon.stub(Logger, 'error')
|
|
sinon.stub(Logger, 'warn')
|
|
|
|
// Mock fs-extra methods
|
|
sinon.stub(fs, 'ensureDir').resolves()
|
|
sinon.stub(fs, 'move').resolves()
|
|
sinon.stub(fs, 'pathExists').resolves(false)
|
|
sinon.stub(fs, 'readdir').resolves([])
|
|
sinon.stub(fs, 'remove').resolves()
|
|
sinon.stub(fs, 'rmdir').resolves()
|
|
|
|
// Mock Scanner
|
|
sinon.stub(LibraryItemScanner, 'scanLibraryItem').resolves()
|
|
})
|
|
|
|
afterEach(async () => {
|
|
sinon.restore()
|
|
// Clear all tables
|
|
await Database.sequelize.sync({ force: true })
|
|
})
|
|
|
|
describe('batchMerge', () => {
|
|
it('should merge two file-based items into a new folder', async () => {
|
|
// Setup Library and Folder
|
|
const library = await Database.libraryModel.create({ name: 'Book Lib', mediaType: 'book' })
|
|
const libraryFolder = await Database.libraryFolderModel.create({ path: '/books', libraryId: library.id })
|
|
|
|
// Item 1: File in root
|
|
const book1 = await Database.bookModel.create({ title: 'Book 1', audioFiles: [] })
|
|
const item1 = await Database.libraryItemModel.create({
|
|
mediaId: book1.id,
|
|
mediaType: 'book',
|
|
libraryId: library.id,
|
|
libraryFolderId: libraryFolder.id,
|
|
path: '/books/book1.mp3',
|
|
relPath: 'book1.mp3',
|
|
isFile: true
|
|
})
|
|
// Create Author
|
|
const author = await Database.authorModel.create({ name: 'Author 1', libraryId: library.id })
|
|
await Database.bookAuthorModel.create({ bookId: book1.id, authorId: author.id })
|
|
|
|
await item1.save()
|
|
|
|
// Item 2: File in root
|
|
const book2 = await Database.bookModel.create({ title: 'Book 1 Part 2', audioFiles: [] })
|
|
const item2 = await Database.libraryItemModel.create({
|
|
mediaId: book2.id,
|
|
mediaType: 'book',
|
|
libraryId: library.id,
|
|
libraryFolderId: libraryFolder.id,
|
|
path: '/books/book2.mp3',
|
|
relPath: 'book2.mp3',
|
|
isFile: true
|
|
})
|
|
item2.media = book2
|
|
|
|
// Mock user
|
|
const user = { canDelete: true, username: 'admin' }
|
|
|
|
const req = {
|
|
user,
|
|
body: {
|
|
libraryItemIds: [item1.id, item2.id]
|
|
}
|
|
}
|
|
|
|
const res = {
|
|
json: sinon.spy(),
|
|
sendStatus: sinon.spy(),
|
|
status: sinon.stub().returnsThis(),
|
|
send: sinon.spy()
|
|
}
|
|
|
|
await LibraryItemController.batchMerge.bind(apiRouter)(req, res)
|
|
|
|
// Verify response
|
|
if (res.status.called) {
|
|
console.log('Error status:', res.status.args[0])
|
|
console.log('Error send:', res.send.args[0])
|
|
}
|
|
expect(res.json.calledOnce).to.be.true
|
|
const result = res.json.args[0][0]
|
|
expect(result.success).to.be.true
|
|
expect(result.successIds).to.have.lengthOf(1) // Only item2 is "processed" (deleted), item1 is updated
|
|
expect(result.successIds).to.include(item2.id)
|
|
|
|
// Verify fs calls
|
|
// Should create folder "Author 1 - Book 1"
|
|
const expectedFolderPath = Path.join('/books', 'Author 1 - Book 1')
|
|
expect(fs.ensureDir.calledWith(expectedFolderPath)).to.be.true
|
|
|
|
// Should move item1
|
|
expect(fs.move.calledWith('/books/book1.mp3', Path.join(expectedFolderPath, 'book1.mp3'))).to.be.true
|
|
|
|
// Should move item2
|
|
expect(fs.move.calledWith('/books/book2.mp3', Path.join(expectedFolderPath, 'book2.mp3'))).to.be.true
|
|
|
|
// item1 should be updated (path change) - NOT checked here because scanLibraryItem is mocked and it handles the update
|
|
// const updatedItem1 = await Database.libraryItemModel.findByPk(item1.id)
|
|
// expect(updatedItem1.path).to.equal(Path.join(expectedFolderPath, 'book1.mp3'))
|
|
|
|
// item2 should be deleted
|
|
const updatedItem2 = await Database.libraryItemModel.findByPk(item2.id)
|
|
expect(updatedItem2).to.be.null
|
|
|
|
// Verify Scanner called
|
|
expect(LibraryItemScanner.scanLibraryItem.called).to.be.true
|
|
const scanArgs = LibraryItemScanner.scanLibraryItem.firstCall.args
|
|
expect(scanArgs[0]).to.equal(item1.id)
|
|
expect(scanArgs[1]).to.deep.equal({
|
|
path: expectedFolderPath,
|
|
relPath: 'Author 1 - Book 1',
|
|
isFile: false
|
|
})
|
|
})
|
|
})
|
|
})
|