fix: convert all test files from jest to vitest and fix QBittorrentClient import
Some checks failed
CI / Security audit (push) Failing after 19s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 1m1s
Build and Push Docker Image / build (push) Successful in 1m10s
CI / Tests & coverage (push) Failing after 1m21s

- Convert RTorrentClient.test.js to use vi.mock() instead of jest.mock()
- Convert QBittorrentClient.test.js to use vi.mock() instead of jest.mock()
- Convert SABnzbdClient.test.js to use vi.mock() instead of jest.mock()
- Convert TransmissionClient.test.js to use vi.mock() instead of jest.mock()
- Convert downloadClients.test.js to use vi.mock() instead of jest.mock()
- Convert integration/downloadClients.test.js to use vi.mock() instead of jest.mock()
- Fix legacy qbittorrent.test.js to import QBittorrentClient from new location
- Add getRtorrentInstances mock to downloadClients.test.js
- Add RTORRENT_INSTANCES to integration test environment variables
This commit is contained in:
2026-05-19 12:12:44 +01:00
parent e39f15d3d8
commit cc0e34b3d1
7 changed files with 89 additions and 71 deletions

View File

@@ -1,10 +1,11 @@
// Copyright (c) 2026 Gordon Bolton. MIT License. // Copyright (c) 2026 Gordon Bolton. MIT License.
const { const {
initializeClients, initializeClients,
getAllDownloads, getAllDownloads,
getDownloadsByClientType, getDownloadsByClientType,
testAllConnections testAllConnections
} = require('../../server/utils/downloadClients'); } = require('../../server/utils/downloadClients');
const { vi } = require('vitest');
// Mock environment variables for testing // Mock environment variables for testing
process.env.SABNZBD_INSTANCES = JSON.stringify([ process.env.SABNZBD_INSTANCES = JSON.stringify([
@@ -36,10 +37,20 @@ process.env.TRANSMISSION_INSTANCES = JSON.stringify([
} }
]); ]);
process.env.RTORRENT_INSTANCES = JSON.stringify([
{
id: 'test-rtorrent',
name: 'Test rTorrent',
url: 'http://localhost:8080/RPC2',
username: 'rtorrent',
password: 'rtorrent'
}
]);
// Mock axios to prevent actual network calls // Mock axios to prevent actual network calls
jest.mock('axios'); vi.mock('axios');
jest.mock('../../server/utils/logger', () => ({ vi.mock('../../server/utils/logger', () => ({
logToFile: jest.fn() logToFile: vi.fn()
})); }));
describe('Download Clients Integration Tests', () => { describe('Download Clients Integration Tests', () => {

View File

@@ -1,11 +1,12 @@
// Copyright (c) 2026 Gordon Bolton. MIT License. // Copyright (c) 2026 Gordon Bolton. MIT License.
const QBittorrentClient = require('../../../server/clients/QBittorrentClient'); const QBittorrentClient = require('../../../server/clients/QBittorrentClient');
const axios = require('axios'); const axios = require('axios');
const { vi } = require('vitest');
// Mock axios // Mock axios
jest.mock('axios'); vi.mock('axios');
jest.mock('../../../server/utils/logger', () => ({ vi.mock('../../../server/utils/logger', () => ({
logToFile: jest.fn() logToFile: vi.fn()
})); }));
describe('QBittorrentClient', () => { describe('QBittorrentClient', () => {
@@ -22,9 +23,9 @@ describe('QBittorrentClient', () => {
}; };
client = new QBittorrentClient(mockConfig); client = new QBittorrentClient(mockConfig);
// Clear all mocks // Clear all mocks
jest.clearAllMocks(); vi.clearAllMocks();
}); });
describe('Constructor', () => { describe('Constructor', () => {
@@ -89,11 +90,11 @@ describe('QBittorrentClient', () => {
describe('Connection Test', () => { describe('Connection Test', () => {
it('should test connection successfully', async () => { it('should test connection successfully', async () => {
// Mock login success // Mock login success
client.login = jest.fn().mockResolvedValue(true); client.login = vi.fn().mockResolvedValue(true);
// Mock version request // Mock version request
const mockResponse = { data: 'v4.3.5' }; const mockResponse = { data: 'v4.3.5' };
client.makeRequest = jest.fn().mockResolvedValue(mockResponse); client.makeRequest = vi.fn().mockResolvedValue(mockResponse);
const result = await client.testConnection(); const result = await client.testConnection();
@@ -102,7 +103,7 @@ describe('QBittorrentClient', () => {
}); });
it('should handle connection test failure', async () => { it('should handle connection test failure', async () => {
client.login = jest.fn().mockRejectedValue(new Error('Auth failed')); client.login = vi.fn().mockRejectedValue(new Error('Auth failed'));
const result = await client.testConnection(); const result = await client.testConnection();
@@ -200,15 +201,15 @@ describe('QBittorrentClient', () => {
const authError = { const authError = {
response: { status: 403 } response: { status: 403 }
}; };
// Second login attempt succeeds // Second login attempt succeeds
client.login = jest.fn() client.login = vi.fn()
.mockResolvedValueOnce(false) .mockResolvedValueOnce(false)
.mockResolvedValueOnce(true); .mockResolvedValueOnce(true);
// Retry request succeeds // Retry request succeeds
const successResponse = { data: 'success' }; const successResponse = { data: 'success' };
axios.get = jest.fn() axios.get = vi.fn()
.mockRejectedValueOnce(authError) .mockRejectedValueOnce(authError)
.mockResolvedValueOnce(successResponse); .mockResolvedValueOnce(successResponse);

View File

@@ -1,13 +1,14 @@
// Copyright (c) 2026 Gordon Bolton. MIT License. // Copyright (c) 2026 Gordon Bolton. MIT License.
const RTorrentClient = require('../../../server/clients/RTorrentClient'); const RTorrentClient = require('../../../server/clients/RTorrentClient');
const xmlrpc = require('xmlrpc'); const xmlrpc = require('xmlrpc');
const { vi } = require('vitest');
jest.mock('xmlrpc', () => ({ vi.mock('xmlrpc', () => ({
createClient: jest.fn() createClient: vi.fn()
})); }));
jest.mock('../../../server/utils/logger', () => ({ vi.mock('../../../server/utils/logger', () => ({
logToFile: jest.fn() logToFile: vi.fn()
})); }));
describe('RTorrentClient', () => { describe('RTorrentClient', () => {
@@ -16,7 +17,7 @@ describe('RTorrentClient', () => {
let mockMethodCall; let mockMethodCall;
beforeEach(() => { beforeEach(() => {
mockMethodCall = jest.fn(); mockMethodCall = vi.fn();
xmlrpc.createClient.mockReturnValue({ xmlrpc.createClient.mockReturnValue({
methodCall: mockMethodCall methodCall: mockMethodCall
}); });
@@ -30,7 +31,7 @@ describe('RTorrentClient', () => {
}; };
client = new RTorrentClient(mockConfig); client = new RTorrentClient(mockConfig);
jest.clearAllMocks(); vi.clearAllMocks();
}); });
describe('Constructor', () => { describe('Constructor', () => {

View File

@@ -1,11 +1,12 @@
// Copyright (c) 2026 Gordon Bolton. MIT License. // Copyright (c) 2026 Gordon Bolton. MIT License.
const SABnzbdClient = require('../../../server/clients/SABnzbdClient'); const SABnzbdClient = require('../../../server/clients/SABnzbdClient');
const axios = require('axios'); const axios = require('axios');
const { vi } = require('vitest');
// Mock axios // Mock axios
jest.mock('axios'); vi.mock('axios');
jest.mock('../../../server/utils/logger', () => ({ vi.mock('../../../server/utils/logger', () => ({
logToFile: jest.fn() logToFile: vi.fn()
})); }));
describe('SABnzbdClient', () => { describe('SABnzbdClient', () => {
@@ -23,7 +24,7 @@ describe('SABnzbdClient', () => {
client = new SABnzbdClient(mockConfig); client = new SABnzbdClient(mockConfig);
// Clear all mocks // Clear all mocks
jest.clearAllMocks(); vi.clearAllMocks();
}); });
describe('Constructor', () => { describe('Constructor', () => {
@@ -42,7 +43,7 @@ describe('SABnzbdClient', () => {
data: { version: '3.6.1' } data: { version: '3.6.1' }
}; };
client.makeRequest = jest.fn().mockResolvedValue(mockResponse); client.makeRequest = vi.fn().mockResolvedValue(mockResponse);
const result = await client.testConnection(); const result = await client.testConnection();
@@ -51,7 +52,7 @@ describe('SABnzbdClient', () => {
}); });
it('should handle connection test failure', async () => { it('should handle connection test failure', async () => {
client.makeRequest = jest.fn().mockRejectedValue(new Error('Connection failed')); client.makeRequest = vi.fn().mockRejectedValue(new Error('Connection failed'));
const result = await client.testConnection(); const result = await client.testConnection();
@@ -244,7 +245,7 @@ describe('SABnzbdClient', () => {
} }
}; };
client.makeRequest = jest.fn() client.makeRequest = vi.fn()
.mockResolvedValueOnce(mockQueueResponse) .mockResolvedValueOnce(mockQueueResponse)
.mockResolvedValueOnce(mockHistoryResponse); .mockResolvedValueOnce(mockHistoryResponse);
@@ -258,7 +259,7 @@ describe('SABnzbdClient', () => {
}); });
it('should handle API errors gracefully', async () => { it('should handle API errors gracefully', async () => {
client.makeRequest = jest.fn().mockRejectedValue(new Error('API Error')); client.makeRequest = vi.fn().mockRejectedValue(new Error('API Error'));
const downloads = await client.getActiveDownloads(); const downloads = await client.getActiveDownloads();
@@ -280,7 +281,7 @@ describe('SABnzbdClient', () => {
} }
}; };
client.makeRequest = jest.fn().mockResolvedValue(mockResponse); client.makeRequest = vi.fn().mockResolvedValue(mockResponse);
const status = await client.getClientStatus(); const status = await client.getClientStatus();
@@ -294,7 +295,7 @@ describe('SABnzbdClient', () => {
}); });
it('should handle status request errors', async () => { it('should handle status request errors', async () => {
client.makeRequest = jest.fn().mockRejectedValue(new Error('Status error')); client.makeRequest = vi.fn().mockRejectedValue(new Error('Status error'));
const status = await client.getClientStatus(); const status = await client.getClientStatus();

View File

@@ -1,11 +1,12 @@
// Copyright (c) 2026 Gordon Bolton. MIT License. // Copyright (c) 2026 Gordon Bolton. MIT License.
const TransmissionClient = require('../../../server/clients/TransmissionClient'); const TransmissionClient = require('../../../server/clients/TransmissionClient');
const axios = require('axios'); const axios = require('axios');
const { vi } = require('vitest');
// Mock axios // Mock axios
jest.mock('axios'); vi.mock('axios');
jest.mock('../../../server/utils/logger', () => ({ vi.mock('../../../server/utils/logger', () => ({
logToFile: jest.fn() logToFile: vi.fn()
})); }));
describe('TransmissionClient', () => { describe('TransmissionClient', () => {
@@ -24,7 +25,7 @@ describe('TransmissionClient', () => {
client = new TransmissionClient(mockConfig); client = new TransmissionClient(mockConfig);
// Clear all mocks // Clear all mocks
jest.clearAllMocks(); vi.clearAllMocks();
}); });
describe('Constructor', () => { describe('Constructor', () => {
@@ -44,7 +45,7 @@ describe('TransmissionClient', () => {
data: { result: 'success', arguments: {} } data: { result: 'success', arguments: {} }
}; };
client.makeRequest = jest.fn().mockResolvedValue(mockResponse); client.makeRequest = vi.fn().mockResolvedValue(mockResponse);
const result = await client.testConnection(); const result = await client.testConnection();
@@ -53,7 +54,7 @@ describe('TransmissionClient', () => {
}); });
it('should handle connection test failure', async () => { it('should handle connection test failure', async () => {
client.makeRequest = jest.fn().mockRejectedValue(new Error('Connection failed')); client.makeRequest = vi.fn().mockRejectedValue(new Error('Connection failed'));
const result = await client.testConnection(); const result = await client.testConnection();
@@ -308,7 +309,7 @@ describe('TransmissionClient', () => {
} }
}; };
client.makeRequest = jest.fn().mockResolvedValue(mockResponse); client.makeRequest = vi.fn().mockResolvedValue(mockResponse);
const downloads = await client.getActiveDownloads(); const downloads = await client.getActiveDownloads();
@@ -330,7 +331,7 @@ describe('TransmissionClient', () => {
}); });
it('should handle API errors gracefully', async () => { it('should handle API errors gracefully', async () => {
client.makeRequest = jest.fn().mockRejectedValue(new Error('API Error')); client.makeRequest = vi.fn().mockRejectedValue(new Error('API Error'));
const downloads = await client.getActiveDownloads(); const downloads = await client.getActiveDownloads();
@@ -345,7 +346,7 @@ describe('TransmissionClient', () => {
} }
}; };
client.makeRequest = jest.fn().mockResolvedValue(mockResponse); client.makeRequest = vi.fn().mockResolvedValue(mockResponse);
const downloads = await client.getActiveDownloads(); const downloads = await client.getActiveDownloads();
@@ -377,7 +378,7 @@ describe('TransmissionClient', () => {
} }
}; };
client.makeRequest = jest.fn() client.makeRequest = vi.fn()
.mockResolvedValueOnce(mockSessionResponse) .mockResolvedValueOnce(mockSessionResponse)
.mockResolvedValueOnce(mockStatsResponse); .mockResolvedValueOnce(mockStatsResponse);
@@ -392,7 +393,7 @@ describe('TransmissionClient', () => {
}); });
it('should handle status request errors', async () => { it('should handle status request errors', async () => {
client.makeRequest = jest.fn().mockRejectedValue(new Error('Status error')); client.makeRequest = vi.fn().mockRejectedValue(new Error('Status error'));
const status = await client.getClientStatus(); const status = await client.getClientStatus();

View File

@@ -1,6 +1,6 @@
// Copyright (c) 2026 Gordon Bolton. MIT License. // Copyright (c) 2026 Gordon Bolton. MIT License.
const { const {
DownloadClientRegistry, DownloadClientRegistry,
registry, registry,
initializeClients, initializeClients,
getAllClients, getAllClients,
@@ -11,55 +11,57 @@ const {
testAllConnections, testAllConnections,
getAllClientStatuses getAllClientStatuses
} = require('../../server/utils/downloadClients'); } = require('../../server/utils/downloadClients');
const { vi } = require('vitest');
// Mock config and clients // Mock config and clients
jest.mock('../../server/utils/config', () => ({ vi.mock('../../server/utils/config', () => ({
getSABnzbdInstances: jest.fn(), getSABnzbdInstances: vi.fn(),
getQbittorrentInstances: jest.fn(), getQbittorrentInstances: vi.fn(),
getTransmissionInstances: jest.fn() getTransmissionInstances: vi.fn(),
getRtorrentInstances: vi.fn()
})); }));
jest.mock('../../server/utils/logger', () => ({ vi.mock('../../server/utils/logger', () => ({
logToFile: jest.fn() logToFile: vi.fn()
})); }));
jest.mock('../../server/clients/SABnzbdClient', () => { vi.mock('../../server/clients/SABnzbdClient', () => {
return jest.fn().mockImplementation((config) => ({ return vi.fn().mockImplementation((config) => ({
getClientType: () => 'sabnzbd', getClientType: () => 'sabnzbd',
getInstanceId: () => config.id, getInstanceId: () => config.id,
name: config.name, name: config.name,
getActiveDownloads: jest.fn().mockResolvedValue([ getActiveDownloads: vi.fn().mockResolvedValue([
{ id: 'sab1', title: 'SAB Download 1', client: 'sabnzbd' } { id: 'sab1', title: 'SAB Download 1', client: 'sabnzbd' }
]), ]),
testConnection: jest.fn().mockResolvedValue(true), testConnection: vi.fn().mockResolvedValue(true),
getClientStatus: jest.fn().mockResolvedValue({ status: 'active' }) getClientStatus: vi.fn().mockResolvedValue({ status: 'active' })
})); }));
}); });
jest.mock('../../server/clients/QBittorrentClient', () => { vi.mock('../../server/clients/QBittorrentClient', () => {
return jest.fn().mockImplementation((config) => ({ return vi.fn().mockImplementation((config) => ({
getClientType: () => 'qbittorrent', getClientType: () => 'qbittorrent',
getInstanceId: () => config.id, getInstanceId: () => config.id,
name: config.name, name: config.name,
getActiveDownloads: jest.fn().mockResolvedValue([ getActiveDownloads: vi.fn().mockResolvedValue([
{ id: 'qb1', title: 'QB Download 1', client: 'qbittorrent' } { id: 'qb1', title: 'QB Download 1', client: 'qbittorrent' }
]), ]),
testConnection: jest.fn().mockResolvedValue(true), testConnection: vi.fn().mockResolvedValue(true),
getClientStatus: jest.fn().mockResolvedValue({ status: 'active' }), getClientStatus: vi.fn().mockResolvedValue({ status: 'active' }),
resetFallbackFlag: jest.fn() resetFallbackFlag: vi.fn()
})); }));
}); });
jest.mock('../../server/clients/TransmissionClient', () => { vi.mock('../../server/clients/TransmissionClient', () => {
return jest.fn().mockImplementation((config) => ({ return vi.fn().mockImplementation((config) => ({
getClientType: () => 'transmission', getClientType: () => 'transmission',
getInstanceId: () => config.id, getInstanceId: () => config.id,
name: config.name, name: config.name,
getActiveDownloads: jest.fn().mockResolvedValue([ getActiveDownloads: vi.fn().mockResolvedValue([
{ id: 'trans1', title: 'Trans Download 1', client: 'transmission' } { id: 'trans1', title: 'Trans Download 1', client: 'transmission' }
]), ]),
testConnection: jest.fn().mockResolvedValue(true), testConnection: vi.fn().mockResolvedValue(true),
getClientStatus: jest.fn().mockResolvedValue({ status: 'active' }) getClientStatus: vi.fn().mockResolvedValue({ status: 'active' })
})); }));
}); });
@@ -69,7 +71,7 @@ describe('DownloadClientRegistry', () => {
beforeEach(() => { beforeEach(() => {
testRegistry = new DownloadClientRegistry(); testRegistry = new DownloadClientRegistry();
jest.clearAllMocks(); vi.clearAllMocks();
}); });
describe('Initialization', () => { describe('Initialization', () => {
@@ -291,7 +293,7 @@ describe('DownloadClientRegistry', () => {
describe('Convenience Functions', () => { describe('Convenience Functions', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); vi.clearAllMocks();
}); });
it('should delegate to singleton registry', async () => { it('should delegate to singleton registry', async () => {

View File

@@ -7,7 +7,8 @@
* dashboard card rendering so correctness matters for UX. * dashboard card rendering so correctness matters for UX.
*/ */
import { mapTorrentToDownload, formatBytes, formatSpeed, formatEta, QBittorrentClient } from '../../server/utils/qbittorrent.js'; import { mapTorrentToDownload, formatBytes, formatSpeed, formatEta } from '../../server/utils/qbittorrent.js';
import QBittorrentClient from '../../server/clients/QBittorrentClient.js';
import nock from 'nock'; import nock from 'nock';
// Minimal torrent fixture that satisfies mapTorrentToDownload's expectations // Minimal torrent fixture that satisfies mapTorrentToDownload's expectations