"""
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