test: enhance authentication and main menu tests with detailed user profile and pagination handling

This commit is contained in:
Benexl
2025-07-15 01:12:25 +03:00
parent bdbf0821c5
commit 0639a3c949
3 changed files with 163 additions and 145 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
)