mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
4b90b35748
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.
158 lines
4.5 KiB
TypeScript
158 lines
4.5 KiB
TypeScript
/**
|
|
* Component: Admin Settings Global Hook Tests
|
|
* Documentation: documentation/settings-pages.md
|
|
*/
|
|
|
|
// @vitest-environment jsdom
|
|
|
|
import React from 'react';
|
|
import { act, render, waitFor } from '@testing-library/react';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const fetchWithAuthMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock('@/lib/utils/api', () => ({
|
|
fetchWithAuth: fetchWithAuthMock,
|
|
}));
|
|
|
|
const renderHook = <T,>(hook: () => T) => {
|
|
const result = { current: undefined as T };
|
|
function Probe() {
|
|
result.current = hook();
|
|
return null;
|
|
}
|
|
render(<Probe />);
|
|
return result;
|
|
};
|
|
|
|
const baseSettings = {
|
|
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: '["first@example.com","second@example.com"]',
|
|
allowedUsernames: '["alpha","beta"]',
|
|
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('useSettings', () => {
|
|
beforeEach(() => {
|
|
fetchWithAuthMock.mockReset();
|
|
});
|
|
|
|
it('loads settings and converts OIDC lists to comma-separated strings', async () => {
|
|
fetchWithAuthMock.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => baseSettings,
|
|
});
|
|
|
|
const { useSettings } = await import('@/app/admin/settings/hooks/useSettings');
|
|
const result = renderHook(() => useSettings());
|
|
|
|
await waitFor(() => expect(result.current.loading).toBe(false));
|
|
|
|
expect(result.current.settings?.oidc.allowedEmails).toBe('first@example.com, second@example.com');
|
|
expect(result.current.settings?.oidc.allowedUsernames).toBe('alpha, beta');
|
|
expect(fetchWithAuthMock).toHaveBeenCalledWith('/api/admin/settings');
|
|
});
|
|
|
|
it('tracks changes, resets, and marks settings as saved', async () => {
|
|
fetchWithAuthMock.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => baseSettings,
|
|
});
|
|
|
|
const { useSettings } = await import('@/app/admin/settings/hooks/useSettings');
|
|
const result = renderHook(() => useSettings());
|
|
|
|
await waitFor(() => expect(result.current.settings).not.toBeNull());
|
|
|
|
act(() => {
|
|
result.current.updateSettings({ audibleRegion: 'uk' });
|
|
});
|
|
|
|
expect(result.current.hasUnsavedChanges()).toBe(true);
|
|
|
|
act(() => {
|
|
result.current.resetSettings();
|
|
});
|
|
|
|
expect(result.current.settings?.audibleRegion).toBe('us');
|
|
expect(result.current.hasUnsavedChanges()).toBe(false);
|
|
|
|
act(() => {
|
|
result.current.updateSettings((prev) => ({ ...prev, audibleRegion: 'ca' }));
|
|
});
|
|
|
|
expect(result.current.hasUnsavedChanges()).toBe(true);
|
|
|
|
act(() => {
|
|
result.current.markAsSaved();
|
|
});
|
|
|
|
expect(result.current.hasUnsavedChanges()).toBe(false);
|
|
});
|
|
|
|
it('updates validation, test results, and message state', async () => {
|
|
fetchWithAuthMock.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => baseSettings,
|
|
});
|
|
|
|
const { useSettings } = await import('@/app/admin/settings/hooks/useSettings');
|
|
const result = renderHook(() => useSettings());
|
|
|
|
await waitFor(() => expect(result.current.settings).not.toBeNull());
|
|
|
|
act(() => {
|
|
result.current.updateValidation('plex', true);
|
|
result.current.updateTestResults('plex', { success: true, message: 'ok' });
|
|
result.current.showMessage({ type: 'success', text: 'Saved' });
|
|
});
|
|
|
|
expect(result.current.validated.plex).toBe(true);
|
|
expect(result.current.testResults.plex).toEqual({ success: true, message: 'ok' });
|
|
expect(result.current.message?.text).toBe('Saved');
|
|
|
|
act(() => {
|
|
result.current.clearMessage();
|
|
});
|
|
|
|
expect(result.current.message).toBeNull();
|
|
});
|
|
});
|