mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-03-01 05:29:41 +00:00
Add review and rating features with sorting and filtering options
- Implemented a new ReviewController to handle review creation, updates, and retrieval for library items. - Added pagination, sorting, and filtering capabilities for reviews in the API. - Updated frontend components to support review display, including a new ReviewsTable and enhanced ratings UI. - Introduced new strings for user interface elements related to reviews and ratings. - Added tests for the ReviewController and Review model to ensure functionality and validation. - Enabled the option to toggle the review feature in server settings.
This commit is contained in:
parent
e4e2770fbd
commit
41e8906312
12 changed files with 603 additions and 43 deletions
154
test/server/controllers/ReviewController.test.js
Normal file
154
test/server/controllers/ReviewController.test.js
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
const { expect } = require('chai')
|
||||
const { Sequelize } = require('sequelize')
|
||||
const sinon = require('sinon')
|
||||
|
||||
const Database = require('../../../server/Database')
|
||||
const ApiRouter = require('../../../server/routers/ApiRouter')
|
||||
const ReviewController = require('../../../server/controllers/ReviewController')
|
||||
const ApiCacheManager = require('../../../server/managers/ApiCacheManager')
|
||||
const Auth = require('../../../server/Auth')
|
||||
const Logger = require('../../../server/Logger')
|
||||
|
||||
describe('ReviewController', () => {
|
||||
/** @type {ApiRouter} */
|
||||
let apiRouter
|
||||
|
||||
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()
|
||||
|
||||
Database.serverSettings = {
|
||||
enableReviews: true
|
||||
}
|
||||
|
||||
apiRouter = new ApiRouter({
|
||||
auth: new Auth(),
|
||||
apiCacheManager: new ApiCacheManager()
|
||||
})
|
||||
|
||||
sinon.stub(Logger, 'info')
|
||||
sinon.stub(Logger, 'error')
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
sinon.restore()
|
||||
await Database.sequelize.close()
|
||||
})
|
||||
|
||||
async function createTestLibraryItem() {
|
||||
const library = await Database.libraryModel.create({ name: 'Test', mediaType: 'book' })
|
||||
const book = await Database.bookModel.create({ title: 'Test Book' })
|
||||
return await Database.libraryItemModel.create({ mediaId: book.id, mediaType: 'book', libraryId: library.id })
|
||||
}
|
||||
|
||||
describe('createUpdate', () => {
|
||||
it('should create a new review', async () => {
|
||||
const user = await Database.userModel.create({ username: 'testuser', type: 'root' })
|
||||
const libraryItem = await createTestLibraryItem()
|
||||
|
||||
const fakeReq = {
|
||||
params: { id: libraryItem.id },
|
||||
body: { rating: 5, reviewText: 'Great book!' },
|
||||
user
|
||||
}
|
||||
const fakeRes = {
|
||||
json: sinon.spy(),
|
||||
status: sinon.stub().returns({ send: sinon.spy() })
|
||||
}
|
||||
|
||||
await ReviewController.createUpdate(fakeReq, fakeRes)
|
||||
|
||||
expect(fakeRes.json.calledOnce).to.be.true
|
||||
const review = fakeRes.json.firstCall.args[0]
|
||||
expect(review.rating).to.equal(5)
|
||||
expect(review.reviewText).to.equal('Great book!')
|
||||
expect(review.userId).to.equal(user.id)
|
||||
})
|
||||
|
||||
it('should update an existing review', async () => {
|
||||
const user = await Database.userModel.create({ username: 'testuser', type: 'root' })
|
||||
const libraryItem = await createTestLibraryItem()
|
||||
|
||||
await Database.reviewModel.create({ userId: user.id, libraryItemId: libraryItem.id, rating: 3 })
|
||||
|
||||
const fakeReq = {
|
||||
params: { id: libraryItem.id },
|
||||
body: { rating: 4, reviewText: 'Actually better' },
|
||||
user
|
||||
}
|
||||
const fakeRes = {
|
||||
json: sinon.spy()
|
||||
}
|
||||
|
||||
await ReviewController.createUpdate(fakeReq, fakeRes)
|
||||
|
||||
const review = fakeRes.json.firstCall.args[0]
|
||||
expect(review.rating).to.equal(4)
|
||||
expect(review.reviewText).to.equal('Actually better')
|
||||
})
|
||||
|
||||
it('should return 400 for invalid rating', async () => {
|
||||
const fakeReq = {
|
||||
params: { id: 'some-id' },
|
||||
body: { rating: 6 },
|
||||
user: { id: 'u1' }
|
||||
}
|
||||
const fakeRes = {
|
||||
status: sinon.stub().returns({ send: sinon.spy() })
|
||||
}
|
||||
|
||||
await ReviewController.createUpdate(fakeReq, fakeRes)
|
||||
expect(fakeRes.status.calledWith(400)).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
describe('findAllForItem', () => {
|
||||
it('should return all reviews for an item', async () => {
|
||||
const user1 = await Database.userModel.create({ username: 'u1', type: 'user' })
|
||||
const user2 = await Database.userModel.create({ username: 'u2', type: 'user' })
|
||||
const libraryItem = await createTestLibraryItem()
|
||||
|
||||
await Database.reviewModel.create({ userId: user1.id, libraryItemId: libraryItem.id, rating: 5 })
|
||||
await Database.reviewModel.create({ userId: user2.id, libraryItemId: libraryItem.id, rating: 4 })
|
||||
|
||||
const fakeReq = { params: { id: libraryItem.id } }
|
||||
const fakeRes = { json: sinon.spy() }
|
||||
|
||||
await ReviewController.findAllForItem(fakeReq, fakeRes)
|
||||
|
||||
expect(fakeRes.json.calledOnce).to.be.true
|
||||
expect(fakeRes.json.firstCall.args[0]).to.have.lengthOf(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete a review', async () => {
|
||||
const user = await Database.userModel.create({ username: 'u1', type: 'user' })
|
||||
const libraryItem = await createTestLibraryItem()
|
||||
await Database.reviewModel.create({ userId: user.id, libraryItemId: libraryItem.id, rating: 5 })
|
||||
|
||||
const fakeReq = { params: { id: libraryItem.id }, user }
|
||||
const fakeRes = { sendStatus: sinon.spy() }
|
||||
|
||||
await ReviewController.delete(fakeReq, fakeRes)
|
||||
expect(fakeRes.sendStatus.calledWith(200)).to.be.true
|
||||
|
||||
const count = await Database.reviewModel.count()
|
||||
expect(count).to.equal(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('middleware', () => {
|
||||
it('should block when enableReviews is false', async () => {
|
||||
Database.serverSettings.enableReviews = false
|
||||
const fakeReq = {}
|
||||
const fakeRes = { status: sinon.stub().returns({ send: sinon.spy() }) }
|
||||
const next = sinon.spy()
|
||||
|
||||
await ReviewController.middleware(fakeReq, fakeRes, next)
|
||||
expect(fakeRes.status.calledWith(403)).to.be.true
|
||||
expect(next.called).to.be.false
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue