"""Tests for ``alfred.application.torrents.search_torrents.SearchTorrentsUseCase``. Wraps ``KnabenClient.search`` and converts ``TorrentResult`` objects into plain dicts inside a ``SearchTorrentsResponse`` envelope. Coverage: - ``TestSuccess`` — multiple results → status="ok" + ``count`` + dict shape. - ``TestEmptyResults`` — empty list from client → status="error", error="not_found". - ``TestErrorTranslation`` — ``KnabenNotFoundError`` → not_found, ``KnabenAPIError`` → api_error, ``ValueError`` → validation_failed. - ``TestPassThrough`` — query + limit are forwarded to the client. KnabenClient is fully mocked — no real HTTP. """ from __future__ import annotations from unittest.mock import MagicMock import pytest from alfred.application.torrents.search_torrents import SearchTorrentsUseCase from alfred.infrastructure.api.knaben.dto import TorrentResult from alfred.infrastructure.api.knaben.exceptions import ( KnabenAPIError, KnabenNotFoundError, ) @pytest.fixture def client(): return MagicMock() @pytest.fixture def use_case(client): return SearchTorrentsUseCase(client) def _torrent(**kw) -> TorrentResult: defaults = dict( title="Inception.2010.1080p", size="10 GB", seeders=500, leechers=50, magnet="magnet:?xt=abc", info_hash="abc", tracker="rarbg", upload_date="2020-01-01", category="movie", ) defaults.update(kw) return TorrentResult(**defaults) # --------------------------------------------------------------------------- # # Success # # --------------------------------------------------------------------------- # class TestSuccess: def test_single_result_serialized_to_dict(self, client, use_case): client.search.return_value = [_torrent()] r = use_case.execute("Inception") assert r.status == "ok" assert r.count == 1 assert len(r.torrents) == 1 t = r.torrents[0] assert t["name"] == "Inception.2010.1080p" assert t["size"] == "10 GB" assert t["seeders"] == 500 assert t["leechers"] == 50 assert t["magnet"].startswith("magnet:") assert t["info_hash"] == "abc" assert t["tracker"] == "rarbg" assert t["upload_date"] == "2020-01-01" assert t["category"] == "movie" def test_multiple_results(self, client, use_case): client.search.return_value = [ _torrent(title="A"), _torrent(title="B"), _torrent(title="C"), ] r = use_case.execute("x") assert r.count == 3 assert [t["name"] for t in r.torrents] == ["A", "B", "C"] # --------------------------------------------------------------------------- # # Empty # # --------------------------------------------------------------------------- # class TestEmptyResults: def test_empty_list_becomes_not_found(self, client, use_case): client.search.return_value = [] r = use_case.execute("ghost") assert r.status == "error" assert r.error == "not_found" assert "ghost" in r.message # --------------------------------------------------------------------------- # # Error translation # # --------------------------------------------------------------------------- # class TestErrorTranslation: def test_not_found(self, client, use_case): client.search.side_effect = KnabenNotFoundError("nope") r = use_case.execute("x") assert r.status == "error" assert r.error == "not_found" assert "nope" in r.message def test_api_error(self, client, use_case): client.search.side_effect = KnabenAPIError("rate limited") r = use_case.execute("x") assert r.status == "error" assert r.error == "api_error" assert "rate" in r.message def test_validation_error(self, client, use_case): client.search.side_effect = ValueError("too long") r = use_case.execute("x") assert r.status == "error" assert r.error == "validation_failed" # --------------------------------------------------------------------------- # # Pass-through # # --------------------------------------------------------------------------- # class TestPassThrough: def test_default_limit_forwarded(self, client, use_case): client.search.return_value = [_torrent()] use_case.execute("Inception") client.search.assert_called_once_with("Inception", limit=10) def test_custom_limit_forwarded(self, client, use_case): client.search.return_value = [_torrent()] use_case.execute("Inception", limit=25) client.search.assert_called_once_with("Inception", limit=25)