import unittest from unittest.mock import AsyncMock, patch from types import SimpleNamespace from backend.app.clients.sonarr import SonarrClient from backend.app.services.download_labels import label_episode_downloads from backend.app.routers import requests class TvDownloadTrackingTests(unittest.IsolatedAsyncioTestCase): async def test_queue_paginates_with_correct_filter(self): client = SonarrClient('http://sonarr.test', 'test') with patch.object(client, 'get', new=AsyncMock(side_effect=[ {'records': [{'id': 1}], 'totalRecords': 2}, {'records': [{'id': 2}], 'totalRecords': 2}, ])) as get: result = await client.get_queue(42) self.assertEqual(len(result['records']), 2) self.assertEqual(get.call_args_list[0].kwargs['params']['seriesIds'], 42) self.assertEqual(get.call_args_list[1].kwargs['params']['page'], 2) self.assertEqual(get.call_args.kwargs['params']['includeEpisode'], 'true') async def test_live_poll_discovers_two_unseen_episode_downloads(self): runtime = SimpleNamespace(jellyseerr_base_url=None, jellyseerr_api_key=None, sonarr_base_url='http://sonarr.test', sonarr_api_key='test', qbittorrent_base_url='http://qbit.test', qbittorrent_username='test', qbittorrent_password='test') queue = {'records': [ {'seriesId': 42, 'downloadId': 'ABC', 'episode': {'seasonNumber': 5, 'episodeNumber': 9}}, {'seriesId': 42, 'downloadId': 'DEF', 'episode': {'seasonNumber': 5, 'episodeNumber': 10}}, {'seriesId': 99, 'downloadId': 'OTHER'}, ]} with patch.object(requests, 'get_runtime_settings', return_value=runtime), \ patch.object(requests, 'get_request_repairs', return_value=[]), \ patch.object(requests, 'get_request_download_evidence', return_value={'observed': True, 'torrents': []}), \ patch.object(requests, 'get_request_cache_payload', return_value={'type': 'tv', 'media': {'tvdbId': 123}}), \ patch.object(requests.SonarrClient, 'get_series_by_tvdb_id', new=AsyncMock(return_value=[{'id': 42}])), \ patch.object(requests.SonarrClient, 'get_queue', new=AsyncMock(return_value=queue)), \ patch.object(requests.QBittorrentClient, 'get_torrents_by_hashes', new=AsyncMock(return_value=[ {'hash': 'abc', 'progress': .25, 'state': 'downloading'}, {'hash': 'def', 'progress': .5, 'state': 'downloading'}, ])) as torrents: result = await requests.get_download_progress('12', {'username': 'viewer', 'role': 'user'}) torrents.assert_awaited_once_with('abc|def') self.assertEqual(result['state'], 'downloading') self.assertEqual(result['torrents'][0]['episodeLabel'], 'S05E09') self.assertEqual(result['torrents'][1]['progressPercent'], 50) def test_pack_does_not_claim_individual_episode_progress(self): rows = [{'downloadId': 'PACK', 'episode': {'seasonNumber': 1, 'episodeNumber': n}} for n in [1, 2, 2]] result = label_episode_downloads([{'hash': 'pack'}], rows) self.assertEqual(result[0]['episodeLabel'], 'S01E01 · S01E02 — shared download progress') if __name__ == '__main__': unittest.main()