mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-03-01 05:29:41 +00:00
Allow books to be merged
This commit is contained in:
parent
fc97b10f58
commit
56eca37304
9 changed files with 615 additions and 25 deletions
148
test/server/controllers/LibraryItemController_merge.test.js
Normal file
148
test/server/controllers/LibraryItemController_merge.test.js
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
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
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue