"""Tests for API tools.""" from unittest.mock import Mock, patch from agent.tools import api as api_tools from infrastructure.persistence import get_memory class TestFindMediaImdbId: """Tests for find_media_imdb_id tool.""" @patch("agent.tools.api.SearchMovieUseCase") def test_success(self, mock_use_case_class, memory): """Should return movie info on success.""" mock_response = Mock() mock_response.to_dict.return_value = { "status": "ok", "imdb_id": "tt1375666", "title": "Inception", "media_type": "movie", "tmdb_id": 27205, } mock_use_case = Mock() mock_use_case.execute.return_value = mock_response mock_use_case_class.return_value = mock_use_case result = api_tools.find_media_imdb_id("Inception") assert result["status"] == "ok" assert result["imdb_id"] == "tt1375666" assert result["title"] == "Inception" @patch("agent.tools.api.SearchMovieUseCase") def test_stores_in_stm(self, mock_use_case_class, memory): """Should store result in STM on success.""" mock_response = Mock() mock_response.to_dict.return_value = { "status": "ok", "imdb_id": "tt1375666", "title": "Inception", "media_type": "movie", "tmdb_id": 27205, } mock_use_case = Mock() mock_use_case.execute.return_value = mock_response mock_use_case_class.return_value = mock_use_case api_tools.find_media_imdb_id("Inception") mem = get_memory() entity = mem.stm.get_entity("last_media_search") assert entity is not None assert entity["title"] == "Inception" assert mem.stm.current_topic == "searching_media" @patch("agent.tools.api.SearchMovieUseCase") def test_not_found(self, mock_use_case_class, memory): """Should return error when not found.""" mock_response = Mock() mock_response.to_dict.return_value = { "status": "error", "error": "not_found", "message": "No results found", } mock_use_case = Mock() mock_use_case.execute.return_value = mock_response mock_use_case_class.return_value = mock_use_case result = api_tools.find_media_imdb_id("NonexistentMovie12345") assert result["status"] == "error" assert result["error"] == "not_found" @patch("agent.tools.api.SearchMovieUseCase") def test_does_not_store_on_error(self, mock_use_case_class, memory): """Should not store in STM on error.""" mock_response = Mock() mock_response.to_dict.return_value = {"status": "error"} mock_use_case = Mock() mock_use_case.execute.return_value = mock_response mock_use_case_class.return_value = mock_use_case api_tools.find_media_imdb_id("Test") mem = get_memory() assert mem.stm.get_entity("last_media_search") is None class TestFindTorrent: """Tests for find_torrent tool.""" @patch("agent.tools.api.SearchTorrentsUseCase") def test_success(self, mock_use_case_class, memory): """Should return torrents on success.""" mock_response = Mock() mock_response.to_dict.return_value = { "status": "ok", "torrents": [ {"name": "Torrent 1", "seeders": 100, "magnet": "magnet:?xt=..."}, {"name": "Torrent 2", "seeders": 50, "magnet": "magnet:?xt=..."}, ], "count": 2, } mock_use_case = Mock() mock_use_case.execute.return_value = mock_response mock_use_case_class.return_value = mock_use_case result = api_tools.find_torrent("Inception 1080p") assert result["status"] == "ok" assert len(result["torrents"]) == 2 @patch("agent.tools.api.SearchTorrentsUseCase") def test_stores_in_episodic(self, mock_use_case_class, memory): """Should store results in episodic memory.""" mock_response = Mock() mock_response.to_dict.return_value = { "status": "ok", "torrents": [ {"name": "Torrent 1", "magnet": "magnet:?xt=..."}, ], "count": 1, } mock_use_case = Mock() mock_use_case.execute.return_value = mock_response mock_use_case_class.return_value = mock_use_case api_tools.find_torrent("Inception") mem = get_memory() assert mem.episodic.last_search_results is not None assert mem.episodic.last_search_results["query"] == "Inception" assert mem.stm.current_topic == "selecting_torrent" @patch("agent.tools.api.SearchTorrentsUseCase") def test_results_have_indexes(self, mock_use_case_class, memory): """Should add indexes to results.""" mock_response = Mock() mock_response.to_dict.return_value = { "status": "ok", "torrents": [ {"name": "Torrent 1"}, {"name": "Torrent 2"}, {"name": "Torrent 3"}, ], "count": 3, } mock_use_case = Mock() mock_use_case.execute.return_value = mock_response mock_use_case_class.return_value = mock_use_case api_tools.find_torrent("Test") mem = get_memory() results = mem.episodic.last_search_results["results"] assert results[0]["index"] == 1 assert results[1]["index"] == 2 assert results[2]["index"] == 3 @patch("agent.tools.api.SearchTorrentsUseCase") def test_not_found(self, mock_use_case_class, memory): """Should return error when no torrents found.""" mock_response = Mock() mock_response.to_dict.return_value = { "status": "error", "error": "not_found", } mock_use_case = Mock() mock_use_case.execute.return_value = mock_response mock_use_case_class.return_value = mock_use_case result = api_tools.find_torrent("NonexistentMovie12345") assert result["status"] == "error" class TestGetTorrentByIndex: """Tests for get_torrent_by_index tool.""" def test_success(self, memory_with_search_results): """Should return torrent at index.""" result = api_tools.get_torrent_by_index(2) assert result["status"] == "ok" assert result["torrent"]["name"] == "Inception.2010.1080p.WEB-DL.x265" def test_first_index(self, memory_with_search_results): """Should return first torrent.""" result = api_tools.get_torrent_by_index(1) assert result["status"] == "ok" assert result["torrent"]["name"] == "Inception.2010.1080p.BluRay.x264" def test_last_index(self, memory_with_search_results): """Should return last torrent.""" result = api_tools.get_torrent_by_index(3) assert result["status"] == "ok" assert result["torrent"]["name"] == "Inception.2010.720p.BluRay" def test_index_out_of_range(self, memory_with_search_results): """Should return error for invalid index.""" result = api_tools.get_torrent_by_index(10) assert result["status"] == "error" assert result["error"] == "not_found" def test_index_zero(self, memory_with_search_results): """Should return error for index 0.""" result = api_tools.get_torrent_by_index(0) assert result["status"] == "error" assert result["error"] == "not_found" def test_negative_index(self, memory_with_search_results): """Should return error for negative index.""" result = api_tools.get_torrent_by_index(-1) assert result["status"] == "error" assert result["error"] == "not_found" def test_no_search_results(self, memory): """Should return error if no search results.""" result = api_tools.get_torrent_by_index(1) assert result["status"] == "error" assert result["error"] == "not_found" assert "Search for torrents first" in result["message"] class TestAddTorrentToQbittorrent: """Tests for add_torrent_to_qbittorrent tool.""" @patch("agent.tools.api.AddTorrentUseCase") def test_success(self, mock_use_case_class, memory): """Should add torrent successfully.""" mock_response = Mock() mock_response.to_dict.return_value = { "status": "ok", "message": "Torrent added", } mock_use_case = Mock() mock_use_case.execute.return_value = mock_response mock_use_case_class.return_value = mock_use_case result = api_tools.add_torrent_to_qbittorrent("magnet:?xt=urn:btih:abc123") assert result["status"] == "ok" @patch("agent.tools.api.AddTorrentUseCase") def test_adds_to_active_downloads( self, mock_use_case_class, memory_with_search_results ): """Should add to active downloads on success.""" mock_response = Mock() mock_response.to_dict.return_value = {"status": "ok"} mock_use_case = Mock() mock_use_case.execute.return_value = mock_response mock_use_case_class.return_value = mock_use_case api_tools.add_torrent_to_qbittorrent("magnet:?xt=urn:btih:abc123") mem = get_memory() assert len(mem.episodic.active_downloads) == 1 assert ( mem.episodic.active_downloads[0]["name"] == "Inception.2010.1080p.BluRay.x264" ) @patch("agent.tools.api.AddTorrentUseCase") def test_sets_topic_and_ends_workflow(self, mock_use_case_class, memory): """Should set topic and end workflow.""" mock_response = Mock() mock_response.to_dict.return_value = {"status": "ok"} mock_use_case = Mock() mock_use_case.execute.return_value = mock_response mock_use_case_class.return_value = mock_use_case memory.stm.start_workflow("download", {"title": "Test"}) api_tools.add_torrent_to_qbittorrent("magnet:?xt=...") mem = get_memory() assert mem.stm.current_topic == "downloading" assert mem.stm.current_workflow is None @patch("agent.tools.api.AddTorrentUseCase") def test_error(self, mock_use_case_class, memory): """Should return error on failure.""" mock_response = Mock() mock_response.to_dict.return_value = { "status": "error", "error": "connection_failed", } mock_use_case = Mock() mock_use_case.execute.return_value = mock_response mock_use_case_class.return_value = mock_use_case result = api_tools.add_torrent_to_qbittorrent("magnet:?xt=...") assert result["status"] == "error" class TestAddTorrentByIndex: """Tests for add_torrent_by_index tool.""" @patch("agent.tools.api.AddTorrentUseCase") def test_success(self, mock_use_case_class, memory_with_search_results): """Should add torrent by index.""" mock_response = Mock() mock_response.to_dict.return_value = {"status": "ok"} mock_use_case = Mock() mock_use_case.execute.return_value = mock_response mock_use_case_class.return_value = mock_use_case result = api_tools.add_torrent_by_index(1) assert result["status"] == "ok" assert result["torrent_name"] == "Inception.2010.1080p.BluRay.x264" @patch("agent.tools.api.AddTorrentUseCase") def test_uses_correct_magnet(self, mock_use_case_class, memory_with_search_results): """Should use magnet from selected torrent.""" mock_response = Mock() mock_response.to_dict.return_value = {"status": "ok"} mock_use_case = Mock() mock_use_case.execute.return_value = mock_response mock_use_case_class.return_value = mock_use_case api_tools.add_torrent_by_index(2) mock_use_case.execute.assert_called_once_with("magnet:?xt=urn:btih:def456") def test_invalid_index(self, memory_with_search_results): """Should return error for invalid index.""" result = api_tools.add_torrent_by_index(99) assert result["status"] == "error" assert result["error"] == "not_found" def test_no_search_results(self, memory): """Should return error if no search results.""" result = api_tools.add_torrent_by_index(1) assert result["status"] == "error" assert result["error"] == "not_found" def test_no_magnet_link(self, memory): """Should return error if torrent has no magnet.""" memory.episodic.store_search_results( "test", [{"name": "Torrent without magnet", "seeders": 100}], ) result = api_tools.add_torrent_by_index(1) assert result["status"] == "error" assert result["error"] == "no_magnet"