Skip to main content
Glama
test_ratings_tools.py22.8 kB
"""Tests for the server.sync.tools module.""" import sys from datetime import datetime from pathlib import Path from unittest.mock import AsyncMock, patch import pytest sys.path.append(str(Path(__file__).parent.parent.parent.parent)) from models.movies.movie import TraktMovie from models.sync.ratings import TraktSyncRating from server.sync.tools import ( add_user_ratings, fetch_user_ratings, remove_user_ratings, ) from utils.api.error_types import TraktResourceNotFoundError from utils.api.errors import InternalError # (Intentionally left blank: inline pydantic objects are constructed per test) @pytest.mark.asyncio async def test_fetch_user_ratings_movies_success() -> None: """Test successful retrieval of user movie ratings.""" with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Mock successful response - create proper Pydantic objects movie = TraktMovie( title="TRON: Legacy", year=2010, ids={ "trakt": "1", "slug": "tron-legacy-2010", "imdb": "tt1104001", "tmdb": "20526", }, ) from models.sync.ratings import TraktSyncRating sample_ratings = [ TraktSyncRating( rated_at=datetime.fromisoformat("2014-09-01T09:10:11.000+00:00"), rating=10, type="movie", movie=movie, ) ] # Mock paginated response from models.types.pagination import ( PaginatedResponse, PaginationMetadata, ) pagination_metadata = PaginationMetadata( current_page=1, items_per_page=10, total_pages=1, total_items=1 ) paginated_response = PaginatedResponse[TraktSyncRating]( data=sample_ratings, pagination=pagination_metadata ) mock_client.get_sync_ratings = AsyncMock(return_value=paginated_response) result = await fetch_user_ratings(rating_type="movies") # Verify result contains formatted content assert "# Your Movies Ratings" in result assert "Found 1 rated movie on this page" in result assert "TRON: Legacy (2010)" in result assert "Rating 10/10" in result # Verify client was called correctly (no pagination when no page specified) mock_client.get_sync_ratings.assert_called_once_with( "movies", None, pagination=None ) @pytest.mark.asyncio async def test_fetch_user_ratings_with_rating_filter() -> None: """Test fetching user ratings with specific rating filter.""" with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Mock response with only high-rated content movie = TraktMovie( title="TRON: Legacy", year=2010, ids={ "trakt": "1", "slug": "tron-legacy-2010", "imdb": "tt1104001", "tmdb": "20526", }, ) from models.sync.ratings import TraktSyncRating high_rated_response = [ TraktSyncRating( rated_at=datetime.fromisoformat("2014-09-01T09:10:11.000+00:00"), rating=10, type="movie", movie=movie, ) ] # Mock paginated response from models.types.pagination import ( PaginatedResponse, PaginationMetadata, ) pagination_metadata = PaginationMetadata( current_page=1, items_per_page=10, total_pages=1, total_items=1 ) paginated_response = PaginatedResponse[TraktSyncRating]( data=high_rated_response, pagination=pagination_metadata ) mock_client.get_sync_ratings = AsyncMock(return_value=paginated_response) result = await fetch_user_ratings(rating_type="shows", rating=10) assert "# Your Shows Ratings (filtered to rating 10)" in result assert "Found 1 rated show on this page" in result # Verify client was called with rating filter (no pagination when no page specified) mock_client.get_sync_ratings.assert_called_once_with( "shows", 10, pagination=None ) @pytest.mark.asyncio async def test_fetch_user_ratings_empty_result() -> None: """Test fetching user ratings when no ratings exist.""" with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Mock empty paginated response from models.types.pagination import ( PaginatedResponse, PaginationMetadata, ) pagination_metadata = PaginationMetadata( current_page=1, items_per_page=10, total_pages=1, total_items=0 ) empty_paginated_response = PaginatedResponse[TraktSyncRating]( data=[], pagination=pagination_metadata ) mock_client.get_sync_ratings = AsyncMock(return_value=empty_paginated_response) result = await fetch_user_ratings(rating_type="episodes") assert "# Your Episodes Ratings" in result assert "You haven't rated any episodes yet" in result mock_client.get_sync_ratings.assert_called_once_with( "episodes", None, pagination=None ) @pytest.mark.asyncio async def test_fetch_user_ratings_error_handling() -> None: """Test error handling in fetch_user_ratings.""" with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Mock API error mock_client.get_sync_ratings = AsyncMock(side_effect=Exception("API error")) with pytest.raises(InternalError) as exc_info: await fetch_user_ratings(rating_type="movies") assert "An unexpected error occurred" in str(exc_info.value) # Import here to avoid NameError mock_client.get_sync_ratings.assert_called_once_with( "movies", None, pagination=None ) @pytest.mark.asyncio async def test_add_user_ratings_success() -> None: """Test successful addition of user ratings.""" sample_items = [{"rating": 9, "title": "Inception", "imdb_id": "tt1375666"}] with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Mock successful response - create proper Pydantic object from models.sync.ratings import ( SyncRatingsNotFound, SyncRatingsSummary, SyncRatingsSummaryCount, TraktSyncRatingItem, ) summary_response = SyncRatingsSummary( added=SyncRatingsSummaryCount(movies=1, shows=1, seasons=1, episodes=2), not_found=SyncRatingsNotFound( movies=[TraktSyncRatingItem(rating=10, ids={"imdb": "tt0000111"})], shows=[], seasons=[], episodes=[], ), ) mock_client.add_sync_ratings = AsyncMock(return_value=summary_response) result = await add_user_ratings(rating_type="movies", items=sample_items) # Verify result contains formatted summary assert "# Ratings Added - Movies" in result assert "Successfully added **1** movies rating(s)" in result assert "Movies: 1" in result assert "Shows: 1" in result # Verify client was called correctly mock_client.add_sync_ratings.assert_called_once() # Verify the request data structure call_args = mock_client.add_sync_ratings.call_args[0][0] assert hasattr(call_args, "movies") assert call_args.movies is not None assert len(call_args.movies) == 1 @pytest.mark.asyncio async def test_add_user_ratings_validation_error() -> None: """Test add user ratings with invalid request data.""" invalid_items = [ { "rating": 15, # Invalid rating (> 10) "imdb_id": "tt1375666", } ] # This should raise a validation error when creating UserRatingRequestItem from pydantic import ValidationError with pytest.raises(ValidationError): await add_user_ratings(rating_type="movies", items=invalid_items) @pytest.mark.asyncio async def test_add_user_ratings_api_error() -> None: """Test error handling in add_user_ratings.""" sample_items = [{"rating": 9, "imdb_id": "tt1375666"}] with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Mock API error mock_client.add_sync_ratings = AsyncMock(side_effect=Exception("API error")) with pytest.raises(InternalError) as exc_info: await add_user_ratings(rating_type="movies", items=sample_items) assert "An unexpected error occurred" in str(exc_info.value) @pytest.mark.asyncio async def test_remove_user_ratings_success() -> None: """Test successful removal of user ratings.""" sample_items = [{"title": "Inception", "imdb_id": "tt1375666"}] with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Mock successful response - create proper Pydantic object from models.sync.ratings import ( SyncRatingsNotFound, SyncRatingsSummary, SyncRatingsSummaryCount, ) summary_response = SyncRatingsSummary( removed=SyncRatingsSummaryCount(movies=2, shows=1, seasons=0, episodes=1), not_found=SyncRatingsNotFound(movies=[], shows=[], seasons=[], episodes=[]), ) mock_client.remove_sync_ratings = AsyncMock(return_value=summary_response) result = await remove_user_ratings(rating_type="movies", items=sample_items) # Verify result contains formatted summary assert "# Ratings Removed - Movies" in result assert "Successfully removed **2** movies rating(s)" in result assert "Movies: 2" in result assert "Shows: 1" in result assert "Episodes: 1" in result # Verify client was called correctly mock_client.remove_sync_ratings.assert_called_once() # Verify the request data structure call_args = mock_client.remove_sync_ratings.call_args[0][0] assert hasattr(call_args, "movies") assert call_args.movies is not None assert len(call_args.movies) == 1 @pytest.mark.asyncio async def test_remove_user_ratings_with_not_found() -> None: """Test remove user ratings when some items are not found.""" sample_items = [{"trakt_id": "123"}] # Response with not_found items (data moved to Pydantic object creation) with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Create proper Pydantic object from models.sync.ratings import ( SyncRatingsNotFound, SyncRatingsSummary, SyncRatingsSummaryCount, TraktSyncRatingItem, ) summary_response = SyncRatingsSummary( removed=SyncRatingsSummaryCount(movies=0, shows=0, seasons=0, episodes=0), not_found=SyncRatingsNotFound( movies=[], shows=[TraktSyncRatingItem(ids={"trakt": "123"})], seasons=[], episodes=[], ), ) mock_client.remove_sync_ratings = AsyncMock(return_value=summary_response) result = await remove_user_ratings(rating_type="shows", items=sample_items) assert "# Ratings Removed - Shows" in result assert "No shows ratings were removed" in result assert "Items Not Found (1)" in result @pytest.mark.asyncio async def test_remove_user_ratings_api_error() -> None: """Test error handling in remove_user_ratings.""" sample_items = [{"imdb_id": "tt1375666"}] with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Mock API error mock_client.remove_sync_ratings = AsyncMock( side_effect=TraktResourceNotFoundError("user", "ratings", "Not found") ) with pytest.raises(TraktResourceNotFoundError): await remove_user_ratings(rating_type="movies", items=sample_items) @pytest.mark.asyncio async def test_all_tools_content_type_validation() -> None: """Test that content_type parameter is validated correctly.""" # fetch_user_ratings should accept valid content types valid_types = ["movies", "shows", "seasons", "episodes"] for content_type in valid_types: with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Mock paginated response for empty results from models.types.pagination import PaginatedResponse, PaginationMetadata pagination_metadata = PaginationMetadata( current_page=1, items_per_page=10, total_pages=1, total_items=0 ) empty_paginated_response = PaginatedResponse[TraktSyncRating]( data=[], pagination=pagination_metadata ) mock_client.get_sync_ratings = AsyncMock( return_value=empty_paginated_response ) result = await fetch_user_ratings(rating_type=content_type) assert f"You haven't rated any {content_type} yet" in result # Import here to avoid NameError mock_client.get_sync_ratings.assert_called_with( content_type, None, pagination=None ) @pytest.mark.asyncio async def test_rating_parameter_validation() -> None: """Test that rating parameter is validated correctly.""" valid_ratings = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] for rating in valid_ratings: with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Mock paginated response for empty results from models.types.pagination import PaginatedResponse, PaginationMetadata pagination_metadata = PaginationMetadata( current_page=1, items_per_page=10, total_pages=1, total_items=0 ) empty_paginated_response = PaginatedResponse[TraktSyncRating]( data=[], pagination=pagination_metadata ) mock_client.get_sync_ratings = AsyncMock( return_value=empty_paginated_response ) result = await fetch_user_ratings(rating_type="movies", rating=rating) assert f"You haven't rated any movies with rating {rating} yet" in result # Import here to avoid NameError mock_client.get_sync_ratings.assert_called_with( "movies", rating, pagination=None ) # Pagination tests @pytest.mark.asyncio async def test_fetch_user_ratings_paginated_success() -> None: """Test successful retrieval of paginated user ratings.""" with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Mock successful paginated response movie = TraktMovie( title="TRON: Legacy", year=2010, ids={ "trakt": "1", "slug": "tron-legacy-2010", "imdb": "tt1104001", "tmdb": "20526", }, ) from models.sync.ratings import TraktSyncRating from models.types.pagination import PaginatedResponse, PaginationMetadata sample_ratings = [ TraktSyncRating( rated_at=datetime.fromisoformat("2014-09-01T09:10:11.000+00:00"), rating=10, type="movie", movie=movie, ) ] pagination_metadata = PaginationMetadata( current_page=1, items_per_page=10, total_pages=3, total_items=25 ) paginated_response = PaginatedResponse[TraktSyncRating]( data=sample_ratings, pagination=pagination_metadata ) mock_client.get_sync_ratings = AsyncMock(return_value=paginated_response) result = await fetch_user_ratings(rating_type="movies", page=1) # Verify result contains paginated content assert "# Your Movies Ratings" in result assert "📄 **Page 1 of 3" in result assert "items 1-1 of 25" in result assert "📍 **Navigation:** Next: page 2" in result assert "Found 1 rated movie on this page" in result assert "TRON: Legacy (2010)" in result # Verify paginated client method was called mock_client.get_sync_ratings.assert_called_once() args, kwargs = mock_client.get_sync_ratings.call_args assert args[0] == "movies" assert args[1] is None # rating filter assert "pagination" in kwargs from config.api import DEFAULT_LIMIT pagination_params = kwargs["pagination"] assert pagination_params.page == 1 assert pagination_params.limit == DEFAULT_LIMIT # uses DEFAULT_LIMIT @pytest.mark.asyncio async def test_fetch_user_ratings_paginated_with_filter() -> None: """Test paginated user ratings with rating filter.""" with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Mock paginated response with rating filter movie = TraktMovie( title="The Dark Knight", year=2008, ids={ "trakt": "6", "slug": "the-dark-knight-2008", "imdb": "tt0468569", "tmdb": "155", }, ) from models.sync.ratings import TraktSyncRating from models.types.pagination import PaginatedResponse, PaginationMetadata sample_ratings = [ TraktSyncRating( rated_at=datetime.fromisoformat("2014-09-01T09:10:11.000+00:00"), rating=10, type="movie", movie=movie, ) ] pagination_metadata = PaginationMetadata( current_page=2, items_per_page=5, total_pages=2, total_items=8 ) paginated_response = PaginatedResponse[TraktSyncRating]( data=sample_ratings, pagination=pagination_metadata ) mock_client.get_sync_ratings = AsyncMock(return_value=paginated_response) result = await fetch_user_ratings(rating_type="movies", rating=10, page=2) # Verify result contains paginated content with rating filter assert "# Your Movies Ratings (filtered to rating 10)" in result assert "📄 **Page 2 of 2" in result assert "📍 **Navigation:** Previous: page 1" in result assert "The Dark Knight (2008)" in result # Verify paginated client method was called with rating filter mock_client.get_sync_ratings.assert_called_once() args, kwargs = mock_client.get_sync_ratings.call_args assert args[0] == "movies" assert args[1] == 10 # rating filter pagination_params = kwargs["pagination"] assert pagination_params.page == 2 @pytest.mark.asyncio async def test_fetch_user_ratings_paginated_empty_result() -> None: """Test paginated user ratings with empty results.""" with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Mock empty paginated response from models.types.pagination import PaginatedResponse, PaginationMetadata pagination_metadata = PaginationMetadata( current_page=1, items_per_page=10, total_pages=1, total_items=0 ) paginated_response = PaginatedResponse[TraktSyncRating]( data=[], pagination=pagination_metadata ) mock_client.get_sync_ratings = AsyncMock(return_value=paginated_response) result = await fetch_user_ratings(rating_type="shows", page=1) # Verify result contains paginated empty state assert "# Your Shows Ratings" in result assert "You haven't rated any shows yet" in result assert "📄 **Pagination Info:** 0 total items" in result # Verify paginated client method was called mock_client.get_sync_ratings.assert_called_once() @pytest.mark.asyncio # REMOVED: Test for backward compatibility that no longer exists after cleanup @pytest.mark.asyncio async def test_fetch_user_ratings_paginated_error_handling() -> None: """Test error handling in paginated user ratings retrieval.""" with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Mock API error in paginated request mock_client.get_sync_ratings = AsyncMock(side_effect=Exception("API error")) with pytest.raises(InternalError) as exc_info: await fetch_user_ratings(rating_type="movies", page=1) assert "An unexpected error occurred" in str(exc_info.value) mock_client.get_sync_ratings.assert_called_once() @pytest.mark.asyncio async def test_fetch_user_ratings_page_parameter_validation() -> None: """Test that page parameter is validated correctly.""" with patch("server.sync.tools.SyncClient") as mock_client_class: mock_client = mock_client_class.return_value # Valid page numbers should work valid_pages = [1, 2, 5, 10, 100] for page_num in valid_pages: from models.types.pagination import ( PaginatedResponse, PaginationMetadata, ) pagination_metadata = PaginationMetadata( current_page=page_num, items_per_page=10, total_pages=100, total_items=1000, ) paginated_response = PaginatedResponse[TraktSyncRating]( data=[], pagination=pagination_metadata ) mock_client.get_sync_ratings = AsyncMock(return_value=paginated_response) result = await fetch_user_ratings(rating_type="movies", page=page_num) assert f"Page {page_num} of 100" in result # Invalid page numbers should raise validation error from pydantic import ValidationError invalid_pages = [0, -1, -10] for invalid_page in invalid_pages: with pytest.raises(ValidationError): await fetch_user_ratings(rating_type="movies", page=invalid_page)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wwiens/trakt_mcpserver'

If you have feedback or need assistance with the MCP directory API, please join our Discord server