// Copyright (c) 2026 Gordon Bolton. MIT License. import QBittorrentClient from '../../../server/clients/QBittorrentClient.js'; import nock from 'nock'; import { vi } from 'vitest'; vi.mock('../../../server/utils/logger', () => ({ logToFile: vi.fn() })); describe('QBittorrentClient', () => { let client; let mockConfig; beforeEach(() => { mockConfig = { id: 'test-qb', name: 'Test qBittorrent', url: 'http://localhost:8080', username: 'admin', password: 'adminadmin' }; client = new QBittorrentClient(mockConfig); // Clear all mocks vi.clearAllMocks(); }); describe('Constructor', () => { it('should initialize with correct properties', () => { expect(client.getClientType()).toBe('qbittorrent'); expect(client.getInstanceId()).toBe('test-qb'); expect(client.name).toBe('Test qBittorrent'); expect(client.url).toBe('http://localhost:8080'); expect(client.authCookie).toBeNull(); expect(client.lastRid).toBe(0); expect(client.torrentMap).toBeInstanceOf(Map); expect(client.fallbackThisCycle).toBe(false); }); }); describe('Authentication', () => { it('should login successfully with valid credentials', async () => { nock('http://localhost:8080') .post('/api/v2/auth/login', 'username=admin&password=adminadmin') .reply(200, {}, { 'set-cookie': ['SID=test-cookie'] }); const result = await client.login(); expect(result).toBe(true); expect(client.authCookie).toBe('SID=test-cookie'); }); it('should handle login failure', async () => { nock('http://localhost:8080') .post('/api/v2/auth/login', 'username=admin&password=adminadmin') .reply(200, {}, {}); const result = await client.login(); expect(result).toBe(false); expect(client.authCookie).toBeNull(); }); it('should handle login error', async () => { nock('http://localhost:8080') .post('/api/v2/auth/login', 'username=admin&password=adminadmin') .replyWithError(new Error('Network error')); const result = await client.login(); expect(result).toBe(false); expect(client.authCookie).toBeNull(); }); }); describe('Connection Test', () => { it('should test connection successfully', async () => { // Mock login success client.login = vi.fn().mockResolvedValue(true); // Mock version request const mockResponse = { data: 'v4.3.5' }; client.makeRequest = vi.fn().mockResolvedValue(mockResponse); const result = await client.testConnection(); expect(result).toBe(true); expect(client.makeRequest).toHaveBeenCalledWith('/api/v2/app/version'); }); it('should handle connection test failure', async () => { client.login = vi.fn().mockRejectedValue(new Error('Auth failed')); const result = await client.testConnection(); expect(result).toBe(false); }); }); describe('Download Normalization', () => { it('should normalize torrent data correctly', () => { const torrent = { hash: 'abc123', name: 'Test Torrent', state: 'downloading', progress: 0.75, size: 1000000000, completed: 750000000, dlspeed: 1048576, eta: 3600, category: 'movies', tags: 'movie,hd', content_path: '/downloads/test', added_on: 1640995200 }; const normalized = client.normalizeDownload(torrent); expect(normalized).toEqual({ id: 'abc123', title: 'Test Torrent', type: 'torrent', client: 'qbittorrent', instanceId: 'test-qb', instanceName: 'Test qBittorrent', status: 'Downloading', progress: 75, size: 1000000000, downloaded: 750000000, speed: 1048576, eta: 3600, category: 'movies', tags: ['movie', 'hd'], savePath: '/downloads/test', addedOn: '2022-01-01T00:00:00.000Z', raw: torrent }); }); it('should handle unknown torrent states', () => { const torrent = { hash: 'abc123', name: 'Test Torrent', state: 'unknown_state', progress: 0.5, size: 1000000, completed: 500000, dlspeed: 0, eta: -1 }; const normalized = client.normalizeDownload(torrent); expect(normalized.status).toBe('unknown_state'); expect(normalized.eta).toBeNull(); }); it('should handle missing completed field', () => { const torrent = { hash: 'abc123', name: 'Test Torrent', state: 'downloading', progress: 0.5, size: 1000000, dlspeed: 0 }; const normalized = client.normalizeDownload(torrent); expect(normalized.downloaded).toBe(500000); }); }); describe('Fallback Flag Management', () => { it('should reset fallback flag', () => { client.fallbackThisCycle = true; client.resetFallbackFlag(); expect(client.fallbackThisCycle).toBe(false); }); }); describe('Error Handling', () => { it('should handle makeRequest authentication failure', async () => { client.authCookie = 'invalid-cookie'; // First request fails with 403 nock('http://localhost:8080') .get('/test') .reply(403, {}); // Re-authentication succeeds nock('http://localhost:8080') .post('/api/v2/auth/login', 'username=admin&password=adminadmin') .reply(200, {}, { 'set-cookie': ['SID=new-cookie'] }); // Retry succeeds nock('http://localhost:8080') .get('/test') .reply(200, { data: 'success' }); const result = await client.makeRequest('/test'); expect(result.data).toEqual({ data: 'success' }); expect(client.authCookie).toBe('SID=new-cookie'); }); }); });