""" This module contains unit tests for classes and services provided in the `geoguessr_mcp` library. It includes test cases for analyzing games, evaluating statistical trends, and extracting detailed insights from game performance data. """ from unittest.mock import patch import pytest from geoguessr_mcp.models import Game, RoundGuess from geoguessr_mcp.services.analysis_service import AnalysisService, GameAnalysis class TestGameAnalysis: """Tests for GameAnalysis dataclass.""" def test_default_values(self): """Test GameAnalysis default values.""" analysis = GameAnalysis() assert analysis.games_analyzed == 0 assert analysis.total_score == 0 assert analysis.average_score == 0.0 assert analysis.perfect_round_percentage == 0.0 assert analysis.score_trend == "stable" assert analysis.weak_areas == [] assert analysis.strong_areas == [] def test_to_dict(self): """Test GameAnalysis to_dict method.""" analysis = GameAnalysis( games_analyzed=10, total_score=200000, average_score=20000.5, total_rounds=50, perfect_rounds=15, perfect_round_percentage=30.0, average_distance_meters=500.123, average_time_seconds=45.678, best_game_score=25000, worst_game_score=15000, score_trend="improving", ) result = analysis.to_dict() assert result["games_analyzed"] == 10 assert result["average_score"] == 20000.5 assert result["perfect_round_percentage"] == 30.0 assert result["score_trend"] == "improving" class TestAnalysisService: """Tests for AnalysisService.""" def test_analyze_games_empty(self): """Test analyzing empty game list.""" result = AnalysisService.analyze_games([]) assert result.games_analyzed == 0 assert result.total_score == 0 assert result.average_score == 0.0 def test_analyze_games_single_game(self): """Test analyzing single game.""" rounds = [ RoundGuess(round_number=1, score=5000, distance_meters=0, time_seconds=20), RoundGuess(round_number=2, score=4500, distance_meters=100, time_seconds=25), RoundGuess(round_number=3, score=4000, distance_meters=200, time_seconds=30), ] game = Game( token="test-game", map_name="World", mode="standard", total_score=13500, rounds=rounds, finished=True, ) result = AnalysisService.analyze_games([game]) assert result.games_analyzed == 1 assert result.total_score == 13500 assert result.average_score == 13500 assert result.total_rounds == 3 assert result.perfect_rounds == 1 assert result.perfect_round_percentage == pytest.approx(33.33, rel=0.01) def test_analyze_games_multiple_games(self, sample_games): """Test analyzing multiple games.""" result = AnalysisService.analyze_games(sample_games) assert result.games_analyzed == 5 assert result.total_rounds == 25 assert result.average_score == result.total_score / 5 assert result.best_game_score >= result.worst_game_score def test_analyze_games_trend_improving(self): """Test score trend detection - improving.""" # Create games with improving scores games = [] for i in range(6): base_score = 15000 + (i * 2000) # Increasing scores rounds = [ RoundGuess(round_number=1, score=base_score, distance_meters=100, time_seconds=30) ] game = Game( token=f"game-{i}", map_name="World", mode="standard", total_score=base_score, rounds=rounds, finished=True, ) games.append(game) result = AnalysisService.analyze_games(games) assert result.score_trend == "improving" def test_analyze_games_trend_declining(self): """Test score trend detection - declining.""" # Create games with declining scores games = [] for i in range(6): base_score = 25000 - (i * 2000) # Decreasing scores rounds = [ RoundGuess(round_number=1, score=base_score, distance_meters=100, time_seconds=30) ] game = Game( token=f"game-{i}", map_name="World", mode="standard", total_score=base_score, rounds=rounds, finished=True, ) games.append(game) result = AnalysisService.analyze_games(games) assert result.score_trend == "declining" def test_analyze_games_weak_areas(self): """Test weak areas' identification.""" rounds = [ RoundGuess(round_number=1, score=5000, distance_meters=0, time_seconds=20), RoundGuess(round_number=2, score=1500, distance_meters=5000, time_seconds=60), # Weak RoundGuess(round_number=3, score=1000, distance_meters=8000, time_seconds=90), # Weak ] game = Game( token="test-game", map_name="World", mode="standard", total_score=7500, rounds=rounds, finished=True, ) result = AnalysisService.analyze_games([game]) assert len(result.weak_areas) == 2 assert all(area["score"] < 2000 for area in result.weak_areas) def test_analyze_games_strong_areas(self): """Test strong areas' identification.""" rounds = [ RoundGuess(round_number=1, score=5000, distance_meters=0, time_seconds=20), RoundGuess(round_number=2, score=4800, distance_meters=50, time_seconds=25), RoundGuess(round_number=3, score=2000, distance_meters=500, time_seconds=30), ] game = Game( token="test-game", map_name="World", mode="standard", total_score=11800, rounds=rounds, finished=True, ) result = AnalysisService.analyze_games([game]) assert len(result.strong_areas) == 2 assert all(area["score"] >= 4500 for area in result.strong_areas) @pytest.mark.asyncio async def test_analyze_recent_games(self, analysis_service, mock_game_service, sample_games): """Test analyze_recent_games method.""" mock_game_service.get_recent_games.return_value = sample_games with patch.object(analysis_service, "analyze_games", wraps=AnalysisService.analyze_games): result = await analysis_service.analyze_recent_games(count=5) assert "analysis" in result assert "games" in result assert result["analysis"]["games_analyzed"] == 5 mock_game_service.get_recent_games.assert_called_once_with(5, None) @pytest.mark.asyncio async def test_analyze_recent_games_with_session( self, analysis_service, mock_game_service, sample_games ): """Test analyze_recent_games with session token.""" mock_game_service.get_recent_games.return_value = sample_games await analysis_service.analyze_recent_games(count=10, session_token="test_token") mock_game_service.get_recent_games.assert_called_once_with(10, "test_token") @pytest.mark.asyncio async def test_get_performance_summary( self, analysis_service, mock_game_service, mock_profile_service, mock_client, sample_games, mock_season_stats_data, mock_dynamic_response, ): """Test comprehensive performance summary.""" mock_profile_service.get_comprehensive_profile.return_value = { "profile": {"nick": "TestPlayer"}, "stats": {"games_played": 100}, } mock_season_response = mock_dynamic_response(mock_season_stats_data) from geoguessr_mcp.models import SeasonStats mock_season_stats = SeasonStats.from_api_response(mock_season_stats_data) mock_game_service.get_season_stats.return_value = (mock_season_stats, mock_season_response) mock_game_service.get_recent_games.return_value = sample_games[:3] mock_client.get.return_value = mock_dynamic_response({"progress": 0.5}) result = await analysis_service.get_performance_summary() assert result["profile"] is not None assert result["season"] is not None assert result["recent_games_analysis"] is not None assert "api_status" in result @pytest.mark.asyncio async def test_get_performance_summary_with_errors( self, analysis_service, mock_game_service, mock_profile_service, mock_client ): """Test performance summary handles errors gracefully.""" mock_profile_service.get_comprehensive_profile.side_effect = Exception("Profile error") mock_game_service.get_season_stats.side_effect = Exception("Season error") mock_game_service.get_recent_games.return_value = [] mock_client.get.side_effect = Exception("API error") result = await analysis_service.get_performance_summary() assert len(result["errors"]) > 0 assert result["profile"] is None assert result["season"] is None @pytest.mark.asyncio async def test_get_strategy_recommendations_low_perfect_rate( self, analysis_service, mock_game_service ): """Test strategy recommendations for low perfect round rate.""" # Create games with no perfect rounds rounds = [ RoundGuess(round_number=i, score=3000, distance_meters=500, time_seconds=30) for i in range(5) ] games = [ Game( token="g1", map_name="World", mode="standard", total_score=15000, rounds=rounds, finished=True, ) for _ in range(5) ] mock_game_service.get_recent_games.return_value = games result = await analysis_service.get_strategy_recommendations() assert len(result["recommendations"]) > 0 accuracy_recs = [r for r in result["recommendations"] if r["category"] == "accuracy"] assert len(accuracy_recs) > 0 @pytest.mark.asyncio async def test_get_strategy_recommendations_fast_play( self, analysis_service, mock_game_service ): """Test strategy recommendations for fast play style.""" # Create games with very short time rounds = [ RoundGuess(round_number=i, score=3500, distance_meters=300, time_seconds=15) for i in range(5) ] games = [ Game( token="g1", map_name="World", mode="standard", total_score=17500, rounds=rounds, finished=True, ) for _ in range(5) ] mock_game_service.get_recent_games.return_value = games result = await analysis_service.get_strategy_recommendations() time_recs = [r for r in result["recommendations"] if r["category"] == "time_management"] assert len(time_recs) > 0 @pytest.mark.asyncio async def test_get_strategy_recommendations_declining_trend( self, analysis_service, mock_game_service ): """Test strategy recommendations for declining performance.""" # Create games with declining scores games = [] for i in range(6): base_score = 25000 - (i * 3000) rounds = [ RoundGuess(round_number=1, score=base_score, distance_meters=100, time_seconds=45) ] game = Game( token=f"game-{i}", map_name="World", mode="standard", total_score=base_score, rounds=rounds, finished=True, ) games.append(game) mock_game_service.get_recent_games.return_value = games result = await analysis_service.get_strategy_recommendations() consistency_recs = [r for r in result["recommendations"] if r["category"] == "consistency"] assert len(consistency_recs) > 0 assert result["analysis_summary"]["trend"] == "declining" @pytest.mark.asyncio async def test_get_strategy_recommendations_many_weak_areas( self, analysis_service, mock_game_service ): """Test strategy recommendations for many weak rounds.""" # Create games with many low scores games = [] for i in range(4): rounds = [ RoundGuess(round_number=j, score=1500, distance_meters=5000, time_seconds=60) for j in range(5) ] game = Game( token=f"game-{i}", map_name="World", mode="standard", total_score=7500, rounds=rounds, finished=True, ) games.append(game) mock_game_service.get_recent_games.return_value = games result = await analysis_service.get_strategy_recommendations() practice_recs = [r for r in result["recommendations"] if r["category"] == "practice"] assert len(practice_recs) > 0