Files
ReadMeABook/tests/processors/audible-refresh.processor.test.ts
T
kikootwo 94dbaf073b Add backend unit test framework and modularize settings UI
Introduced a Vitest-based backend unit testing framework with supporting scripts, helpers, and GitHub Actions integration. Refactored the admin settings page to a modular architecture, splitting monolithic logic into feature-specific tabs and hooks for improved maintainability and testability. Updated documentation to reflect the new testing setup and settings architecture, and added new dependencies for testing utilities.
2026-01-28 11:41:59 -05:00

115 lines
3.6 KiB
TypeScript

/**
* Component: Audible Refresh 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 audibleServiceMock = vi.hoisted(() => ({
getPopularAudiobooks: vi.fn(),
getNewReleases: vi.fn(),
}));
const thumbnailCacheMock = vi.hoisted(() => ({
cacheThumbnail: vi.fn(),
cleanupUnusedThumbnails: vi.fn(),
}));
vi.mock('@/lib/db', () => ({
prisma: prismaMock,
}));
vi.mock('@/lib/integrations/audible.service', () => ({
getAudibleService: () => audibleServiceMock,
}));
vi.mock('@/lib/services/thumbnail-cache.service', () => ({
getThumbnailCacheService: () => thumbnailCacheMock,
}));
describe('processAudibleRefresh', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('refreshes popular and new releases, caching thumbnails', async () => {
const popular = [
{
asin: 'ASIN-1',
title: 'Popular One',
author: 'Author A',
narrator: 'Narrator A',
description: 'Desc',
coverArtUrl: 'http://image/1',
durationMinutes: 120,
releaseDate: '2024-01-01',
rating: 4.8,
genres: ['fiction'],
},
{
asin: 'ASIN-2',
title: 'Popular Two',
author: 'Author B',
narrator: 'Narrator B',
description: 'Desc',
coverArtUrl: null,
durationMinutes: 90,
releaseDate: null,
rating: null,
genres: [],
},
];
const newReleases = [
{
asin: 'ASIN-3',
title: 'New Release',
author: 'Author C',
narrator: 'Narrator C',
description: 'Desc',
coverArtUrl: 'http://image/3',
durationMinutes: 200,
releaseDate: '2024-02-02',
rating: 4.2,
genres: ['history'],
},
];
audibleServiceMock.getPopularAudiobooks.mockResolvedValue(popular);
audibleServiceMock.getNewReleases.mockResolvedValue(newReleases);
thumbnailCacheMock.cacheThumbnail.mockResolvedValue('cached/path.jpg');
thumbnailCacheMock.cleanupUnusedThumbnails.mockResolvedValue(2);
prismaMock.audibleCache.updateMany.mockResolvedValue({ count: 1 });
prismaMock.audibleCache.upsert.mockResolvedValue({});
prismaMock.audibleCache.findMany.mockResolvedValue([
{ asin: 'ASIN-1' },
{ asin: 'ASIN-2' },
{ asin: 'ASIN-3' },
]);
const { processAudibleRefresh } = await import('@/lib/processors/audible-refresh.processor');
const result = await processAudibleRefresh({ jobId: 'job-1' });
expect(result.success).toBe(true);
expect(result.popularSaved).toBe(2);
expect(result.newReleasesSaved).toBe(1);
expect(prismaMock.audibleCache.updateMany).toHaveBeenCalled();
expect(prismaMock.audibleCache.upsert).toHaveBeenCalledTimes(3);
expect(thumbnailCacheMock.cacheThumbnail).toHaveBeenCalledWith('ASIN-1', 'http://image/1');
expect(thumbnailCacheMock.cacheThumbnail).toHaveBeenCalledWith('ASIN-3', 'http://image/3');
expect(thumbnailCacheMock.cleanupUnusedThumbnails).toHaveBeenCalled();
const activeSet = thumbnailCacheMock.cleanupUnusedThumbnails.mock.calls[0][0] as Set<string>;
expect(Array.from(activeSet).sort()).toEqual(['ASIN-1', 'ASIN-2', 'ASIN-3']);
});
it('rethrows fatal errors', async () => {
prismaMock.audibleCache.updateMany.mockRejectedValue(new Error('DB down'));
const { processAudibleRefresh } = await import('@/lib/processors/audible-refresh.processor');
await expect(processAudibleRefresh({ jobId: 'job-2' })).rejects.toThrow('DB down');
});
});