/** * Tests for server/utils/qbittorrent.js pure utility functions. * * mapTorrentToDownload, formatBytes, formatSpeed, and formatEta are all * pure functions with no I/O — ideal unit test targets. These power the * dashboard card rendering so correctness matters for UX. */ import { mapTorrentToDownload, formatBytes, formatSpeed, formatEta } from '../../server/utils/qbittorrent.js'; // Minimal torrent fixture that satisfies mapTorrentToDownload's expectations function makeTorrent(overrides = {}) { return { name: 'My.Show.S01E01.1080p.mkv', state: 'downloading', size: 1073741824, // 1 GB completed: 536870912, // 512 MB progress: 0.5, dlspeed: 1048576, // 1 MB/s eta: 512, // seconds num_seeds: 10, num_leechs: 3, availability: 1.0, hash: 'aabbccdd', category: 'sonarr', tags: '', content_path: '/downloads/My.Show.S01E01.1080p.mkv', save_path: '/downloads/', instanceName: 'i3omb', ...overrides }; } describe('formatBytes', () => { it('formats 0 bytes', () => expect(formatBytes(0)).toBe('0 B')); it('formats bytes', () => expect(formatBytes(512)).toBe('512 B')); it('formats kilobytes', () => expect(formatBytes(1024)).toBe('1 KB')); it('formats megabytes', () => expect(formatBytes(1048576)).toBe('1 MB')); it('formats gigabytes', () => expect(formatBytes(1073741824)).toBe('1 GB')); it('formats fractional GB', () => expect(formatBytes(1610612736)).toBe('1.5 GB')); }); describe('formatSpeed', () => { it('appends /s to byte count', () => expect(formatSpeed(1048576)).toBe('1 MB/s')); it('handles zero speed', () => expect(formatSpeed(0)).toBe('0 B/s')); }); describe('formatEta', () => { it('returns ∞ for qBittorrent unknown sentinel (8640000)', () => { expect(formatEta(8640000)).toBe('∞'); }); it('returns ∞ for negative eta', () => expect(formatEta(-1)).toBe('∞')); it('formats minutes only', () => expect(formatEta(90)).toBe('1m')); it('formats hours and minutes', () => expect(formatEta(3661)).toBe('1h 1m')); it('formats days, hours and minutes', () => expect(formatEta(90061)).toBe('1d 1h 1m')); it('returns 0m for zero seconds', () => expect(formatEta(0)).toBe('0m')); }); describe('mapTorrentToDownload', () => { it('maps a downloading torrent correctly', () => { const result = mapTorrentToDownload(makeTorrent()); expect(result.status).toBe('Downloading'); expect(result.progress).toBe('50.0'); expect(result.size).toBe('1 GB'); expect(result.speed).toBe('1 MB/s'); expect(result.eta).toBe('8m'); expect(result.seeds).toBe(10); expect(result.peers).toBe(3); expect(result.qbittorrent).toBe(true); expect(result.instanceName).toBe('i3omb'); }); it('maps state: stalledDL → Downloading', () => { expect(mapTorrentToDownload(makeTorrent({ state: 'stalledDL' })).status).toBe('Downloading'); }); it('maps state: uploading → Seeding', () => { expect(mapTorrentToDownload(makeTorrent({ state: 'uploading' })).status).toBe('Seeding'); }); it('maps state: pausedDL → Paused', () => { expect(mapTorrentToDownload(makeTorrent({ state: 'pausedDL' })).status).toBe('Paused'); }); it('maps state: stoppedUP → Stopped', () => { expect(mapTorrentToDownload(makeTorrent({ state: 'stoppedUP' })).status).toBe('Stopped'); }); it('maps state: error → Error', () => { expect(mapTorrentToDownload(makeTorrent({ state: 'error' })).status).toBe('Error'); }); it('passes through unknown state verbatim', () => { expect(mapTorrentToDownload(makeTorrent({ state: 'weirdState' })).status).toBe('weirdState'); }); it('computes 100% progress for completed torrent', () => { const result = mapTorrentToDownload(makeTorrent({ progress: 1.0 })); expect(result.progress).toBe('100.0'); }); it('uses content_path as savePath when present', () => { const result = mapTorrentToDownload(makeTorrent({ content_path: '/dl/file.mkv' })); expect(result.savePath).toBe('/dl/file.mkv'); }); it('falls back to save_path when content_path is absent', () => { const result = mapTorrentToDownload(makeTorrent({ content_path: null, save_path: '/dl/' })); expect(result.savePath).toBe('/dl/'); }); });