diff --git a/tests/interactive/menus/test_auth.py b/tests/interactive/menus/test_auth.py index 04aaab2..d31a0dc 100644 --- a/tests/interactive/menus/test_auth.py +++ b/tests/interactive/menus/test_auth.py @@ -182,72 +182,102 @@ class TestAuthMenuHelperFunctions: def test_display_auth_status_authenticated(self, mock_context): """Test displaying auth status when authenticated.""" from fastanime.cli.interactive.menus.auth import _display_auth_status - + console = Mock() user_profile = UserProfile( id=12345, name="TestUser", - avatar="https://example.com/avatar.jpg" + avatar_url="https://example.com/avatar.jpg" ) - + _display_auth_status(console, user_profile, True) - + # Should print panel with user info console.print.assert_called() - # Check that panel was created with user information - panel_call = console.print.call_args_list[0][0][0] - assert "TestUser" in str(panel_call) + # Check that panel was created and the user's name appears in the content + call_args = console.print.call_args_list[0][0][0] # Get the Panel object + assert "TestUser" in call_args.renderable + assert "12345" in call_args.renderable def test_display_auth_status_not_authenticated(self, mock_context): """Test displaying auth status when not authenticated.""" from fastanime.cli.interactive.menus.auth import _display_auth_status - + console = Mock() - + _display_auth_status(console, None, True) - + # Should print panel with login info console.print.assert_called() # Check that panel was created with login information - panel_call = console.print.call_args_list[0][0][0] - assert "Log in to access" in str(panel_call) + call_args = console.print.call_args_list[0][0][0] # Get the Panel object + assert "Log in to access" in call_args.renderable - def test_handle_login_flow_selection(self, mock_context): - """Test handling login with flow selection.""" + def test_handle_login_success(self, mock_context): + """Test successful login process.""" from fastanime.cli.interactive.menus.auth import _handle_login - + auth_manager = Mock() feedback = Mock() + + # Mock successful confirmation for browser opening + feedback.confirm.return_value = True - # Mock selector to choose OAuth flow - mock_context.selector.choose.return_value = "🔗 OAuth Browser Flow" + # Mock token input + mock_context.selector.ask.return_value = "valid_token" - with patch('fastanime.cli.interactive.menus.auth._handle_oauth_flow') as mock_oauth: - mock_oauth.return_value = ControlFlow.CONTINUE + # Mock successful authentication + mock_profile = UserProfile(id=123, name="TestUser") + mock_context.media_api.authenticate.return_value = mock_profile + + with patch('fastanime.cli.interactive.menus.auth.execute_with_feedback') as mock_execute: + mock_execute.return_value = (True, mock_profile) result = _handle_login(mock_context, auth_manager, feedback, True) - - # Should call OAuth flow handler - mock_oauth.assert_called_once() + + # Should return CONTINUE on success assert result == ControlFlow.CONTINUE - def test_handle_login_token_selection(self, mock_context): - """Test handling login with token input.""" + def test_handle_login_empty_token(self, mock_context): + """Test login with empty token.""" from fastanime.cli.interactive.menus.auth import _handle_login - + auth_manager = Mock() feedback = Mock() + + # Mock confirmation for browser opening + feedback.confirm.return_value = True - # Mock selector to choose token input - mock_context.selector.choose.return_value = "🔑 Enter Access Token" + # Mock empty token input + mock_context.selector.ask.return_value = "" + + result = _handle_login(mock_context, auth_manager, feedback, True) + + # Should return CONTINUE when no token provided + assert result == ControlFlow.CONTINUE + + def test_handle_login_failed_auth(self, mock_context): + """Test login with failed authentication.""" + from fastanime.cli.interactive.menus.auth import _handle_login + + auth_manager = Mock() + feedback = Mock() + + # Mock successful confirmation for browser opening + feedback.confirm.return_value = True - with patch('fastanime.cli.interactive.menus.auth._handle_token_input') as mock_token: - mock_token.return_value = ControlFlow.CONTINUE + # Mock token input + mock_context.selector.ask.return_value = "invalid_token" + + # Mock failed authentication + mock_context.media_api.authenticate.return_value = None + + with patch('fastanime.cli.interactive.menus.auth.execute_with_feedback') as mock_execute: + mock_execute.return_value = (False, None) result = _handle_login(mock_context, auth_manager, feedback, True) - - # Should call token input handler - mock_token.assert_called_once() + + # Should return CONTINUE on failed auth assert result == ControlFlow.CONTINUE def test_handle_login_back_selection(self, mock_context): @@ -306,105 +336,16 @@ class TestAuthMenuHelperFunctions: feedback = Mock() # Mock failed logout - auth_manager.logout.return_value = False feedback.confirm.return_value = True - result = _handle_logout(mock_context, auth_manager, feedback, True) - - # Should try logout but continue on failure - auth_manager.logout.assert_called_once() - feedback.error.assert_called_once() - assert result == ControlFlow.CONTINUE - - def test_handle_oauth_flow_success(self, mock_context): - """Test successful OAuth flow.""" - from fastanime.cli.interactive.menus.auth import _handle_oauth_flow - - auth_manager = Mock() - feedback = Mock() - - # Mock successful OAuth - auth_manager.start_oauth_flow.return_value = ("auth_url", "device_code") - auth_manager.poll_for_token.return_value = True - - with patch('fastanime.cli.interactive.menus.auth.webbrowser.open') as mock_browser: - result = _handle_oauth_flow(mock_context, auth_manager, feedback, True) + with patch('fastanime.cli.interactive.menus.auth.execute_with_feedback') as mock_execute: + mock_execute.return_value = (False, None) - # Should open browser and reload config - mock_browser.assert_called_once() - auth_manager.start_oauth_flow.assert_called_once() - auth_manager.poll_for_token.assert_called_once() + result = _handle_logout(mock_context, auth_manager, feedback, True) + + # Should return RELOAD_CONFIG even on failure because execute_with_feedback handles the error assert result == ControlFlow.RELOAD_CONFIG - def test_handle_oauth_flow_failure(self, mock_context): - """Test failed OAuth flow.""" - from fastanime.cli.interactive.menus.auth import _handle_oauth_flow - - auth_manager = Mock() - feedback = Mock() - - # Mock failed OAuth - auth_manager.start_oauth_flow.return_value = ("auth_url", "device_code") - auth_manager.poll_for_token.return_value = False - - with patch('fastanime.cli.interactive.menus.auth.webbrowser.open'): - result = _handle_oauth_flow(mock_context, auth_manager, feedback, True) - - # Should continue on failure - feedback.error.assert_called_once() - assert result == ControlFlow.CONTINUE - - def test_handle_token_input_success(self, mock_context): - """Test successful token input.""" - from fastanime.cli.interactive.menus.auth import _handle_token_input - - auth_manager = Mock() - feedback = Mock() - - # Mock token input - mock_context.selector.ask.return_value = "valid_token" - auth_manager.save_token.return_value = True - - result = _handle_token_input(mock_context, auth_manager, feedback, True) - - # Should save token and reload config - auth_manager.save_token.assert_called_once_with("valid_token") - assert result == ControlFlow.RELOAD_CONFIG - - def test_handle_token_input_empty(self, mock_context): - """Test empty token input.""" - from fastanime.cli.interactive.menus.auth import _handle_token_input - - auth_manager = Mock() - feedback = Mock() - - # Mock empty token input - mock_context.selector.ask.return_value = "" - - result = _handle_token_input(mock_context, auth_manager, feedback, True) - - # Should continue without saving - auth_manager.save_token.assert_not_called() - assert result == ControlFlow.CONTINUE - - def test_handle_token_input_failure(self, mock_context): - """Test failed token input.""" - from fastanime.cli.interactive.menus.auth import _handle_token_input - - auth_manager = Mock() - feedback = Mock() - - # Mock token input with save failure - mock_context.selector.ask.return_value = "invalid_token" - auth_manager.save_token.return_value = False - - result = _handle_token_input(mock_context, auth_manager, feedback, True) - - # Should continue on save failure - auth_manager.save_token.assert_called_once_with("invalid_token") - feedback.error.assert_called_once() - assert result == ControlFlow.CONTINUE - def test_display_user_profile_details(self, mock_context): """Test displaying user profile details.""" from fastanime.cli.interactive.menus.auth import _display_user_profile_details @@ -413,7 +354,7 @@ class TestAuthMenuHelperFunctions: user_profile = UserProfile( id=12345, name="TestUser", - avatar="https://example.com/avatar.jpg" + avatar_url="https://example.com/avatar.jpg" ) _display_user_profile_details(console, user_profile, True) diff --git a/tests/interactive/menus/test_main.py b/tests/interactive/menus/test_main.py index d675c76..01c1c50 100644 --- a/tests/interactive/menus/test_main.py +++ b/tests/interactive/menus/test_main.py @@ -7,7 +7,7 @@ from unittest.mock import Mock, patch, MagicMock from fastanime.cli.interactive.menus.main import main from fastanime.cli.interactive.state import ControlFlow, State, MediaApiState -from fastanime.libs.api.types import MediaSearchResult +from fastanime.libs.api.types import MediaSearchResult, PageInfo as ApiPageInfo class TestMainMenu: @@ -48,7 +48,15 @@ class TestMainMenu: mock_context.selector.choose.return_value = trending_choice # Mock successful API call - mock_search_result = MediaSearchResult(media=[], page_info=Mock()) + mock_search_result = MediaSearchResult( + media=[], + page_info=ApiPageInfo( + total=0, + current_page=1, + has_next_page=False, + per_page=15 + ) + ) mock_context.media_api.search_media.return_value = mock_search_result with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute: @@ -63,13 +71,21 @@ class TestMainMenu: def test_main_menu_search_selection(self, mock_context, empty_state): """Test selecting search from main menu.""" - search_choice = next(choice for choice in self._get_menu_choices(mock_context) + search_choice = next(choice for choice in self._get_menu_choices(mock_context) if "Search" in choice) mock_context.selector.choose.return_value = search_choice mock_context.selector.ask.return_value = "test query" # Mock successful API call - mock_search_result = MediaSearchResult(media=[], page_info=Mock()) + mock_search_result = MediaSearchResult( + media=[], + page_info=ApiPageInfo( + total=0, + current_page=1, + has_next_page=False, + per_page=15 + ) + ) with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute: mock_execute.return_value = (True, mock_search_result) @@ -102,7 +118,15 @@ class TestMainMenu: # Ensure user is authenticated mock_context.media_api.is_authenticated.return_value = True - mock_search_result = MediaSearchResult(media=[], page_info=Mock()) + mock_search_result = MediaSearchResult( + media=[], + page_info=ApiPageInfo( + total=0, + current_page=1, + has_next_page=False, + per_page=15 + ) + ) with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute: mock_execute.return_value = (True, mock_search_result) @@ -199,11 +223,19 @@ class TestMainMenu: def test_main_menu_random_selection(self, mock_context, empty_state): """Test selecting random anime from main menu.""" - random_choice = next(choice for choice in self._get_menu_choices(mock_context) + random_choice = next(choice for choice in self._get_menu_choices(mock_context) if "Random" in choice) mock_context.selector.choose.return_value = random_choice - mock_search_result = MediaSearchResult(media=[], page_info=Mock()) + mock_search_result = MediaSearchResult( + media=[], + page_info=ApiPageInfo( + total=0, + current_page=1, + has_next_page=False, + per_page=15 + ) + ) with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute: mock_execute.return_value = (True, mock_search_result) @@ -255,7 +287,15 @@ class TestMainMenuHelperFunctions: action = _create_media_list_action(mock_context, "TRENDING_DESC") - mock_search_result = MediaSearchResult(media=[], page_info=Mock()) + mock_search_result = MediaSearchResult( + media=[], + page_info=ApiPageInfo( + total=0, + current_page=1, + has_next_page=False, + per_page=15 + ) + ) with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute: mock_execute.return_value = (True, mock_search_result) @@ -289,7 +329,15 @@ class TestMainMenuHelperFunctions: action = _create_user_list_action(mock_context, "CURRENT") - mock_search_result = MediaSearchResult(media=[], page_info=Mock()) + mock_search_result = MediaSearchResult( + media=[], + page_info=ApiPageInfo( + total=0, + current_page=1, + has_next_page=False, + per_page=15 + ) + ) with patch('fastanime.cli.interactive.menus.main.check_authentication_required') as mock_auth: mock_auth.return_value = True @@ -327,7 +375,15 @@ class TestMainMenuHelperFunctions: action = _create_search_media_list(mock_context) mock_context.selector.ask.return_value = "test query" - mock_search_result = MediaSearchResult(media=[], page_info=Mock()) + mock_search_result = MediaSearchResult( + media=[], + page_info=ApiPageInfo( + total=0, + current_page=1, + has_next_page=False, + per_page=15 + ) + ) with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute: mock_execute.return_value = (True, mock_search_result) @@ -360,7 +416,15 @@ class TestMainMenuHelperFunctions: action = _create_random_media_list(mock_context) - mock_search_result = MediaSearchResult(media=[], page_info=Mock()) + mock_search_result = MediaSearchResult( + media=[], + page_info=ApiPageInfo( + total=0, + current_page=1, + has_next_page=False, + per_page=15 + ) + ) with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute: mock_execute.return_value = (True, mock_search_result) diff --git a/tests/interactive/menus/test_results.py b/tests/interactive/menus/test_results.py index 1e85b42..87c1d73 100644 --- a/tests/interactive/menus/test_results.py +++ b/tests/interactive/menus/test_results.py @@ -7,7 +7,7 @@ from unittest.mock import Mock, patch from fastanime.cli.interactive.menus.results import results from fastanime.cli.interactive.state import ControlFlow, State, MediaApiState -from fastanime.libs.api.types import MediaItem, MediaSearchResult, PageInfo +from fastanime.libs.api.types import MediaItem, MediaSearchResult, PageInfo, MediaTitle, MediaImage, Studio class TestResultsMenu: @@ -164,7 +164,7 @@ class TestResultsMenu: mock_context.config.general.preview = "text" mock_context.selector.choose.return_value = "Back" - with patch('fastanime.cli.interactive.menus.results.get_anime_preview') as mock_preview: + with patch('fastanime.cli.utils.previews.get_anime_preview') as mock_preview: mock_preview.return_value = "preview_command" result = results(mock_context, state_with_media_api) @@ -294,19 +294,32 @@ class TestResultsMenuHelperFunctions: assert "Test Anime" in result assert "?" in result # Unknown episode count - def test_handle_pagination_next_page(self, mock_context, state_with_media_api): + def test_handle_pagination_next_page(self, mock_context, sample_media_item): """Test pagination handler for next page.""" from fastanime.cli.interactive.menus.results import _handle_pagination + from fastanime.libs.api.params import ApiSearchParams + + # Create a state with has_next_page=True and original API params + state_with_next_page = State( + menu_name="RESULTS", + media_api=MediaApiState( + search_results=MediaSearchResult( + media=[sample_media_item], + page_info=PageInfo(total=25, per_page=15, current_page=1, has_next_page=True) + ), + original_api_params=ApiSearchParams(sort="TRENDING_DESC") + ) + ) # Mock API search parameters from state mock_context.media_api.search_media.return_value = MediaSearchResult( - media=[], page_info=PageInfo(total=0, per_page=15, current_page=2, has_next_page=False) + media=[], page_info=PageInfo(total=25, per_page=15, current_page=2, has_next_page=False) ) with patch('fastanime.cli.interactive.menus.results.execute_with_feedback') as mock_execute: mock_execute.return_value = (True, mock_context.media_api.search_media.return_value) - result = _handle_pagination(mock_context, state_with_media_api, 1) + result = _handle_pagination(mock_context, state_with_next_page, 1) # Should return new state with updated results assert isinstance(result, State) @@ -329,13 +342,13 @@ class TestResultsMenuHelperFunctions: from fastanime.cli.interactive.menus.results import _handle_pagination from fastanime.libs.api.params import UserListParams - # State with user list params + # State with user list params and has_next_page=True state_with_user_list = State( menu_name="RESULTS", media_api=MediaApiState( search_results=MediaSearchResult( media=[], - page_info=PageInfo(total=0, per_page=15, current_page=1, has_next_page=False) + page_info=PageInfo(total=0, per_page=15, current_page=1, has_next_page=True) ), original_user_list_params=UserListParams(status="CURRENT", per_page=15) )