Files
FastAnime/tests/interactive/menus/test_main.py
2025-07-15 00:02:55 +03:00

377 lines
17 KiB
Python

"""
Tests for the main menu functionality.
"""
import pytest
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
class TestMainMenu:
"""Test cases for the main menu."""
def test_main_menu_displays_options(self, mock_context, empty_state):
"""Test that the main menu displays all expected options."""
# Setup selector to return None (exit)
mock_context.selector.choose.return_value = None
result = main(mock_context, empty_state)
# Should return EXIT when no choice is made
assert result == ControlFlow.EXIT
# Verify selector was called with expected options
mock_context.selector.choose.assert_called_once()
call_args = mock_context.selector.choose.call_args
choices = call_args[1]['choices']
# Check that key options are present
expected_options = [
"Trending", "Popular", "Favourites", "Top Scored",
"Upcoming", "Recently Updated", "Random", "Search",
"Watching", "Planned", "Completed", "Paused", "Dropped", "Rewatching",
"Local Watch History", "Authentication", "Session Management",
"Edit Config", "Exit"
]
for option in expected_options:
assert any(option in choice for choice in choices)
def test_main_menu_trending_selection(self, mock_context, empty_state):
"""Test selecting trending anime from main menu."""
# Setup selector to return trending choice
trending_choice = next(choice for choice in self._get_menu_choices(mock_context)
if "Trending" in choice)
mock_context.selector.choose.return_value = trending_choice
# Mock successful API call
mock_search_result = MediaSearchResult(media=[], page_info=Mock())
mock_context.media_api.search_media.return_value = mock_search_result
with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute:
mock_execute.return_value = (True, mock_search_result)
result = main(mock_context, empty_state)
# Should transition to RESULTS state
assert isinstance(result, State)
assert result.menu_name == "RESULTS"
assert result.media_api.search_results == mock_search_result
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)
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())
with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute:
mock_execute.return_value = (True, mock_search_result)
result = main(mock_context, empty_state)
# Should transition to RESULTS state
assert isinstance(result, State)
assert result.menu_name == "RESULTS"
assert result.media_api.search_results == mock_search_result
def test_main_menu_search_empty_query(self, mock_context, empty_state):
"""Test search with empty query returns to menu."""
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 = "" # Empty query
result = main(mock_context, empty_state)
# Should return CONTINUE when search query is empty
assert result == ControlFlow.CONTINUE
def test_main_menu_user_list_authenticated(self, mock_context, empty_state):
"""Test accessing user list when authenticated."""
watching_choice = next(choice for choice in self._get_menu_choices(mock_context)
if "Watching" in choice)
mock_context.selector.choose.return_value = watching_choice
# Ensure user is authenticated
mock_context.media_api.is_authenticated.return_value = True
mock_search_result = MediaSearchResult(media=[], page_info=Mock())
with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute:
mock_execute.return_value = (True, mock_search_result)
result = main(mock_context, empty_state)
# Should transition to RESULTS state
assert isinstance(result, State)
assert result.menu_name == "RESULTS"
def test_main_menu_user_list_not_authenticated(self, mock_context, empty_state):
"""Test accessing user list when not authenticated."""
watching_choice = next(choice for choice in self._get_menu_choices(mock_context)
if "Watching" in choice)
mock_context.selector.choose.return_value = watching_choice
# User not authenticated
mock_context.media_api.is_authenticated.return_value = False
with patch('fastanime.cli.interactive.menus.main.check_authentication_required') as mock_auth:
mock_auth.return_value = False # Authentication check fails
result = main(mock_context, empty_state)
# Should return CONTINUE when authentication is required but not provided
assert result == ControlFlow.CONTINUE
def test_main_menu_exit_selection(self, mock_context, empty_state):
"""Test selecting exit from main menu."""
exit_choice = next(choice for choice in self._get_menu_choices(mock_context)
if "Exit" in choice)
mock_context.selector.choose.return_value = exit_choice
result = main(mock_context, empty_state)
assert result == ControlFlow.EXIT
def test_main_menu_config_edit_selection(self, mock_context, empty_state):
"""Test selecting config edit from main menu."""
config_choice = next(choice for choice in self._get_menu_choices(mock_context)
if "Edit Config" in choice)
mock_context.selector.choose.return_value = config_choice
result = main(mock_context, empty_state)
assert result == ControlFlow.RELOAD_CONFIG
def test_main_menu_session_management_selection(self, mock_context, empty_state):
"""Test selecting session management from main menu."""
session_choice = next(choice for choice in self._get_menu_choices(mock_context)
if "Session Management" in choice)
mock_context.selector.choose.return_value = session_choice
result = main(mock_context, empty_state)
assert isinstance(result, State)
assert result.menu_name == "SESSION_MANAGEMENT"
def test_main_menu_auth_selection(self, mock_context, empty_state):
"""Test selecting authentication from main menu."""
auth_choice = next(choice for choice in self._get_menu_choices(mock_context)
if "Authentication" in choice)
mock_context.selector.choose.return_value = auth_choice
result = main(mock_context, empty_state)
assert isinstance(result, State)
assert result.menu_name == "AUTH"
def test_main_menu_watch_history_selection(self, mock_context, empty_state):
"""Test selecting local watch history from main menu."""
history_choice = next(choice for choice in self._get_menu_choices(mock_context)
if "Local Watch History" in choice)
mock_context.selector.choose.return_value = history_choice
result = main(mock_context, empty_state)
assert isinstance(result, State)
assert result.menu_name == "WATCH_HISTORY"
def test_main_menu_api_failure(self, mock_context, empty_state):
"""Test handling API failures in main menu."""
trending_choice = next(choice for choice in self._get_menu_choices(mock_context)
if "Trending" in choice)
mock_context.selector.choose.return_value = trending_choice
with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute:
mock_execute.return_value = (False, None) # API failure
result = main(mock_context, empty_state)
# Should return CONTINUE on API failure
assert result == ControlFlow.CONTINUE
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)
if "Random" in choice)
mock_context.selector.choose.return_value = random_choice
mock_search_result = MediaSearchResult(media=[], page_info=Mock())
with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute:
mock_execute.return_value = (True, mock_search_result)
result = main(mock_context, empty_state)
# Should transition to RESULTS state
assert isinstance(result, State)
assert result.menu_name == "RESULTS"
assert result.media_api.search_results == mock_search_result
def test_main_menu_icons_enabled(self, mock_context, empty_state):
"""Test main menu with icons enabled."""
mock_context.config.general.icons = True
# Just ensure menu doesn't crash with icons enabled
mock_context.selector.choose.return_value = None
result = main(mock_context, empty_state)
assert result == ControlFlow.EXIT
def test_main_menu_icons_disabled(self, mock_context, empty_state):
"""Test main menu with icons disabled."""
mock_context.config.general.icons = False
# Just ensure menu doesn't crash with icons disabled
mock_context.selector.choose.return_value = None
result = main(mock_context, empty_state)
assert result == ControlFlow.EXIT
def _get_menu_choices(self, mock_context):
"""Helper to get the menu choices from a mock call."""
# Temporarily call the menu to get choices
mock_context.selector.choose.return_value = None
main(mock_context, State(menu_name="TEST"))
# Extract choices from the call
call_args = mock_context.selector.choose.call_args
return call_args[1]['choices']
class TestMainMenuHelperFunctions:
"""Test the helper functions in main menu."""
def test_create_media_list_action_success(self, mock_context):
"""Test creating a media list action that succeeds."""
from fastanime.cli.interactive.menus.main import _create_media_list_action
action = _create_media_list_action(mock_context, "TRENDING_DESC")
mock_search_result = MediaSearchResult(media=[], page_info=Mock())
with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute:
mock_execute.return_value = (True, mock_search_result)
menu_name, result, api_params, user_list_params = action()
assert menu_name == "RESULTS"
assert result == mock_search_result
assert api_params is not None
assert user_list_params is None
def test_create_media_list_action_failure(self, mock_context):
"""Test creating a media list action that fails."""
from fastanime.cli.interactive.menus.main import _create_media_list_action
action = _create_media_list_action(mock_context, "TRENDING_DESC")
with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute:
mock_execute.return_value = (False, None)
menu_name, result, api_params, user_list_params = action()
assert menu_name == "CONTINUE"
assert result is None
assert api_params is None
assert user_list_params is None
def test_create_user_list_action_authenticated(self, mock_context):
"""Test creating a user list action when authenticated."""
from fastanime.cli.interactive.menus.main import _create_user_list_action
action = _create_user_list_action(mock_context, "CURRENT")
mock_search_result = MediaSearchResult(media=[], page_info=Mock())
with patch('fastanime.cli.interactive.menus.main.check_authentication_required') as mock_auth:
mock_auth.return_value = True
with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute:
mock_execute.return_value = (True, mock_search_result)
menu_name, result, api_params, user_list_params = action()
assert menu_name == "RESULTS"
assert result == mock_search_result
assert api_params is None
assert user_list_params is not None
def test_create_user_list_action_not_authenticated(self, mock_context):
"""Test creating a user list action when not authenticated."""
from fastanime.cli.interactive.menus.main import _create_user_list_action
action = _create_user_list_action(mock_context, "CURRENT")
with patch('fastanime.cli.interactive.menus.main.check_authentication_required') as mock_auth:
mock_auth.return_value = False
menu_name, result, api_params, user_list_params = action()
assert menu_name == "CONTINUE"
assert result is None
assert api_params is None
assert user_list_params is None
def test_create_search_media_list_with_query(self, mock_context):
"""Test creating a search media list action with a query."""
from fastanime.cli.interactive.menus.main import _create_search_media_list
action = _create_search_media_list(mock_context)
mock_context.selector.ask.return_value = "test query"
mock_search_result = MediaSearchResult(media=[], page_info=Mock())
with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute:
mock_execute.return_value = (True, mock_search_result)
menu_name, result, api_params, user_list_params = action()
assert menu_name == "RESULTS"
assert result == mock_search_result
assert api_params is not None
assert user_list_params is None
def test_create_search_media_list_no_query(self, mock_context):
"""Test creating a search media list action without a query."""
from fastanime.cli.interactive.menus.main import _create_search_media_list
action = _create_search_media_list(mock_context)
mock_context.selector.ask.return_value = "" # Empty query
menu_name, result, api_params, user_list_params = action()
assert menu_name == "CONTINUE"
assert result is None
assert api_params is None
assert user_list_params is None
def test_create_random_media_list(self, mock_context):
"""Test creating a random media list action."""
from fastanime.cli.interactive.menus.main import _create_random_media_list
action = _create_random_media_list(mock_context)
mock_search_result = MediaSearchResult(media=[], page_info=Mock())
with patch('fastanime.cli.interactive.menus.main.execute_with_feedback') as mock_execute:
mock_execute.return_value = (True, mock_search_result)
menu_name, result, api_params, user_list_params = action()
assert menu_name == "RESULTS"
assert result == mock_search_result
assert api_params is not None
assert user_list_params is None
# Check that random IDs were used
assert api_params.id_in is not None
assert len(api_params.id_in) == 50