From 2892e99384f9a1d17f4dfc204226a14e2695e922 Mon Sep 17 00:00:00 2001 From: wakamex Date: Mon, 29 Jun 2026 16:57:44 -0400 Subject: [PATCH] Add regression test for book duration with disabled tracks Demonstrates #5311: disabling an audio track via updateTracks does not recompute the stored book duration, so it keeps counting the excluded track. This test fails until the fix is applied (expects duration to drop to only the enabled track's length). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../controllers/LibraryItemController.test.js | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/server/controllers/LibraryItemController.test.js b/test/server/controllers/LibraryItemController.test.js index 1d57219d3..c19059533 100644 --- a/test/server/controllers/LibraryItemController.test.js +++ b/test/server/controllers/LibraryItemController.test.js @@ -8,6 +8,7 @@ const LibraryItemController = require('../../../server/controllers/LibraryItemCo const ApiCacheManager = require('../../../server/managers/ApiCacheManager') const Auth = require('../../../server/Auth') const Logger = require('../../../server/Logger') +const SocketAuthority = require('../../../server/SocketAuthority') describe('LibraryItemController', () => { /** @type {ApiRouter} */ @@ -299,4 +300,41 @@ describe('LibraryItemController', () => { expect(fakeRes.sendStatus.calledWith(403)).to.be.true }) }) + + describe('updateTracks', () => { + it('recomputes book duration to exclude disabled audio tracks', async () => { + const library = await Database.libraryModel.create({ name: 'Tracks Library', mediaType: 'book' }) + const libraryFolder = await Database.libraryFolderModel.create({ path: '/tracks', libraryId: library.id }) + + // Book with two audio tracks (100s + 50s) and a stored duration of 150s + const audioFiles = [ + { index: 1, ino: 'file-1', duration: 100, exclude: false, metadata: { filename: 'track1.mp3', size: 1000 } }, + { index: 2, ino: 'file-2', duration: 50, exclude: false, metadata: { filename: 'track2.mp3', size: 1000 } } + ] + const book = await Database.bookModel.create({ title: 'Two Track Book', duration: 150, audioFiles, tags: [], narrators: [], genres: [], chapters: [] }) + const libraryItem = await Database.libraryItemModel.create({ libraryFiles: [], mediaId: book.id, mediaType: 'book', libraryId: library.id, libraryFolderId: libraryFolder.id }) + + sinon.stub(SocketAuthority, 'libraryItemEmitter') + + const expandedLibraryItem = await Database.libraryItemModel.getExpandedById(libraryItem.id) + const fakeReq = { + libraryItem: expandedLibraryItem, + body: { + orderedFileData: [ + { ino: 'file-1', exclude: false }, + { ino: 'file-2', exclude: true } + ] + } + } + const fakeRes = { json: sinon.spy(), sendStatus: sinon.spy() } + + await LibraryItemController.updateTracks.bind(apiRouter)(fakeReq, fakeRes) + + expect(fakeRes.json.calledOnce).to.be.true + + // Duration should now reflect only the enabled track (100s), not the original 150s + const updatedBook = await Database.bookModel.findByPk(book.id) + expect(updatedBook.duration).to.equal(100) + }) + }) })