Files
ReadMeABook/tests/api/bookdate-library.routes.test.ts
kikootwo 31bca0052f Add series fields to audiobooks and update related logic
Introduces 'series' and 'seriesPart' fields to the Audiobook model and database schema. Updates API routes, file organization, and path template utilities to support series metadata. Enhances chapter merging logic, improves notification backend testing, and expands test coverage for admin and API routes.
2026-01-28 11:42:00 -05:00

135 lines
3.9 KiB
TypeScript

/**
* Component: BookDate Library Route Tests
* Documentation: documentation/features/bookdate.md
*/
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createPrismaMock } from '../helpers/prisma';
let authRequest: any;
const prismaMock = createPrismaMock();
const requireAuthMock = vi.hoisted(() => vi.fn());
const configMock = vi.hoisted(() => ({
getBackendMode: vi.fn(),
get: vi.fn(),
getPlexConfig: vi.fn(),
}));
vi.mock('@/lib/db', () => ({
prisma: prismaMock,
}));
vi.mock('@/lib/middleware/auth', () => ({
requireAuth: requireAuthMock,
}));
vi.mock('@/lib/services/config.service', () => ({
getConfigService: () => configMock,
}));
describe('BookDate library route', () => {
beforeEach(() => {
vi.clearAllMocks();
authRequest = { user: { id: 'user-1' } };
requireAuthMock.mockImplementation((_req: any, handler: any) => handler(authRequest));
});
it('returns 400 when Audiobookshelf library ID is missing', async () => {
configMock.getBackendMode.mockResolvedValue('audiobookshelf');
configMock.get.mockResolvedValue(null);
const { GET } = await import('@/app/api/bookdate/library/route');
const response = await GET({} as any);
const payload = await response.json();
expect(response.status).toBe(400);
expect(payload.error).toMatch(/Audiobookshelf library ID/i);
});
it('returns 400 when Plex library ID is missing', async () => {
configMock.getBackendMode.mockResolvedValue('plex');
configMock.getPlexConfig.mockResolvedValue({ libraryId: null });
const { GET } = await import('@/app/api/bookdate/library/route');
const response = await GET({} as any);
const payload = await response.json();
expect(response.status).toBe(400);
expect(payload.error).toMatch(/Plex library ID/i);
});
it('returns books with cover priority (library cache > audible cache > null)', async () => {
configMock.getBackendMode.mockResolvedValue('plex');
configMock.getPlexConfig.mockResolvedValue({ libraryId: 'lib-1' });
prismaMock.plexLibrary.findMany.mockResolvedValue([
{
id: 'book-1',
title: 'Cached Cover',
author: 'Author A',
asin: 'ASIN1',
cachedLibraryCoverPath: '/cache/library/cover1.jpg',
},
{
id: 'book-2',
title: 'Audible Cover',
author: 'Author B',
asin: 'ASIN2',
cachedLibraryCoverPath: null,
},
{
id: 'book-3',
title: 'No Cover',
author: 'Author C',
asin: null,
cachedLibraryCoverPath: null,
},
]);
prismaMock.audibleCache.findMany.mockResolvedValue([
{ asin: 'ASIN1', coverArtUrl: 'http://audible/cover1.jpg' },
{ asin: 'ASIN2', coverArtUrl: 'http://audible/cover2.jpg' },
]);
const { GET } = await import('@/app/api/bookdate/library/route');
const response = await GET({} as any);
const payload = await response.json();
expect(response.status).toBe(200);
expect(payload.books).toEqual([
{
id: 'book-1',
title: 'Cached Cover',
author: 'Author A',
coverUrl: '/api/cache/library/cover1.jpg',
},
{
id: 'book-2',
title: 'Audible Cover',
author: 'Author B',
coverUrl: 'http://audible/cover2.jpg',
},
{
id: 'book-3',
title: 'No Cover',
author: 'Author C',
coverUrl: null,
},
]);
});
it('returns 500 when database lookup fails', async () => {
configMock.getBackendMode.mockResolvedValue('plex');
configMock.getPlexConfig.mockResolvedValue({ libraryId: 'lib-1' });
prismaMock.plexLibrary.findMany.mockRejectedValue(new Error('db down'));
const { GET } = await import('@/app/api/bookdate/library/route');
const response = await GET({} as any);
const payload = await response.json();
expect(response.status).toBe(500);
expect(payload.error).toMatch(/db down/i);
});
});