Add multi-download-client support and UI management

Implements support for configuring both qBittorrent and SABnzbd simultaneously, including migration from legacy config, protocol-aware routing, and protocol filtering. Adds new CRUD API routes for download clients, new UI management components, and updates setup and settings flows to use the new multi-client architecture. Updates documentation to describe the new structure and usage.
This commit is contained in:
kikootwo
2026-01-29 09:21:33 -05:00
parent 3290ebbc9d
commit 2cda6decbe
26 changed files with 3452 additions and 924 deletions
+28 -16
View File
@@ -18,13 +18,25 @@ const configServiceMock = vi.hoisted(() => ({
get: vi.fn(),
}));
// Mock for DownloadClientManager
const downloadClientManagerMock = vi.hoisted(() => ({
getClientForProtocol: vi.fn(),
getAllClients: vi.fn(),
hasClientForProtocol: vi.fn(),
}));
vi.mock('axios', () => ({
default: axiosMock,
...axiosMock,
}));
vi.mock('@/lib/services/config.service', () => ({
getConfigService: () => configServiceMock,
getConfigService: vi.fn(async () => configServiceMock),
}));
vi.mock('@/lib/services/download-client-manager.service', () => ({
getDownloadClientManager: () => downloadClientManagerMock,
invalidateDownloadClientManager: vi.fn(),
}));
describe('SABnzbdService', () => {
@@ -32,6 +44,9 @@ describe('SABnzbdService', () => {
vi.clearAllMocks();
clientMock.get.mockReset();
configServiceMock.get.mockReset();
downloadClientManagerMock.getClientForProtocol.mockReset();
downloadClientManagerMock.getAllClients.mockReset();
downloadClientManagerMock.hasClientForProtocol.mockReset();
invalidateSABnzbdService();
});
@@ -456,22 +471,19 @@ describe('SABnzbdService', () => {
});
it('creates a singleton service from config', async () => {
configServiceMock.get.mockImplementation(async (key: string) => {
switch (key) {
case 'download_client_url':
return 'http://sab';
case 'download_client_password':
return 'api-key';
case 'sabnzbd_category':
return 'books';
case 'download_client_disable_ssl_verify':
return 'false';
case 'download_dir':
return '/downloads';
default:
return null;
}
// Mock: SABnzbd client configured via DownloadClientManager
downloadClientManagerMock.getClientForProtocol.mockResolvedValue({
id: 'client-1',
type: 'sabnzbd',
name: 'SABnzbd',
enabled: true,
url: 'http://sab',
password: 'api-key', // API key stored in password field
disableSSLVerify: false,
remotePathMappingEnabled: false,
category: 'books',
});
configServiceMock.get.mockResolvedValue('/downloads');
const ensureSpy = vi.spyOn(SABnzbdService.prototype, 'ensureCategory').mockResolvedValue();