All checks were successful
Build and Push Docker Image / build (push) Successful in 24s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 56s
CI / Security audit (push) Successful in 1m12s
CI / Tests & coverage (push) Successful in 1m25s
Docs Check / Markdown lint (pull_request) Successful in 41s
Licence Check / Licence compatibility and copyright header verification (pull_request) Successful in 1m14s
CI / Security audit (pull_request) Successful in 1m33s
Docs Check / Mermaid diagram parse check (pull_request) Successful in 1m56s
CI / Tests & coverage (pull_request) Successful in 2m3s
- Replace vi.mock('axios') with nock for HTTP request mocking (ES/CJS interop issue)
- Fix RTorrentClient by mocking client.client.methodCall directly instead of xmlrpc module
- Fix downloadClients.test.js by manually adding mock clients to registry
- Fix qbittorrent.test.js to use getActiveDownloads() and normalized properties
- Fix integration test env var mocks and error assertions
- Fix SABnzbdClient size parsing and test fixtures
- Fix RTorrentClient ETA calculation expectation
All 261 tests now passing.
209 lines
5.9 KiB
JavaScript
209 lines
5.9 KiB
JavaScript
// 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');
|
|
});
|
|
});
|
|
});
|