Unfucked gemini's mess

This commit is contained in:
2025-12-07 03:27:45 +01:00
parent 5b71233fb0
commit a923a760ef
24 changed files with 1885 additions and 1282 deletions

View File

@@ -1,4 +1,4 @@
"""Tests for API tools."""
"""Tests for API tools - Refactored to use real components with minimal mocking."""
from unittest.mock import Mock, patch
@@ -6,44 +6,67 @@ from agent.tools import api as api_tools
from infrastructure.persistence import get_memory
def create_mock_response(status_code, json_data=None, text=None):
"""Helper to create properly mocked HTTP response."""
response = Mock()
response.status_code = status_code
response.raise_for_status = Mock()
if json_data is not None:
response.json = Mock(return_value=json_data)
if text is not None:
response.text = text
return response
class TestFindMediaImdbId:
"""Tests for find_media_imdb_id tool."""
@patch("agent.tools.api.SearchMovieUseCase")
def test_success(self, mock_use_case_class, memory):
@patch('infrastructure.api.tmdb.client.requests.get')
def test_success(self, mock_get, 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
# Mock HTTP responses
def mock_get_side_effect(url, **kwargs):
if "search" in url:
return create_mock_response(200, json_data={
"results": [{
"id": 27205,
"title": "Inception",
"release_date": "2010-07-16",
"overview": "A thief...",
"media_type": "movie"
}]
})
elif "external_ids" in url:
return create_mock_response(200, json_data={"imdb_id": "tt1375666"})
mock_get.side_effect = mock_get_side_effect
result = api_tools.find_media_imdb_id("Inception")
assert result["status"] == "ok"
assert result["imdb_id"] == "tt1375666"
assert result["title"] == "Inception"
# Verify HTTP calls
assert mock_get.call_count == 2
@patch("agent.tools.api.SearchMovieUseCase")
def test_stores_in_stm(self, mock_use_case_class, memory):
@patch('infrastructure.api.tmdb.client.requests.get')
def test_stores_in_stm(self, mock_get, 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
def mock_get_side_effect(url, **kwargs):
if "search" in url:
return create_mock_response(200, json_data={
"results": [{
"id": 27205,
"title": "Inception",
"release_date": "2010-07-16",
"media_type": "movie"
}]
})
elif "external_ids" in url:
return create_mock_response(200, json_data={"imdb_id": "tt1375666"})
mock_get.side_effect = mock_get_side_effect
api_tools.find_media_imdb_id("Inception")
@@ -53,32 +76,20 @@ class TestFindMediaImdbId:
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):
@patch('infrastructure.api.tmdb.client.requests.get')
def test_not_found(self, mock_get, 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
mock_get.return_value = create_mock_response(200, json_data={"results": []})
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):
@patch('infrastructure.api.tmdb.client.requests.get')
def test_does_not_store_on_error(self, mock_get, 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
mock_get.return_value = create_mock_response(200, json_data={"results": []})
api_tools.find_media_imdb_id("Test")
@@ -89,41 +100,49 @@ class TestFindMediaImdbId:
class TestFindTorrent:
"""Tests for find_torrent tool."""
@patch("agent.tools.api.SearchTorrentsUseCase")
def test_success(self, mock_use_case_class, memory):
@patch('infrastructure.api.knaben.client.requests.post')
def test_success(self, mock_post, 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
mock_post.return_value = create_mock_response(200, json_data={
"hits": [
{
"title": "Torrent 1",
"seeders": 100,
"leechers": 10,
"magnetUrl": "magnet:?xt=...",
"size": "2.5 GB"
},
{
"title": "Torrent 2",
"seeders": 50,
"leechers": 5,
"magnetUrl": "magnet:?xt=...",
"size": "1.8 GB"
}
]
})
result = api_tools.find_torrent("Inception 1080p")
assert result["status"] == "ok"
assert len(result["torrents"]) == 2
# Verify HTTP payload
payload = mock_post.call_args[1]['json']
assert payload['query'] == "Inception 1080p"
@patch("agent.tools.api.SearchTorrentsUseCase")
def test_stores_in_episodic(self, mock_use_case_class, memory):
@patch('infrastructure.api.knaben.client.requests.post')
def test_stores_in_episodic(self, mock_post, 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
mock_post.return_value = create_mock_response(200, json_data={
"hits": [{
"title": "Torrent 1",
"seeders": 100,
"leechers": 10,
"magnetUrl": "magnet:?xt=...",
"size": "2.5 GB"
}]
})
api_tools.find_torrent("Inception")
@@ -132,22 +151,16 @@ class TestFindTorrent:
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):
@patch('infrastructure.api.knaben.client.requests.post')
def test_results_have_indexes(self, mock_post, 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
mock_post.return_value = create_mock_response(200, json_data={
"hits": [
{"title": "Torrent 1", "seeders": 100, "leechers": 10, "magnetUrl": "magnet:?xt=1", "size": "1GB"},
{"title": "Torrent 2", "seeders": 50, "leechers": 5, "magnetUrl": "magnet:?xt=2", "size": "2GB"},
{"title": "Torrent 3", "seeders": 25, "leechers": 2, "magnetUrl": "magnet:?xt=3", "size": "3GB"}
]
})
api_tools.find_torrent("Test")
@@ -157,17 +170,10 @@ class TestFindTorrent:
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):
@patch('infrastructure.api.knaben.client.requests.post')
def test_not_found(self, mock_post, 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
mock_post.return_value = create_mock_response(200, json_data={"hits": []})
result = api_tools.find_torrent("NonexistentMovie12345")
@@ -229,112 +235,103 @@ class TestGetTorrentByIndex:
class TestAddTorrentToQbittorrent:
"""Tests for add_torrent_to_qbittorrent tool."""
"""Tests for add_torrent_to_qbittorrent tool.
Note: These tests mock the qBittorrent client because:
1. The client requires authentication/session management
2. We want to test the tool's logic (memory updates, workflow management)
3. The client itself is tested separately in infrastructure tests
This is acceptable mocking because we're testing the TOOL logic, not the client.
"""
@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
@patch('agent.tools.api.qbittorrent_client')
def test_success(self, mock_client, memory):
"""Should add torrent successfully and update memory."""
mock_client.add_torrent.return_value = True
result = api_tools.add_torrent_to_qbittorrent("magnet:?xt=urn:btih:abc123")
# Test tool logic
assert result["status"] == "ok"
# Verify client was called correctly
mock_client.add_torrent.assert_called_once_with("magnet:?xt=urn:btih:abc123")
@patch("agent.tools.api.AddTorrentUseCase")
def test_adds_to_active_downloads(
self, mock_use_case_class, memory_with_search_results
):
@patch('agent.tools.api.qbittorrent_client')
def test_adds_to_active_downloads(self, mock_client, 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
mock_client.add_torrent.return_value = True
api_tools.add_torrent_to_qbittorrent("magnet:?xt=urn:btih:abc123")
# Test memory update logic
mem = get_memory()
assert len(mem.episodic.active_downloads) == 1
assert (
mem.episodic.active_downloads[0]["name"]
== "Inception.2010.1080p.BluRay.x264"
)
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):
@patch('agent.tools.api.qbittorrent_client')
def test_sets_topic_and_ends_workflow(self, mock_client, 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
mock_client.add_torrent.return_value = True
memory.stm.start_workflow("download", {"title": "Test"})
api_tools.add_torrent_to_qbittorrent("magnet:?xt=...")
# Test workflow management logic
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
@patch('agent.tools.api.qbittorrent_client')
def test_error_handling(self, mock_client, memory):
"""Should handle client errors correctly."""
from infrastructure.api.qbittorrent.exceptions import QBittorrentAPIError
mock_client.add_torrent.side_effect = QBittorrentAPIError("Connection failed")
result = api_tools.add_torrent_to_qbittorrent("magnet:?xt=...")
# Test error handling logic
assert result["status"] == "error"
class TestAddTorrentByIndex:
"""Tests for add_torrent_by_index tool."""
"""Tests for add_torrent_by_index tool.
These tests verify the tool's logic:
- Getting torrent from memory by index
- Extracting magnet link
- Calling add_torrent_to_qbittorrent
- Error handling for edge cases
"""
@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
@patch('agent.tools.api.qbittorrent_client')
def test_success(self, mock_client, memory_with_search_results):
"""Should get torrent by index and add it."""
mock_client.add_torrent.return_value = True
result = api_tools.add_torrent_by_index(1)
# Test tool logic
assert result["status"] == "ok"
assert result["torrent_name"] == "Inception.2010.1080p.BluRay.x264"
# Verify correct magnet was extracted and used
mock_client.add_torrent.assert_called_once_with("magnet:?xt=urn:btih:abc123")
@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
@patch('agent.tools.api.qbittorrent_client')
def test_uses_correct_magnet(self, mock_client, memory_with_search_results):
"""Should extract correct magnet from index."""
mock_client.add_torrent.return_value = True
api_tools.add_torrent_by_index(2)
mock_use_case.execute.assert_called_once_with("magnet:?xt=urn:btih:def456")
# Test magnet extraction logic
mock_client.add_torrent.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)
# Test error handling logic (no mock needed)
assert result["status"] == "error"
assert result["error"] == "not_found"
@@ -342,6 +339,7 @@ class TestAddTorrentByIndex:
"""Should return error if no search results."""
result = api_tools.add_torrent_by_index(1)
# Test error handling logic (no mock needed)
assert result["status"] == "error"
assert result["error"] == "not_found"
@@ -354,5 +352,6 @@ class TestAddTorrentByIndex:
result = api_tools.add_torrent_by_index(1)
# Test error handling logic (no mock needed)
assert result["status"] == "error"
assert result["error"] == "no_magnet"