/** * Component: Recently Added Processor Tests * Documentation: documentation/backend/services/scheduler.md */ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { createPrismaMock } from '../helpers/prisma'; const prismaMock = createPrismaMock(); const libraryServiceMock = vi.hoisted(() => ({ getRecentlyAdded: vi.fn(), })); const configMock = vi.hoisted(() => ({ getBackendMode: vi.fn(), getMany: vi.fn(), get: vi.fn(), })); vi.mock('@/lib/db', () => ({ prisma: prismaMock, })); vi.mock('@/lib/services/library', () => ({ getLibraryService: async () => libraryServiceMock, })); vi.mock('@/lib/services/config.service', () => ({ getConfigService: () => configMock, })); vi.mock('@/lib/utils/audiobook-matcher', () => ({ findPlexMatch: vi.fn(), })); vi.mock('@/lib/services/audiobookshelf/api', () => ({ triggerABSItemMatch: vi.fn(), })); describe('processPlexRecentlyAddedCheck', () => { beforeEach(() => { vi.clearAllMocks(); }); it('skips when Plex configuration is missing', async () => { configMock.getBackendMode.mockResolvedValue('plex'); configMock.getMany.mockResolvedValue({ plex_url: '', plex_token: '', plex_audiobook_library_id: '', }); const { processPlexRecentlyAddedCheck } = await import('@/lib/processors/plex-recently-added.processor'); const result = await processPlexRecentlyAddedCheck({ jobId: 'job-1' }); expect(result.skipped).toBe(true); expect(prismaMock.plexLibrary.findUnique).not.toHaveBeenCalled(); }); it('creates and updates recently added library items', async () => { configMock.getBackendMode.mockResolvedValue('plex'); configMock.getMany.mockResolvedValue({ plex_url: 'http://plex', plex_token: 'token', plex_audiobook_library_id: 'lib-1', }); configMock.get.mockResolvedValue('lib-1'); libraryServiceMock.getRecentlyAdded.mockResolvedValue([ { id: 'rating-1', externalId: 'guid-1', title: 'New Item', author: 'Author A', addedAt: new Date(), }, { id: 'rating-2', externalId: 'guid-2', title: 'Existing Item', author: 'Author B', addedAt: new Date(), }, ]); prismaMock.plexLibrary.findUnique.mockImplementation(async (query: any) => { if (query.where.plexGuid === 'guid-2') { return { id: 'existing-id', plexGuid: 'guid-2', author: 'Author B' }; } return null; }); prismaMock.plexLibrary.create.mockResolvedValue({}); prismaMock.plexLibrary.update.mockResolvedValue({}); prismaMock.request.findMany.mockResolvedValue([]); const { processPlexRecentlyAddedCheck } = await import('@/lib/processors/plex-recently-added.processor'); const result = await processPlexRecentlyAddedCheck({ jobId: 'job-2' }); expect(result.newCount).toBe(1); expect(result.updatedCount).toBe(1); expect(prismaMock.plexLibrary.create).toHaveBeenCalled(); expect(prismaMock.plexLibrary.update).toHaveBeenCalled(); }); it('matches requests and triggers ABS metadata match for audiobookshelf', async () => { const matcher = await import('@/lib/utils/audiobook-matcher'); const absApi = await import('@/lib/services/audiobookshelf/api'); configMock.getBackendMode.mockResolvedValue('audiobookshelf'); configMock.getMany.mockResolvedValue({ 'audiobookshelf.server_url': 'http://abs', 'audiobookshelf.api_token': 'token', 'audiobookshelf.library_id': 'abs-lib', }); configMock.get.mockResolvedValue('abs-lib'); libraryServiceMock.getRecentlyAdded.mockResolvedValue([ { id: 'abs-1', externalId: 'abs-item-1', title: 'New ABS Item', author: 'Author A', addedAt: new Date(), }, ]); prismaMock.plexLibrary.findUnique.mockResolvedValue(null); prismaMock.plexLibrary.create.mockResolvedValue({}); prismaMock.request.findMany.mockResolvedValue([ { id: 'req-1', status: 'downloaded', audiobook: { id: 'ab-1', title: 'Match Me', author: 'Author A', narrator: 'Narrator A', audibleAsin: 'ASIN-ABS', }, }, ]); (matcher.findPlexMatch as ReturnType).mockResolvedValue({ plexGuid: 'abs-item-1', plexRatingKey: 'rating-abs', title: 'Match Me', author: 'Author A', }); prismaMock.audiobook.update.mockResolvedValue({}); prismaMock.request.update.mockResolvedValue({}); const { processPlexRecentlyAddedCheck } = await import('@/lib/processors/plex-recently-added.processor'); const result = await processPlexRecentlyAddedCheck({ jobId: 'job-3' }); expect(result.matchedDownloads).toBe(1); expect(prismaMock.audiobook.update).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ absItemId: 'abs-item-1' }), }) ); expect(prismaMock.request.update).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ status: 'available' }), }) ); expect(absApi.triggerABSItemMatch).toHaveBeenCalledWith('abs-item-1', 'ASIN-ABS'); }); });