mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
Implement file hash-based library matching and remove fuzzy ASIN matching
Adds file hash-based matching for Audiobookshelf library items to ensure 100% accurate ASIN assignment for RMAB-organized content. Removes fuzzy matching from library availability checks, making all matching ASIN-only to eliminate false positives and race conditions. Updates database schema, processors, and matcher utilities; adds new tests and documentation for the new matching strategy. Removes obsolete scripts, Dockerfile, and related tests; updates docker-compose for test environments.
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Component: Admin Jobs Page Tests
|
||||
* Documentation: documentation/backend/services/scheduler.md
|
||||
*/
|
||||
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import React from 'react';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import AdminJobsPage from '@/app/admin/jobs/page';
|
||||
|
||||
const authenticatedFetcherMock = vi.hoisted(() => vi.fn());
|
||||
const fetchJSONMock = vi.hoisted(() => vi.fn());
|
||||
const toastMock = vi.hoisted(() => ({
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warning: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/utils/api', () => ({
|
||||
authenticatedFetcher: authenticatedFetcherMock,
|
||||
fetchJSON: fetchJSONMock,
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/Toast', () => ({
|
||||
ToastProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
useToast: () => toastMock,
|
||||
}));
|
||||
|
||||
describe('AdminJobsPage', () => {
|
||||
beforeEach(() => {
|
||||
authenticatedFetcherMock.mockReset();
|
||||
fetchJSONMock.mockReset();
|
||||
toastMock.success.mockReset();
|
||||
toastMock.error.mockReset();
|
||||
});
|
||||
|
||||
it('renders scheduled jobs and allows manual trigger', async () => {
|
||||
authenticatedFetcherMock.mockResolvedValue({
|
||||
jobs: [
|
||||
{
|
||||
id: 'job-1',
|
||||
name: 'Library Scan',
|
||||
type: 'scan_plex',
|
||||
schedule: '0 * * * *',
|
||||
enabled: true,
|
||||
lastRun: null,
|
||||
nextRun: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
fetchJSONMock.mockResolvedValue({ success: true });
|
||||
|
||||
render(<AdminJobsPage />);
|
||||
|
||||
expect(await screen.findByText('Library Scan')).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /Trigger Now/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Trigger Job' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchJSONMock).toHaveBeenCalledWith('/api/admin/jobs/job-1/trigger', {
|
||||
method: 'POST',
|
||||
});
|
||||
expect(toastMock.success).toHaveBeenCalledWith('Job "Library Scan" triggered successfully');
|
||||
});
|
||||
|
||||
expect(authenticatedFetcherMock.mock.calls.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it('updates a job schedule using preset selection', async () => {
|
||||
authenticatedFetcherMock.mockResolvedValue({
|
||||
jobs: [
|
||||
{
|
||||
id: 'job-2',
|
||||
name: 'Audible Refresh',
|
||||
type: 'audible_refresh',
|
||||
schedule: '0 * * * *',
|
||||
enabled: true,
|
||||
lastRun: null,
|
||||
nextRun: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
fetchJSONMock.mockResolvedValue({ success: true });
|
||||
|
||||
render(<AdminJobsPage />);
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Edit' }));
|
||||
fireEvent.click(screen.getByRole('radio', { name: /Every 2 hours/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save Changes' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchJSONMock).toHaveBeenCalledWith('/api/admin/jobs/job-2', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ schedule: '0 */2 * * *', enabled: true }),
|
||||
});
|
||||
expect(toastMock.success).toHaveBeenCalledWith('Job "Audible Refresh" updated successfully');
|
||||
});
|
||||
});
|
||||
|
||||
it('shows an error when jobs fail to load', async () => {
|
||||
authenticatedFetcherMock.mockRejectedValue(new Error('boom'));
|
||||
|
||||
render(<AdminJobsPage />);
|
||||
|
||||
expect(await screen.findByText('Failed to load scheduled jobs')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user