fix: tighten browse keyset fallback rules

This commit is contained in:
Jonathan Finley 2026-03-13 21:49:30 -04:00
parent b701b6efb6
commit 2d13ec3f8c
4 changed files with 132 additions and 30 deletions

View file

@ -5,6 +5,7 @@ const sinon = require('sinon')
const Database = require('../../../server/Database')
const LibraryController = require('../../../server/controllers/LibraryController')
const libraryFilters = require('../../../server/utils/queries/libraryFilters')
const { getLibraryBrowseStrategy } = require('../../../server/utils/queries/libraryBrowseStrategy')
const libraryItemsBookFilters = require('../../../server/utils/queries/libraryItemsBookFilters')
const libraryItemsPodcastFilters = require('../../../server/utils/queries/libraryItemsPodcastFilters')
@ -226,4 +227,93 @@ describe('LibraryController large-library browse contract', () => {
expect(countStub.called).to.equal(false)
expect(findAndCountAllStub.called).to.equal(false)
})
it('falls back to offset mode for endless progress browse because null-aware keyset is unsupported', () => {
const strategy = getLibraryBrowseStrategy({
mediaType: 'book',
sortBy: 'progress',
filterGroup: 'progress',
pageMode: 'endless'
})
expect(strategy.paginationMode).to.equal('offset')
expect(strategy.countMode).to.equal('exact-on-initial-page')
expect(strategy.deepScrollAllowed).to.equal(false)
})
it('falls back to offset mode for collapsed-series endless title browse because the SQL sort key differs', () => {
global.ServerSettings = { sortingIgnorePrefix: true }
const strategy = getLibraryBrowseStrategy({
mediaType: 'book',
sortBy: 'media.metadata.title',
filterGroup: 'series',
pageMode: 'endless',
collapseseries: true
})
expect(strategy.paginationMode).to.equal('offset')
expect(strategy.countMode).to.equal('exact-on-initial-page')
})
it('adds a deterministic libraryItem.id tie-breaker for keyset title browse with duplicate titles', async () => {
global.ServerSettings = { sortingIgnorePrefix: true }
const countStub = sinon.stub(Database.bookModel, 'count').resolves(123)
const findAndCountAllStub = sinon.stub(Database.bookModel, 'findAndCountAll').resolves({ rows: [], count: 123 })
const findAllStub = sinon.stub(Database.bookModel, 'findAll').resolves([
{ id: 'book-1', title: 'Alpha', libraryItem: { id: 'item-1', titleIgnorePrefix: 'Alpha', dataValues: { titleIgnorePrefix: 'Alpha' } } },
{ id: 'book-2', title: 'Alpha', libraryItem: { id: 'item-2', titleIgnorePrefix: 'Alpha', dataValues: { titleIgnorePrefix: 'Alpha' } } },
{ id: 'book-3', title: 'Beta', libraryItem: { id: 'item-3', titleIgnorePrefix: 'Beta', dataValues: { titleIgnorePrefix: 'Beta' } } }
])
const result = await libraryItemsBookFilters.getFilteredLibraryItems(
'lib_1',
{ id: 'user_1', canAccessExplicitContent: true, accessAllTags: true },
null,
null,
'media.metadata.title',
false,
false,
[],
2,
0,
false,
{ pageMode: 'endless' }
)
expect(result.paginationMode).to.equal('keyset')
expect(result.nextCursor).to.be.a('string')
expect(findAllStub.firstCall.args[0].order[1][0].val || findAllStub.firstCall.args[0].order[1][0]).to.include('libraryItem')
expect(findAllStub.firstCall.args[0].order[1][0].val || findAllStub.firstCall.args[0].order[1][0]).to.include('id')
expect(countStub.calledOnce).to.equal(true)
expect(findAndCountAllStub.called).to.equal(false)
})
it('uses offset fallback for progress browse requests with null-valued sort fields', async () => {
const countStub = sinon.stub(Database.bookModel, 'count').resolves(123)
const findAllStub = sinon.stub(Database.bookModel, 'findAll').resolves([])
const findAndCountAllStub = sinon.stub(Database.bookModel, 'findAndCountAll').resolves({ rows: [], count: 7 })
const result = await libraryItemsBookFilters.getFilteredLibraryItems(
'lib_1',
{ id: 'user_1', canAccessExplicitContent: true, accessAllTags: true },
'progress',
'in-progress',
'progress',
true,
false,
[],
20,
0,
false,
{ cursor: 'cursor-2', pageMode: 'endless' }
)
expect(result.paginationMode).to.equal('offset')
expect(result.countMode).to.equal('exact-on-initial-page')
expect(findAndCountAllStub.calledOnce).to.equal(true)
expect(findAllStub.called).to.equal(false)
expect(countStub.called).to.equal(false)
})
})