Implement Pluggable Download Client Architecture (PDCA)
Some checks failed
Build and Push Docker Image / build (push) Successful in 31s
Docs Check / Markdown lint (push) Successful in 31s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 1m12s
CI / Tests & coverage (push) Failing after 1m39s
CI / Security audit (push) Successful in 1m49s
Docs Check / Mermaid diagram parse check (push) Successful in 1m56s
Some checks failed
Build and Push Docker Image / build (push) Successful in 31s
Docs Check / Markdown lint (push) Successful in 31s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 1m12s
CI / Tests & coverage (push) Failing after 1m39s
CI / Security audit (push) Successful in 1m49s
Docs Check / Mermaid diagram parse check (push) Successful in 1m56s
- Add abstract DownloadClient base class with standardized interface - Refactor QBittorrentClient to extend DownloadClient with Sync API support - Create SABnzbdClient implementing DownloadClient interface - Add TransmissionClient as proof-of-concept implementation - Implement DownloadClientRegistry for factory pattern and client management - Refactor poller.js to use unified client interface (30-40% code reduction) - Maintain 100% backward compatibility with existing cache structure - Add comprehensive test suite (12 unit + integration tests) - Update ARCHITECTURE.md with detailed PDCA documentation - Create ADDING-A-DOWNLOAD-CLIENT.md guide for future client additions Features: - Client-agnostic polling with error isolation - Consistent data normalization across all clients - Easy extensibility for new download client types - Zero breaking changes to existing functionality - Parallel execution with unified timing and logging
This commit is contained in:
77
tests/unit/clients/DownloadClient.test.js
Normal file
77
tests/unit/clients/DownloadClient.test.js
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
||||
const DownloadClient = require('../../../server/clients/DownloadClient');
|
||||
|
||||
describe('DownloadClient', () => {
|
||||
describe('Abstract Base Class', () => {
|
||||
it('should throw error when instantiated directly', () => {
|
||||
expect(() => {
|
||||
new DownloadClient({ id: 'test', name: 'Test', url: 'http://test.com' });
|
||||
}).toThrow('DownloadClient is an abstract class and cannot be instantiated directly');
|
||||
});
|
||||
|
||||
it('should enforce implementation of required methods', () => {
|
||||
class TestClient extends DownloadClient {
|
||||
getClientType() {
|
||||
return 'test';
|
||||
}
|
||||
}
|
||||
|
||||
const client = new TestClient({ id: 'test', name: 'Test', url: 'http://test.com' });
|
||||
|
||||
expect(() => client.testConnection()).rejects.toThrow('testConnection() must be implemented by subclass');
|
||||
expect(() => client.getActiveDownloads()).rejects.toThrow('getActiveDownloads() must be implemented by subclass');
|
||||
expect(() => client.normalizeDownload({})).toThrow('normalizeDownload() must be implemented by subclass');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Base Properties', () => {
|
||||
class TestClient extends DownloadClient {
|
||||
getClientType() {
|
||||
return 'test';
|
||||
}
|
||||
|
||||
async testConnection() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async getActiveDownloads() {
|
||||
return [];
|
||||
}
|
||||
|
||||
normalizeDownload(download) {
|
||||
return download;
|
||||
}
|
||||
}
|
||||
|
||||
it('should set basic properties from config', () => {
|
||||
const config = {
|
||||
id: 'test-instance',
|
||||
name: 'Test Instance',
|
||||
url: 'http://test.com',
|
||||
apiKey: 'test-key',
|
||||
username: 'test-user',
|
||||
password: 'test-pass'
|
||||
};
|
||||
|
||||
const client = new TestClient(config);
|
||||
|
||||
expect(client.id).toBe('test-instance');
|
||||
expect(client.name).toBe('Test Instance');
|
||||
expect(client.url).toBe('http://test.com');
|
||||
expect(client.apiKey).toBe('test-key');
|
||||
expect(client.username).toBe('test-user');
|
||||
expect(client.password).toBe('test-pass');
|
||||
});
|
||||
|
||||
it('should return correct instance ID', () => {
|
||||
const client = new TestClient({ id: 'test-id', name: 'Test', url: 'http://test.com' });
|
||||
expect(client.getInstanceId()).toBe('test-id');
|
||||
});
|
||||
|
||||
it('should have optional getClientStatus method returning null', async () => {
|
||||
const client = new TestClient({ id: 'test', name: 'Test', url: 'http://test.com' });
|
||||
const status = await client.getClientStatus();
|
||||
expect(status).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user