mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 21:00:09 +00:00
770cd5165f
Update admin page tests to mock next/navigation (useRouter, usePathname, useSearchParams) for the RecentRequestsTable component and reset router mocks in beforeEach. Adjust SWR test fixtures to match the component's real API usage by replacing /api/admin/requests/recent with the paginated endpoint (/api/admin/requests?page=1&pageSize=25&search=&status=all&userId=&sortBy=createdAt&sortOrder=desc), include pagination metadata (total, page, pageSize, totalPages) and request.userId, and add a mock for /api/admin/users. This aligns the tests with the component's routing and data fetching behavior.
193 lines
6.1 KiB
TypeScript
193 lines
6.1 KiB
TypeScript
/**
|
|
* 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());
|
|
|
|
// Mock next/navigation for RecentRequestsTable component
|
|
const mockRouter = vi.hoisted(() => ({
|
|
push: vi.fn(),
|
|
replace: vi.fn(),
|
|
back: vi.fn(),
|
|
}));
|
|
const mockSearchParams = vi.hoisted(() => new URLSearchParams());
|
|
|
|
vi.mock('next/navigation', () => ({
|
|
useRouter: () => mockRouter,
|
|
usePathname: () => '/admin',
|
|
useSearchParams: () => mockSearchParams,
|
|
}));
|
|
|
|
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();
|
|
mockRouter.push.mockReset();
|
|
mockRouter.replace.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'),
|
|
},
|
|
],
|
|
},
|
|
});
|
|
// RecentRequestsTable fetches from /api/admin/requests with query params
|
|
swrState.set('/api/admin/requests?page=1&pageSize=25&search=&status=all&userId=&sortBy=createdAt&sortOrder=desc', {
|
|
data: {
|
|
requests: [
|
|
{
|
|
requestId: 'req-1',
|
|
title: 'Recent Book',
|
|
author: 'Author Two',
|
|
status: 'pending',
|
|
userId: 'user-1',
|
|
user: 'Sam',
|
|
createdAt: new Date('2024-01-02T00:00:00Z'),
|
|
completedAt: null,
|
|
errorMessage: null,
|
|
},
|
|
],
|
|
total: 1,
|
|
page: 1,
|
|
pageSize: 25,
|
|
totalPages: 1,
|
|
},
|
|
});
|
|
swrState.set('/api/admin/users', { data: { users: [] } });
|
|
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: [] } });
|
|
// RecentRequestsTable fetches from /api/admin/requests with query params
|
|
swrState.set('/api/admin/requests?page=1&pageSize=25&search=&status=all&userId=&sortBy=createdAt&sortOrder=desc', {
|
|
data: { requests: [], total: 0, page: 1, pageSize: 25, totalPages: 0 },
|
|
});
|
|
swrState.set('/api/admin/users', { data: { users: [] } });
|
|
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();
|
|
});
|
|
});
|