Add and enhance unit tests: include tests for ProfileService, GameService, and AnalysisService; integrate comprehensive mock data and fixtures. Refactor imports and naming conventions in tests for consistency. Augment .env.example with monitoring and logging configurations.
This commit is contained in:
parent
8cc53378b7
commit
1b7963c239
9 changed files with 1814 additions and 17 deletions
|
|
@ -1 +1,355 @@
|
|||
# TODO
|
||||
"""
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue