Add comprehensive unit tests: reorganized test files, removed outdated e2e and unit tests for auth and profile_service, introduced tests for SchemaRegistry, UserProfile, UserStats, Game, Achievement, and SeasonStats.

This commit is contained in:
Yûki VACHOT 2025-11-29 00:47:42 +01:00
parent f6226f51e4
commit f9011dbeaa
24 changed files with 623 additions and 126 deletions

View file

@ -1,16 +0,0 @@
"""Integration tests for authentication."""
import pytest
from geoguessr_mcp.auth.session import SessionManager
@pytest.mark.integration
class TestAuthFlow:
"""Integration tests for authentication flow."""
@pytest.mark.asyncio
async def test_full_login_logout_cycle(self):
"""Test complete login and logout cycle."""
# This would use real API calls in a test environment
pass

View file

@ -0,0 +1 @@
"""Integration tests for GeoGuessr MCP Server."""

View file

@ -0,0 +1 @@
# TODO

View file

@ -0,0 +1 @@
# TODO

View file

@ -0,0 +1 @@
"""Unit tests for GeoGuessr MCP Server."""

View file

@ -1,16 +1,30 @@
"""Unit tests for session management.""" """
This module contains test cases for the UserSession dataclass and the
SessionManager class, which handle session authentication and management
functionality.
from datetime import UTC, datetime, timedelta The test cases validate the implementation of session validity, login,
from unittest.mock import AsyncMock, MagicMock, patch logout, and retrieval features for these classes under various scenarios.
These tests ensure that the session and authentication-related operations
perform correctly and as expected under different conditions.
Classes
-------
TestUserSession
Provides unit tests for the UserSession dataclass, which represents
user session details.
TestSessionManager
Provides unit tests for the SessionManager class, which facilitates
login, logout, and session management operations in an async context.
"""
import pytest import pytest
from datetime import datetime, timedelta, UTC
from unittest.mock import AsyncMock, MagicMock, patch
from geoguessr_mcp.auth.session import SessionManager, UserSession from geoguessr_mcp.auth.session import SessionManager, UserSession
# ============================================================================
# USER SESSION TESTS
# ============================================================================
class TestUserSession: class TestUserSession:
"""Tests for UserSession dataclass.""" """Tests for UserSession dataclass."""
@ -40,14 +54,24 @@ class TestUserSession:
def test_session_without_cookie(self): def test_session_without_cookie(self):
"""Test that a session without cookie is invalid.""" """Test that a session without cookie is invalid."""
session = UserSession( session = UserSession(
ncfa_cookie="", user_id="user123", username="TestUser", email="test@example.com" ncfa_cookie="",
user_id="user123",
username="TestUser",
email="test@example.com",
) )
assert not session.is_valid() assert not session.is_valid()
def test_session_no_expiry(self):
"""Test session without expiration date."""
session = UserSession(
ncfa_cookie="test_cookie",
user_id="user123",
username="TestUser",
email="test@example.com",
expires_at=None,
)
assert session.is_valid()
# ============================================================================
# SESSION MANAGER TESTS
# ============================================================================
class TestSessionManager: class TestSessionManager:
"""Tests for SessionManager.""" """Tests for SessionManager."""
@ -55,11 +79,9 @@ class TestSessionManager:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_login_success(self, mock_profile_response): async def test_login_success(self, mock_profile_response):
"""Test successful login flow.""" """Test successful login flow."""
manager = SessionManager() manager = SessionManager()
with patch("httpx.AsyncClient") as mock_client_class: with patch("httpx.AsyncClient") as mock_client_class:
# Create mock client
mock_client = AsyncMock() mock_client = AsyncMock()
mock_client.__aenter__.return_value = mock_client mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None mock_client.__aexit__.return_value = None
@ -70,7 +92,6 @@ class TestSessionManager:
login_response.status_code = 200 login_response.status_code = 200
login_response.cookies.jar = [] login_response.cookies.jar = []
# Create mock cookie
mock_cookie = MagicMock() mock_cookie = MagicMock()
mock_cookie.name = "_ncfa" mock_cookie.name = "_ncfa"
mock_cookie.value = "test_ncfa_cookie_value" mock_cookie.value = "test_ncfa_cookie_value"
@ -81,7 +102,6 @@ class TestSessionManager:
profile_response.status_code = 200 profile_response.status_code = 200
profile_response.json.return_value = mock_profile_response profile_response.json.return_value = mock_profile_response
# Set up mock client responses
mock_client.post = AsyncMock(return_value=login_response) mock_client.post = AsyncMock(return_value=login_response)
mock_client.get = AsyncMock(return_value=profile_response) mock_client.get = AsyncMock(return_value=profile_response)
mock_client.cookies.set = MagicMock() mock_client.cookies.set = MagicMock()
@ -89,13 +109,11 @@ class TestSessionManager:
# Perform login # Perform login
session_token, session = await manager.login("test@example.com", "password123") session_token, session = await manager.login("test@example.com", "password123")
# Assertions
assert session_token is not None assert session_token is not None
assert len(session_token) > 0 assert len(session_token) > 0
assert session.ncfa_cookie == "test_ncfa_cookie_value" assert session.ncfa_cookie == "test_ncfa_cookie_value"
assert session.user_id == "test-user-id" assert session.user_id == "test-user-id-123"
assert session.username == "TestPlayer" assert session.username == "TestPlayer"
assert session.email == "test@example.com"
assert session.is_valid() assert session.is_valid()
@pytest.mark.asyncio @pytest.mark.asyncio
@ -109,23 +127,37 @@ class TestSessionManager:
mock_client.__aexit__.return_value = None mock_client.__aexit__.return_value = None
mock_client_class.return_value = mock_client mock_client_class.return_value = mock_client
# Mock 401 response
login_response = MagicMock() login_response = MagicMock()
login_response.status_code = 401 login_response.status_code = 401
mock_client.post = AsyncMock(return_value=login_response) mock_client.post = AsyncMock(return_value=login_response)
# Attempt login and expect error
with pytest.raises(ValueError, match="Invalid email or password"): with pytest.raises(ValueError, match="Invalid email or password"):
await manager.login("wrong@example.com", "wrong_pass") await manager.login("wrong@example.com", "wrong_pass")
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_logout(self, mock_profile_response): async def test_login_rate_limited(self):
"""Test logout functionality.""" """Test login when rate limited."""
manager = SessionManager()
with patch("httpx.AsyncClient") as mock_client_class:
mock_client = AsyncMock()
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
mock_client_class.return_value = mock_client
login_response = MagicMock()
login_response.status_code = 429
mock_client.post = AsyncMock(return_value=login_response)
with pytest.raises(ValueError, match="Too many login attempts"):
await manager.login("test@example.com", "password")
@pytest.mark.asyncio
async def test_logout(self, mock_profile_response):
"""Test logout functionality."""
manager = SessionManager() manager = SessionManager()
with patch("httpx.AsyncClient") as mock_client_class: with patch("httpx.AsyncClient") as mock_client_class:
# Set up successful login first
mock_client = AsyncMock() mock_client = AsyncMock()
mock_client.__aenter__.return_value = mock_client mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None mock_client.__aexit__.return_value = None
@ -149,7 +181,7 @@ class TestSessionManager:
session_token, _ = await manager.login("test@example.com", "password") session_token, _ = await manager.login("test@example.com", "password")
# Now logout # Logout
result = await manager.logout(session_token) result = await manager.logout(session_token)
assert result is True assert result is True
@ -158,12 +190,38 @@ class TestSessionManager:
assert session is None assert session is None
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_session_with_default_cookie(self): async def test_logout_invalid_token(self):
"""Test getting session with default cookie from environment.""" """Test logout with invalid token."""
manager = SessionManager()
result = await manager.logout("invalid_token")
assert result is False
@pytest.mark.asyncio
async def test_get_session_with_default_cookie(self):
"""Test getting session with default cookie."""
manager = SessionManager(default_cookie="default_test_cookie")
session = await manager.get_session()
assert session is not None
assert session.ncfa_cookie == "default_test_cookie"
assert session.user_id == "default"
@pytest.mark.asyncio
async def test_get_session_no_auth(self):
"""Test getting session with no authentication."""
manager = SessionManager(default_cookie=None)
session = await manager.get_session()
assert session is None
@pytest.mark.asyncio
async def test_set_default_cookie(self):
"""Test setting default cookie."""
manager = SessionManager() manager = SessionManager()
# Should use default cookie from environment await manager.set_default_cookie("new_cookie")
session = await manager.get_session() session = await manager.get_session()
assert session is not None assert session is not None
assert session.ncfa_cookie == "test_cookie_value" assert session.ncfa_cookie == "new_cookie"

View file

View file

@ -0,0 +1,45 @@
"""
Tests for the Achievement model.
This module provides integration tests to verify that the Achievement
model behaves correctly when constructed using API response data. It
includes scenarios for both locked and unlocked achievements with relevant
data transformations.
"""
from geoguessr_mcp.models import Achievement
class TestAchievement:
"""Tests for Achievement model."""
def test_from_api_response_unlocked(self):
"""Test creating unlocked achievement."""
data = {
"id": "ach-1",
"name": "First Steps",
"description": "Complete your first game",
"unlocked": True,
"unlockedAt": "2024-01-15T00:00:00.000Z",
}
achievement = Achievement.from_api_response(data)
assert achievement.id == "ach-1"
assert achievement.name == "First Steps"
assert achievement.unlocked is True
assert achievement.unlocked_at == "2024-01-15T00:00:00.000Z"
def test_from_api_response_locked(self):
"""Test creating locked achievement with progress."""
data = {
"id": "ach-2",
"name": "Explorer",
"description": "Play 100 games",
"unlocked": False,
"progress": 0.45,
}
achievement = Achievement.from_api_response(data)
assert achievement.id == "ach-2"
assert achievement.unlocked is False
assert achievement.progress == 0.45

View file

@ -0,0 +1,62 @@
"""
Unit tests for validating the functionality of the Game model and related components.
These tests ensure the proper creation and behavior of Game and RoundGuess
instances when interacting with API responses or performing operations like
serialization. The tests cover both standard and edge cases.
"""
from geoguessr_mcp.models.Game import Game
from geoguessr_mcp.models.RoundGuess import RoundGuess
class TestGame:
"""Tests for Game model."""
def test_from_api_response(self, mock_game_response):
"""Test creating game from API response."""
game = Game.from_api_response(mock_game_response)
assert game.token == "ABC123XYZ"
assert game.map_name == "World"
assert game.mode == "standard"
assert game.finished is True
assert len(game.rounds) == 5
assert game.total_score == 5000 + 4500 + 3800 + 4900 + 5000
def test_from_api_response_minimal(self):
"""Test creating game from minimal response."""
data = {
"token": "TEST",
"type": "challenge",
"player": {"guesses": []},
}
game = Game.from_api_response(data)
assert game.token == "TEST"
assert game.mode == "challenge"
assert game.total_score == 0
assert len(game.rounds) == 0
def test_round_guess(self):
"""Test creating round guess."""
data = {
"roundScoreInPoints": 4500,
"distanceInMeters": 150.5,
"time": 25,
}
guess = RoundGuess.from_api_response(data, round_num=1)
assert guess.round_number == 1
assert guess.score == 4500
assert guess.distance_meters == 150.5
assert guess.time_seconds == 25
def test_to_dict(self, mock_game_response):
"""Test serializing game to dict."""
game = Game.from_api_response(mock_game_response)
result = game.to_dict()
assert result["token"] == "ABC123XYZ"
assert len(result["rounds"]) == 5
assert result["total_score"] > 0

View file

@ -0,0 +1,46 @@
"""
Tests for the SeasonStats model.
This module includes test cases to verify the functionality of the SeasonStats
model, especially its method for creating an instance from an API response.
The tests validate proper handling of fields and alternative field names in
the response.
Classes:
TestSeasonStats: Contains test cases for the SeasonStats model.
"""
from geoguessr_mcp.models import SeasonStats
class TestSeasonStats:
"""Tests for SeasonStats model."""
def test_from_api_response(self, mock_season_stats_response):
"""Test creating season stats from API response."""
stats = SeasonStats.from_api_response(mock_season_stats_response)
assert stats.season_id == "season-2024-1"
assert stats.season_name == "Season 1 2024"
assert stats.rank == 150
assert stats.rating == 1850
assert stats.games_played == 45
assert stats.wins == 30
assert stats.division == "Gold"
def test_from_api_response_alternative_fields(self):
"""Test handling alternative field names."""
data = {
"id": "s1",
"name": "Season One",
"position": 100,
"elo": 1500,
"games": 20,
"tier": "Silver",
}
stats = SeasonStats.from_api_response(data)
assert stats.season_id == "s1"
assert stats.rank == 100
assert stats.rating == 1500
assert stats.division == "Silver"

View file

@ -0,0 +1,43 @@
"""Test suite for the UserProfile model.
This module contains a collection of test cases designed to validate
the behavior of the UserProfile model, including creating instances
from API responses and serializing them back into dictionaries.
"""
from geoguessr_mcp.models import UserProfile
class TestUserProfile:
"""Tests for UserProfile model."""
def test_from_api_response(self, mock_profile_response):
"""Test creating profile from API response."""
profile = UserProfile.from_api_response(mock_profile_response)
assert profile.id == "test-user-id-123"
assert profile.nick == "TestPlayer"
assert profile.email == "test@example.com"
assert profile.country == "US"
assert profile.level == 50
assert profile.is_verified is True
assert profile.is_pro is True
def test_from_api_response_minimal(self):
"""Test creating profile from minimal response."""
data = {"id": "123", "nick": "Player"}
profile = UserProfile.from_api_response(data)
assert profile.id == "123"
assert profile.nick == "Player"
assert profile.email == ""
assert profile.level == 0
def test_to_dict(self, mock_profile_response):
"""Test serializing profile to dict."""
profile = UserProfile.from_api_response(mock_profile_response)
result = profile.to_dict()
assert result["id"] == "test-user-id-123"
assert result["nick"] == "TestPlayer"
assert "raw_data" not in result

View file

@ -0,0 +1,51 @@
"""
A set of tests for the UserStats model.
This module is responsible for testing functionalities of the UserStats
model, including testing the creation of model instances from API
responses, handling alternative field names in the API data, and
serialization of model instances into dictionaries.
"""
from geoguessr_mcp.models import UserStats
class TestUserStats:
"""Tests for UserStats model."""
def test_from_api_response(self, mock_stats_response):
"""Test creating stats from API response."""
stats = UserStats.from_api_response(mock_stats_response)
assert stats.games_played == 100
assert stats.rounds_played == 500
assert stats.total_score == 2250000
assert stats.average_score == 22500
assert stats.perfect_games == 10
assert stats.win_rate == 0.65
assert stats.streak_best == 25
def test_from_api_response_alternative_fields(self):
"""Test handling alternative field names."""
data = {
"totalGames": 50,
"totalRounds": 250,
"score": 1000000,
"fiveKs": 5,
"countryStreakBest": 15,
}
stats = UserStats.from_api_response(data)
assert stats.games_played == 50
assert stats.rounds_played == 250
assert stats.total_score == 1000000
assert stats.perfect_games == 5
assert stats.streak_best == 15
def test_to_dict(self, mock_stats_response):
"""Test serializing stats to dict."""
stats = UserStats.from_api_response(mock_stats_response)
result = stats.to_dict()
assert result["games_played"] == 100
assert "raw_data" not in result

View file

View file

@ -0,0 +1,58 @@
"""
Module for testing the EndpointSchema class and its functionality.
This module contains unit tests for the EndpointSchema class, focusing on
the serialization and deserialization functionality. The tests validate
the `to_dict` and `from_dict` methods, ensuring correct conversion between
dictionary representation and the EndpointSchema object.
"""
from geoguessr_mcp.monitoring import EndpointSchema, SchemaField
class TestEndpointSchema:
"""Tests for EndpointSchema class."""
def test_to_dict(self):
"""Test serialization to dictionary."""
schema = EndpointSchema(
endpoint="/v3/profiles",
method="GET",
fields={
"id": SchemaField(name="id", field_type="string"),
},
response_code=200,
is_available=True,
)
result = schema.to_dict()
assert result["endpoint"] == "/v3/profiles"
assert result["method"] == "GET"
assert result["is_available"] is True
assert "id" in result["fields"]
def test_from_dict(self):
"""Test deserialization from dictionary."""
data = {
"endpoint": "/v3/profiles",
"method": "GET",
"fields": {
"id": {
"name": "id",
"field_type": "string",
"nullable": False,
}
},
"last_updated": "2024-01-15T12:00:00+00:00",
"schema_hash": "abc123",
"response_code": 200,
"is_available": True,
}
schema = EndpointSchema.from_dict(data)
assert schema.endpoint == "/v3/profiles"
assert schema.method == "GET"
assert "id" in schema.fields
assert schema.fields["id"].field_type == "string"

View file

@ -0,0 +1,131 @@
"""
Unit tests for the SchemaDetector class.
This module provides a suite of unit tests to validate the functionality of
the SchemaDetector class from the monitoring.schema package. The tests ensure
the correct detection and classification of data types, handling of nested
objects, computation of schema hashes, and parsing of specific data formats
such as datetime strings, URLs, and UUIDs.
"""
from geoguessr_mcp.monitoring.schema.SchemaDetector import SchemaDetector
from geoguessr_mcp.monitoring.schema.EndpointSchema import SchemaField
class TestSchemaDetector:
"""Tests for SchemaDetector class."""
def test_detect_type_string(self):
"""Test string type detection."""
detector = SchemaDetector()
assert detector.detect_type("hello") == "string"
def test_detect_type_integer(self):
"""Test integer type detection."""
detector = SchemaDetector()
assert detector.detect_type(42) == "integer"
def test_detect_type_float(self):
"""Test float type detection."""
detector = SchemaDetector()
assert detector.detect_type(3.14) == "number"
def test_detect_type_boolean(self):
"""Test boolean type detection."""
detector = SchemaDetector()
assert detector.detect_type(True) == "boolean"
assert detector.detect_type(False) == "boolean"
def test_detect_type_null(self):
"""Test null type detection."""
detector = SchemaDetector()
assert detector.detect_type(None) == "null"
def test_detect_type_array(self):
"""Test array type detection."""
detector = SchemaDetector()
assert detector.detect_type([1, 2, 3]) == "array"
def test_detect_type_object(self):
"""Test object type detection."""
detector = SchemaDetector()
assert detector.detect_type({"key": "value"}) == "object"
def test_detect_type_datetime(self):
"""Test datetime string detection."""
detector = SchemaDetector()
assert detector.detect_type("2024-01-15T12:00:00Z") == "datetime"
assert detector.detect_type("2024-01-15T12:00:00+00:00") == "datetime"
def test_detect_type_uuid(self):
"""Test UUID string detection."""
detector = SchemaDetector()
assert detector.detect_type("550e8400-e29b-41d4-a716-446655440000") == "uuid"
def test_detect_type_url(self):
"""Test URL string detection."""
detector = SchemaDetector()
assert detector.detect_type("https://example.com/path") == "url"
assert detector.detect_type("http://test.com") == "url"
def test_analyze_response_simple(self):
"""Test analyzing a simple response."""
detector = SchemaDetector()
data = {
"id": "123",
"name": "Test",
"count": 42,
"active": True,
}
fields = detector.analyze_response(data)
assert len(fields) == 4
assert fields["id"].field_type == "string"
assert fields["name"].field_type == "string"
assert fields["count"].field_type == "integer"
assert fields["active"].field_type == "boolean"
def test_analyze_response_nested(self):
"""Test analyzing a nested response."""
detector = SchemaDetector()
data = {
"user": {
"id": "123",
"profile": {
"name": "Test",
}
}
}
fields = detector.analyze_response(data)
assert "user" in fields
assert fields["user"].field_type == "object"
assert fields["user"].nested_schema is not None
def test_compute_schema_hash(self):
"""Test schema hash computation."""
detector = SchemaDetector()
fields1 = {
"id": SchemaField(name="id", field_type="string"),
"name": SchemaField(name="name", field_type="string"),
}
fields2 = {
"id": SchemaField(name="id", field_type="string"),
"name": SchemaField(name="name", field_type="string"),
}
fields3 = {
"id": SchemaField(name="id", field_type="integer"), # Different type
"name": SchemaField(name="name", field_type="string"),
}
hash1 = detector.compute_schema_hash(fields1)
hash2 = detector.compute_schema_hash(fields2)
hash3 = detector.compute_schema_hash(fields3)
assert hash1 == hash2 # Same schema
assert hash1 != hash3 # Different schema

View file

@ -0,0 +1,93 @@
"""
Unit tests for the SchemaRegistry class.
This module contains test cases to verify the behavior of the
SchemaRegistry class, including the ability to update schemas,
store schema metadata, track schema availability, and retrieve
dynamic descriptions for endpoints.
Classes:
TestSchemaRegistry: Contains test methods for testing schema
registry functionality.
"""
from geoguessr_mcp.monitoring import SchemaRegistry
class TestSchemaRegistry:
"""Tests for SchemaRegistry class."""
def test_update_schema_new(self, tmp_path):
"""Test adding a new schema."""
registry = SchemaRegistry(cache_dir=str(tmp_path))
data = {"id": "123", "name": "Test"}
schema, changed = registry.update_schema("/v3/test", data)
assert changed is True
assert schema.endpoint == "/v3/test"
assert schema.is_available is True
def test_update_schema_unchanged(self, tmp_path):
"""Test updating with same schema."""
registry = SchemaRegistry(cache_dir=str(tmp_path))
data = {"id": "123", "name": "Test"}
# First update
schema1, changed1 = registry.update_schema("/v3/test", data)
assert changed1 is True
# Second update with same data
schema2, changed2 = registry.update_schema("/v3/test", data)
assert changed2 is False
def test_update_schema_changed(self, tmp_path):
"""Test detecting schema changes."""
registry = SchemaRegistry(cache_dir=str(tmp_path))
data1 = {"id": "123", "name": "Test"}
data2 = {"id": "123", "name": "Test", "new_field": 42} # Added field
schema1, changed1 = registry.update_schema("/v3/test", data1)
schema2, changed2 = registry.update_schema("/v3/test", data2)
assert changed1 is True
assert changed2 is True
assert len(schema2.fields) > len(schema1.fields)
def test_mark_unavailable(self, tmp_path):
"""Test marking endpoint as unavailable."""
registry = SchemaRegistry(cache_dir=str(tmp_path))
registry.mark_unavailable("/v3/test", "Server error", 500)
schema = registry.get_schema("/v3/test")
assert schema is not None
assert schema.is_available is False
assert schema.error_message == "Server error"
assert schema.response_code == 500
def test_get_available_endpoints(self, tmp_path):
"""Test getting list of available endpoints."""
registry = SchemaRegistry(cache_dir=str(tmp_path))
registry.update_schema("/v3/available", {"id": "1"})
registry.mark_unavailable("/v3/unavailable", "Error")
available = registry.get_available_endpoints()
assert "/v3/available" in available
assert "/v3/unavailable" not in available
def test_generate_dynamic_description(self, tmp_path):
"""Test generating endpoint description."""
registry = SchemaRegistry(cache_dir=str(tmp_path))
registry.update_schema("/v3/test", {"id": "123", "name": "Test"})
description = registry.generate_dynamic_description("/v3/test")
assert "/v3/test" in description
assert "id" in description
assert "name" in description

View file

View file

@ -0,0 +1 @@
# TODO

View file

@ -0,0 +1 @@
# TODO

View file

@ -0,0 +1 @@
# TODO

View file

@ -1,81 +0,0 @@
"""Unit tests for ProfileService."""
from unittest.mock import AsyncMock, MagicMock
import pytest
from geoguessr_mcp.models.profile import UserProfile
from geoguessr_mcp.services.profile_service import ProfileService
from geoguessr_mcp.config import settings
class TestProfileService:
"""Tests for ProfileService."""
@pytest.mark.asyncio
async def test_get_profile_success(self, mock_session, mock_profile_data):
"""Test successful profile retrieval."""
# Create mock client
mock_client = MagicMock()
mock_client.base_url = settings.GEOGUESSR_BASE_URL
mock_client.get_async_session = AsyncMock(return_value=mock_session)
# Mock HTTP response
mock_response = MagicMock()
mock_response.json.return_value = mock_profile_data
mock_response.raise_for_status = MagicMock()
mock_session.get = AsyncMock(return_value=mock_response)
# Test
service = ProfileService(mock_client)
profile = await service.get_profile()
assert isinstance(profile, UserProfile)
assert profile.nick == "TestPlayer"
assert profile.id == "test-user-id"
@pytest.mark.asyncio
async def test_get_my_stats_success(self, mock_session, mock_profile_data):
"""Test successful stats retrieval."""
# Create mock client
mock_client = MagicMock()
mock_client.base_url = settings.GEOGUESSR_BASE_URL
mock_client.get_async_session = AsyncMock(return_value=mock_session)
# Mock HTTP response
mock_response = MagicMock()
mock_response.json.return_value = mock_profile_data
mock_response.raise_for_status = MagicMock()
mock_session.get = AsyncMock(return_value=mock_response)
service = ProfileService(mock_client)
profile = await service.get_stats()
assert isinstance(profile, UserProfile)
assert profile. == 100
assert result["averageScore"] == 4500
@pytest.mark.asyncio
async def test_get_extended_stats(self, mock_session):
"""Test extended stats retrieval."""
from server import get_extended_stats
extended_stats = {
"totalGames": 150,
"winRate": 0.65,
"averageTime": 180
}
with patch("server.get_async_session") as mock_get_session:
mock_http_response = MagicMock()
mock_http_response.json.return_value = extended_stats
mock_http_response.raise_for_status = MagicMock()
mock_session.get = AsyncMock(return_value=mock_http_response)
mock_get_session.return_value = mock_session
result = await get_extended_stats()
assert result["totalGames"] == 150
assert result["winRate"] == 0.65