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:
kikootwo
2026-01-28 10:32:14 -05:00
parent 497849f427
commit a97979358f
111 changed files with 6571 additions and 1426 deletions
+165
View File
@@ -0,0 +1,165 @@
/**
* Component: Admin Dashboard Page Tests
* Documentation: documentation/admin-dashboard.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 AdminDashboard from '@/app/admin/page';
const authenticatedFetcherMock = vi.hoisted(() => vi.fn());
const fetchJSONMock = vi.hoisted(() => vi.fn());
const mutateMock = vi.hoisted(() => vi.fn());
const toastMock = vi.hoisted(() => ({
success: vi.fn(),
error: vi.fn(),
info: vi.fn(),
warning: vi.fn(),
}));
const swrState = new Map<string, { data?: any; error?: any; mutate?: ReturnType<typeof vi.fn> }>();
vi.mock('swr', () => ({
default: (key: string) => {
return swrState.get(key) || { data: undefined, error: undefined, mutate: vi.fn() };
},
mutate: mutateMock,
}));
vi.mock('@/lib/utils/api', () => ({
authenticatedFetcher: authenticatedFetcherMock,
fetchJSON: fetchJSONMock,
fetchWithAuth: vi.fn(),
}));
vi.mock('@/components/ui/Toast', () => ({
ToastProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
useToast: () => toastMock,
}));
vi.mock('@/components/requests/InteractiveTorrentSearchModal', () => ({
InteractiveTorrentSearchModal: () => null,
}));
describe('AdminDashboard', () => {
beforeEach(() => {
swrState.clear();
fetchJSONMock.mockReset();
mutateMock.mockReset();
toastMock.success.mockReset();
toastMock.error.mockReset();
});
it('renders metrics, downloads, and recent requests', async () => {
swrState.set('/api/admin/metrics', {
data: {
totalRequests: 12,
activeDownloads: 2,
completedLast30Days: 8,
failedLast30Days: 1,
totalUsers: 4,
systemHealth: { status: 'healthy', issues: [] },
},
});
swrState.set('/api/admin/downloads/active', {
data: {
downloads: [
{
requestId: 'r1',
title: 'Active Book',
author: 'Author One',
progress: 55,
speed: 1024,
eta: 1200,
user: 'Zach',
startedAt: new Date('2024-01-01T00:00:00Z'),
},
],
},
});
swrState.set('/api/admin/requests/recent', {
data: {
requests: [
{
requestId: 'req-1',
title: 'Recent Book',
author: 'Author Two',
status: 'pending',
user: 'Sam',
createdAt: new Date('2024-01-02T00:00:00Z'),
completedAt: null,
errorMessage: null,
},
],
},
});
swrState.set('/api/admin/requests/pending-approval', { data: { requests: [] } });
swrState.set('/api/admin/settings', { data: { ebook: { enabled: false } } });
render(<AdminDashboard />);
expect(await screen.findByText('Admin Dashboard')).toBeInTheDocument();
expect(screen.getByText('Total Requests')).toBeInTheDocument();
expect(screen.getByText('Active Book')).toBeInTheDocument();
expect(screen.getByText('Recent Book')).toBeInTheDocument();
});
it('approves a pending request and refreshes caches', async () => {
swrState.set('/api/admin/metrics', {
data: {
totalRequests: 1,
activeDownloads: 0,
completedLast30Days: 0,
failedLast30Days: 0,
totalUsers: 1,
systemHealth: { status: 'healthy', issues: [] },
},
});
swrState.set('/api/admin/downloads/active', { data: { downloads: [] } });
swrState.set('/api/admin/requests/recent', { data: { requests: [] } });
swrState.set('/api/admin/settings', { data: { ebook: { enabled: false } } });
swrState.set('/api/admin/requests/pending-approval', {
data: {
requests: [
{
id: 'pending-1',
createdAt: new Date().toISOString(),
audiobook: { title: 'Awaiting', author: 'Author', coverArtUrl: null },
user: { id: 'u1', plexUsername: 'User', avatarUrl: null },
},
],
},
});
fetchJSONMock.mockResolvedValue({ success: true });
render(<AdminDashboard />);
fireEvent.click(await screen.findByRole('button', { name: 'Approve' }));
await waitFor(() => {
expect(fetchJSONMock).toHaveBeenCalledWith('/api/admin/requests/pending-1/approve', {
method: 'POST',
body: JSON.stringify({ action: 'approve' }),
});
expect(toastMock.success).toHaveBeenCalledWith('Request approved');
});
expect(mutateMock).toHaveBeenCalledWith('/api/admin/requests/pending-approval');
expect(mutateMock).toHaveBeenCalledWith('/api/admin/requests/recent');
expect(mutateMock).toHaveBeenCalledWith('/api/admin/metrics');
});
it('shows an error message when dashboard data fails to load', async () => {
swrState.set('/api/admin/metrics', { error: new Error('Metrics unavailable') });
render(<AdminDashboard />);
expect(await screen.findByText('Error Loading Dashboard')).toBeInTheDocument();
expect(screen.getByText('Metrics unavailable')).toBeInTheDocument();
});
});