test(placeholders): add server coverage for placeholder creation, serialization, and query visibility

This commit is contained in:
Jens Bickel 2026-02-09 17:19:19 +01:00
parent b01facc034
commit 96de890bdd
3 changed files with 209 additions and 0 deletions

View file

@ -0,0 +1,78 @@
const { expect } = require('chai')
const { Sequelize } = require('sequelize')
const sinon = require('sinon')
const Database = require('../../../server/Database')
const ApiRouter = require('../../../server/routers/ApiRouter')
const SeriesController = require('../../../server/controllers/SeriesController')
const ApiCacheManager = require('../../../server/managers/ApiCacheManager')
const Auth = require('../../../server/Auth')
const Logger = require('../../../server/Logger')
const User = require('../../../server/models/User')
describe('SeriesController placeholders', () => {
/** @type {ApiRouter} */
let apiRouter
let library
let libraryFolder
let series
let user
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, 'warn')
library = await Database.libraryModel.create({ name: 'Test Library', mediaType: 'book' })
libraryFolder = await Database.libraryFolderModel.create({ path: '/test', libraryId: library.id })
series = await Database.seriesModel.create({ name: 'Test Series', libraryId: library.id })
user = await Database.userModel.create({
username: 'Admin',
type: 'admin',
isActive: true,
permissions: User.getDefaultPermissionsForUserType('admin'),
bookmarks: [],
extraData: { seriesHideFromContinueListening: [] }
})
})
afterEach(async () => {
sinon.restore()
await Database.sequelize.sync({ force: true })
})
it('creates placeholder items in a series', async () => {
expect(SeriesController.createPlaceholder).to.be.a('function')
const fakeReq = {
params: {
id: library.id,
seriesId: series.id
},
user,
body: {}
}
const fakeRes = {
status: sinon.stub().returnsThis(),
json: sinon.spy(),
sendStatus: sinon.spy()
}
await SeriesController.createPlaceholder.bind(apiRouter)(fakeReq, fakeRes)
expect(fakeRes.json.calledOnce).to.be.true
const payload = fakeRes.json.firstCall.args[0]
expect(payload).to.include({ mediaType: 'book', isPlaceholder: true })
expect(payload.media.metadata.series.some((entry) => entry.id === series.id)).to.be.true
})
})

View file

@ -0,0 +1,47 @@
const { expect } = require('chai')
const { Sequelize } = require('sequelize')
const Database = require('../../../server/Database')
describe('LibraryItem placeholder serialization', () => {
beforeEach(async () => {
Database.sequelize = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
Database.sequelize.uppercaseFirst = (str) => (str ? `${str[0].toUpperCase()}${str.substr(1)}` : '')
await Database.buildModels()
})
afterEach(async () => {
await Database.sequelize.sync({ force: true })
})
it('includes isPlaceholder in minified and expanded JSON', async () => {
const library = await Database.libraryModel.create({ name: 'Test Library', mediaType: 'book' })
const libraryFolder = await Database.libraryFolderModel.create({ path: '/test', libraryId: library.id })
const book = await Database.bookModel.create({
title: 'Placeholder Book',
audioFiles: [],
tags: [],
narrators: [],
genres: [],
chapters: []
})
const libraryItem = await Database.libraryItemModel.create({
libraryFiles: [],
mediaId: book.id,
mediaType: 'book',
libraryId: library.id,
libraryFolderId: libraryFolder.id,
isPlaceholder: true
})
const expanded = await Database.libraryItemModel.getExpandedById(libraryItem.id)
const minifiedJson = expanded.toOldJSONMinified()
const expandedJson = expanded.toOldJSONExpanded()
expect(minifiedJson).to.have.property('isPlaceholder', true)
expect(expandedJson).to.have.property('isPlaceholder', true)
})
})

View file

@ -0,0 +1,84 @@
const { expect } = require('chai')
const { Sequelize } = require('sequelize')
const Database = require('../../../../server/Database')
const libraryItemsBookFilters = require('../../../../server/utils/queries/libraryItemsBookFilters')
const User = require('../../../../server/models/User')
describe('libraryItemsBookFilters placeholders', () => {
let library
let libraryFolder
let user
beforeEach(async () => {
Database.sequelize = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
Database.sequelize.uppercaseFirst = (str) => (str ? `${str[0].toUpperCase()}${str.substr(1)}` : '')
await Database.buildModels()
library = await Database.libraryModel.create({ name: 'Test Library', mediaType: 'book' })
libraryFolder = await Database.libraryFolderModel.create({ path: '/test', libraryId: library.id })
user = await Database.userModel.create({
username: 'Admin',
type: 'admin',
isActive: true,
permissions: User.getDefaultPermissionsForUserType('admin'),
bookmarks: [],
extraData: { seriesHideFromContinueListening: [] }
})
})
afterEach(async () => {
await Database.sequelize.sync({ force: true })
})
const createBookWithItem = async ({ title, isPlaceholder }) => {
const book = await Database.bookModel.create({
title,
audioFiles: [],
tags: [],
narrators: [],
genres: [],
chapters: []
})
return Database.libraryItemModel.create({
libraryFiles: [],
mediaId: book.id,
mediaType: 'book',
libraryId: library.id,
libraryFolderId: libraryFolder.id,
isPlaceholder
})
}
it('excludes placeholders from general library list queries', async () => {
await createBookWithItem({ title: 'Real Book', isPlaceholder: false })
await createBookWithItem({ title: 'Placeholder Book', isPlaceholder: true })
const { libraryItems, count } = await libraryItemsBookFilters.getFilteredLibraryItems(
library.id,
user,
null,
null,
'addedAt',
true,
false,
[],
20,
0
)
expect(count).to.equal(1)
expect(libraryItems).to.have.length(1)
expect(libraryItems[0].media.title).to.equal('Real Book')
})
it('excludes placeholders from search results', async () => {
await createBookWithItem({ title: 'Real Book', isPlaceholder: false })
await createBookWithItem({ title: 'Placeholder Book', isPlaceholder: true })
const results = await libraryItemsBookFilters.search(user, library, 'Placeholder', 20, 0)
expect(results.book).to.have.length(0)
})
})