Files
ReadMeABook/tests/app/admin-settings.page.test.tsx
T
kikootwo 4b90b35748 Add Transmission/NZBGet and per-client paths and much more
Extend multi-download-client support to include Transmission and NZBGet and introduce per-client custom download paths. Adds protocol mapping and new client types, Transmission/NZBGet integration services, API CRUD and validation changes, UI components/modal updates and live path previews, and manager routing by protocol. Includes DB migrations (download_path on download_history, interactive_search_access on users), schema updates, and related processor/service fixes and tests to ensure backward compatibility and proper path resolution.
2026-02-09 19:45:43 -05:00

166 lines
5.2 KiB
TypeScript

/**
* Component: Admin Settings Page Tests
* Documentation: documentation/settings-pages.md
*/
// @vitest-environment jsdom
import React from 'react';
import path from 'path';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
const fetchWithAuthMock = vi.hoisted(() => vi.fn());
const saveTabSettingsMock = vi.hoisted(() => vi.fn());
const getTabValidationMock = vi.hoisted(() => vi.fn());
const getTabsMock = vi.hoisted(() => vi.fn());
const parseArrayToCommaSeparatedMock = vi.hoisted(() => vi.fn((value: string) => value));
vi.mock('@/lib/utils/api', () => ({
fetchWithAuth: fetchWithAuthMock,
}));
const mockAdminSettingsModules = () => {
vi.doMock(path.resolve('src/app/admin/settings/lib/helpers.ts'), () => ({
parseArrayToCommaSeparated: parseArrayToCommaSeparatedMock,
saveTabSettings: saveTabSettingsMock,
validateAuthSettings: vi.fn(() => ({ valid: true })),
getTabValidation: getTabValidationMock,
getTabs: getTabsMock,
}));
vi.doMock(path.resolve('src/app/admin/settings/tabs/LibraryTab/LibraryTab.tsx'), () => ({
LibraryTab: ({ settings, onChange }: { settings: any; onChange: (next: any) => void }) => (
<div>
<div>Library Tab</div>
<button type="button" onClick={() => onChange({ ...settings, audibleRegion: 'uk' })}>
Change Settings
</button>
</div>
),
}));
vi.doMock(path.resolve('src/app/admin/settings/tabs/AuthTab/AuthTab.tsx'), () => ({
AuthTab: () => <div>Auth Tab</div>,
}));
vi.doMock(path.resolve('src/app/admin/settings/tabs/IndexersTab/IndexersTab.tsx'), () => ({
IndexersTab: () => <div>Indexers Tab</div>,
}));
vi.doMock(path.resolve('src/app/admin/settings/tabs/DownloadTab/DownloadTab.tsx'), () => ({
DownloadTab: () => <div>Download Tab</div>,
}));
vi.doMock(path.resolve('src/app/admin/settings/tabs/PathsTab/PathsTab.tsx'), () => ({
PathsTab: () => <div>Paths Tab</div>,
}));
vi.doMock(path.resolve('src/app/admin/settings/tabs/EbookTab/EbookTab.tsx'), () => ({
EbookTab: () => <div>Ebook Tab</div>,
}));
vi.doMock(path.resolve('src/app/admin/settings/tabs/BookDateTab/BookDateTab.tsx'), () => ({
BookDateTab: () => <div>BookDate Tab</div>,
}));
vi.doMock(path.resolve('src/app/admin/settings/tabs/NotificationsTab/index.tsx'), () => ({
NotificationsTab: () => <div>Notifications Tab</div>,
}));
};
const settingsFixture = {
backendMode: 'plex',
hasLocalUsers: true,
audibleRegion: 'us',
plex: { url: '', token: '', libraryId: '', triggerScanAfterImport: false },
audiobookshelf: { serverUrl: '', apiToken: '', libraryId: '', triggerScanAfterImport: false },
oidc: {
enabled: false,
providerName: '',
issuerUrl: '',
clientId: '',
clientSecret: '',
accessControlMethod: 'open',
accessGroupClaim: 'groups',
accessGroupValue: '',
allowedEmails: '[]',
allowedUsernames: '[]',
adminClaimEnabled: false,
adminClaimName: 'groups',
adminClaimValue: '',
},
registration: { enabled: false, requireAdminApproval: false },
prowlarr: { url: '', apiKey: '' },
downloadClient: {
type: 'qbittorrent',
url: '',
username: '',
password: '',
disableSSLVerify: false,
remotePathMappingEnabled: false,
remotePath: '',
localPath: '',
},
paths: {
downloadDir: '',
mediaDir: '',
audiobookPathTemplate: '',
ebookPathTemplate: '',
metadataTaggingEnabled: true,
chapterMergingEnabled: false,
},
ebook: { enabled: false, preferredFormat: '', baseUrl: '', flaresolverrUrl: '' },
};
describe('AdminSettings', () => {
beforeEach(() => {
fetchWithAuthMock.mockReset();
saveTabSettingsMock.mockReset();
getTabValidationMock.mockReset();
getTabsMock.mockReset();
parseArrayToCommaSeparatedMock.mockReset();
vi.resetModules();
mockAdminSettingsModules();
});
it('fetches settings and renders the settings shell', async () => {
fetchWithAuthMock.mockResolvedValue({
ok: true,
json: async () => settingsFixture,
});
getTabValidationMock.mockReturnValue(true);
getTabsMock.mockReturnValue([{ id: 'library', label: 'Library', icon: 'L' }]);
const { default: AdminSettings } = await import('@/app/admin/settings/page');
render(<AdminSettings />);
expect(await screen.findByText('Settings')).toBeInTheDocument();
expect(fetchWithAuthMock).toHaveBeenCalledWith('/api/admin/settings');
});
it('saves settings when changes are made and validation passes', async () => {
fetchWithAuthMock.mockResolvedValue({
ok: true,
json: async () => settingsFixture,
});
getTabValidationMock.mockReturnValue(true);
getTabsMock.mockReturnValue([{ id: 'library', label: 'Library', icon: 'L' }]);
const { default: AdminSettings } = await import('@/app/admin/settings/page');
render(<AdminSettings />);
fireEvent.click(await screen.findByRole('button', { name: 'Change Settings' }));
fireEvent.click(await screen.findByRole('button', { name: 'Save Settings' }));
await waitFor(() => {
expect(saveTabSettingsMock).toHaveBeenCalledWith(
'library',
expect.objectContaining({ audibleRegion: 'uk' }),
[],
[]
);
});
});
});