# pylint: disable=W0212 """Tests for the remove_done_seeding job.""" from unittest.mock import AsyncMock, MagicMock, patch import pytest from src.jobs.remove_done_seeding import COMPLETED_STATES, RemoveDoneSeeding def create_mock_settings(target_tags=None, target_categories=None): """Create mock settings for testing.""" settings = MagicMock() settings.jobs = MagicMock() settings.jobs.remove_done_seeding.enabled = True settings.jobs.remove_done_seeding.target_tags = target_tags or [] settings.jobs.remove_done_seeding.target_categories = target_categories or [] settings.general = MagicMock() settings.general.protected_tag = "protected" return settings def create_mock_download_client(items, client_name="mock_client_name"): """Create a mock download client.""" client = MagicMock() client.get_qbit_items = AsyncMock(return_value=items) client.name = client_name return client # Default item properties for tests ITEM_DEFAULTS = { "progress": 1, "ratio": 0, "ratio_limit": -1, "seeding_time": 0, "seeding_time_limit": -1, "tags": "", "category": "movies", "state": "stoppedUP", } @pytest.mark.asyncio @pytest.mark.parametrize( ("item_properties", "target_tags", "target_categories", "should_be_removed"), [ # Ratio limit met, matching tag and category ( {"ratio": 2, "ratio_limit": 2, "tags": "tag1"}, ["tag1"], ["movies"], True, ), # Seeding time limit met, matching tag and category ( {"seeding_time": 100, "seeding_time_limit": 100, "tags": "tag1"}, ["tag1"], ["movies"], True, ), # Neither limit met ({"ratio": 1, "ratio_limit": 2}, ["tag1"], ["movies"], False), # Progress less than 1 (should not be considered completed) ( {"progress": 0.5, "state": "downloading"}, ["tag1"], ["movies"], False, ), # No matching tags or categories ( {"ratio": 2, "ratio_limit": 2, "tags": "other", "category": "tv"}, ["tag1"], ["movies"], False, ), # Matching category, but not completed ({"category": "tv", "state": "downloading"}, [], ["tv"], False), # Matching tag, but not completed ({"tags": "tag2", "state": "downloading"}, ["tag2"], [], False), # Matching category and completed (ratio) ( {"ratio": 2, "ratio_limit": 2, "category": "tv"}, [], ["tv"], True, ), # Matching tag and completed (seeding time) ( {"seeding_time": 100, "seeding_time_limit": 100, "tags": "tag2"}, ["tag2"], [], True, ), # No targets specified ({"ratio": 2, "ratio_limit": 2}, [], [], False), # Item with multiple tags, one is a target ( {"tags": "tag1,tag2", "ratio": 2, "ratio_limit": 2}, ["tag2"], [], True, ), # Item with a tag that is a substring of a target tag (should not match) ({"tags": "tag", "ratio": 2, "ratio_limit": 2}, ["tag1"], [], False), # Item with a category that is a substring of a target (should not match) ( {"category": "movie", "ratio": 2, "ratio_limit": 2}, [], ["movies"], False, ), # Test with another completed state ( {"ratio": 2, "ratio_limit": 2, "state": "pausedUP"}, ["tag1"], ["movies"], True, ), ], ) async def test_remove_done_seeding_logic( item_properties: dict, target_tags: list, target_categories: list, should_be_removed: bool, ): """Test the logic of the remove_done_seeding job with various scenarios.""" item = {**ITEM_DEFAULTS, **item_properties, "name": "test_item"} settings = create_mock_settings(target_tags, target_categories) client = create_mock_download_client([item]) job = RemoveDoneSeeding(client, "qbittorrent", settings, "remove_done_seeding") items_to_remove = await job._get_items_to_remove(await client.get_qbit_items()) if should_be_removed: assert len(items_to_remove) == 1 assert items_to_remove[0]["name"] == "test_item" else: assert len(items_to_remove) == 0 @pytest.mark.asyncio async def test_remove_done_seeding_skipped_for_sabnzbd(): """Test that the remove_done_seeding job is skipped for SABnzbd clients.""" settings = create_mock_settings() client = create_mock_download_client([], client_name="mock_client_name") job = RemoveDoneSeeding(client, "sabnzbd", settings, "remove_done_seeding") # Test that the job returns 0 for unsupported clients result = await job.run() assert result == 0 # Verify that no client methods were called since the job should be skipped client.get_qbit_items.assert_not_called() @pytest.mark.asyncio async def test_remove_done_seeding_test_run_enabled(): """Test that no items are actually removed when test_run is enabled.""" item = { **ITEM_DEFAULTS, "ratio": 2, "ratio_limit": 2, "name": "test_item", "tags": "tag1", } settings = create_mock_settings(target_tags=["tag1"]) settings.general.test_run = True client = create_mock_download_client([item]) job = RemoveDoneSeeding(client, "qbittorrent", settings, "remove_done_seeding") with patch.object( client, "remove_download", new_callable=AsyncMock, ) as mock_client_remove: result = await job.run() # The job should still report the number of items it would have removed assert result == 1 # But no actual removal should occur on the client mock_client_remove.assert_not_called() @pytest.mark.asyncio @pytest.mark.parametrize("protected_on", ["tag", "category"]) async def test_remove_done_seeding_with_protected_item(protected_on): """Test that items with a protected tag or category are not removed.""" item_properties = {"ratio": 2, "ratio_limit": 2, "name": "protected_item"} target_tags = ["tag1"] target_categories = ["movies"] if protected_on == "tag": item_properties["tags"] = "protected" # Also add a targetable tag to ensure it's the protection that stops it item_properties["tags"] += ",tag1" else: item_properties["category"] = "protected" target_categories = ["protected"] item = {**ITEM_DEFAULTS, **item_properties} settings = create_mock_settings( target_tags=target_tags, target_categories=target_categories, ) client = create_mock_download_client([item]) job = RemoveDoneSeeding(client, "qbittorrent", settings, "remove_done_seeding") with patch.object( job, "_remove_items", new_callable=AsyncMock, ) as mock_remove: result = await job.run() assert result == 0 # No items should be removed mock_remove.assert_not_called() @pytest.mark.asyncio async def test_is_completed_logic(): """Test the internal _is_completed logic with different states and limits.""" job = RemoveDoneSeeding( MagicMock(), "qbittorrent", MagicMock(), "remove_done_seeding" ) # Completed states for state in COMPLETED_STATES: # Ratio met assert job._is_completed( {"state": state, "ratio": 2, "ratio_limit": 2}, ), f"Failed for state {state} with ratio met" # Seeding time met assert job._is_completed( {"state": state, "seeding_time": 100, "seeding_time_limit": 100}, ), f"Failed for state {state} with seeding time met" # Neither met assert not job._is_completed( {"state": state, "ratio": 1, "ratio_limit": 2}, ), f"Failed for state {state} with neither limit met" # Limits not set assert not job._is_completed( {"state": state, "ratio": 1, "ratio_limit": -1}, ), f"Failed for state {state} with no ratio limit" # Non-completed states assert not job._is_completed( {"state": "downloading", "ratio": 2, "ratio_limit": 1}, ), "Failed for non-completed state"