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:
parent
f6226f51e4
commit
f9011dbeaa
24 changed files with 623 additions and 126 deletions
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
"""Integration tests for GeoGuessr MCP Server."""
|
||||
|
|
@ -0,0 +1 @@
|
|||
# TODO
|
||||
|
|
@ -0,0 +1 @@
|
|||
# TODO
|
||||
|
|
@ -0,0 +1 @@
|
|||
"""Unit tests for GeoGuessr MCP Server."""
|
||||
|
|
@ -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
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
The test cases validate the implementation of session validity, login,
|
||||
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
|
||||
from datetime import datetime, timedelta, UTC
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from geoguessr_mcp.auth.session import SessionManager, UserSession
|
||||
|
||||
# ============================================================================
|
||||
# USER SESSION TESTS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TestUserSession:
|
||||
"""Tests for UserSession dataclass."""
|
||||
|
|
@ -40,14 +54,24 @@ class TestUserSession:
|
|||
def test_session_without_cookie(self):
|
||||
"""Test that a session without cookie is invalid."""
|
||||
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()
|
||||
|
||||
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:
|
||||
"""Tests for SessionManager."""
|
||||
|
|
@ -55,11 +79,9 @@ class TestSessionManager:
|
|||
@pytest.mark.asyncio
|
||||
async def test_login_success(self, mock_profile_response):
|
||||
"""Test successful login flow."""
|
||||
|
||||
manager = SessionManager()
|
||||
|
||||
with patch("httpx.AsyncClient") as mock_client_class:
|
||||
# Create mock client
|
||||
mock_client = AsyncMock()
|
||||
mock_client.__aenter__.return_value = mock_client
|
||||
mock_client.__aexit__.return_value = None
|
||||
|
|
@ -70,7 +92,6 @@ class TestSessionManager:
|
|||
login_response.status_code = 200
|
||||
login_response.cookies.jar = []
|
||||
|
||||
# Create mock cookie
|
||||
mock_cookie = MagicMock()
|
||||
mock_cookie.name = "_ncfa"
|
||||
mock_cookie.value = "test_ncfa_cookie_value"
|
||||
|
|
@ -81,7 +102,6 @@ class TestSessionManager:
|
|||
profile_response.status_code = 200
|
||||
profile_response.json.return_value = mock_profile_response
|
||||
|
||||
# Set up mock client responses
|
||||
mock_client.post = AsyncMock(return_value=login_response)
|
||||
mock_client.get = AsyncMock(return_value=profile_response)
|
||||
mock_client.cookies.set = MagicMock()
|
||||
|
|
@ -89,13 +109,11 @@ class TestSessionManager:
|
|||
# Perform login
|
||||
session_token, session = await manager.login("test@example.com", "password123")
|
||||
|
||||
# Assertions
|
||||
assert session_token is not None
|
||||
assert len(session_token) > 0
|
||||
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.email == "test@example.com"
|
||||
assert session.is_valid()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -109,23 +127,37 @@ class TestSessionManager:
|
|||
mock_client.__aexit__.return_value = None
|
||||
mock_client_class.return_value = mock_client
|
||||
|
||||
# Mock 401 response
|
||||
login_response = MagicMock()
|
||||
login_response.status_code = 401
|
||||
mock_client.post = AsyncMock(return_value=login_response)
|
||||
|
||||
# Attempt login and expect error
|
||||
with pytest.raises(ValueError, match="Invalid email or password"):
|
||||
await manager.login("wrong@example.com", "wrong_pass")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_logout(self, mock_profile_response):
|
||||
"""Test logout functionality."""
|
||||
|
||||
async def test_login_rate_limited(self):
|
||||
"""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()
|
||||
|
||||
with patch("httpx.AsyncClient") as mock_client_class:
|
||||
# Set up successful login first
|
||||
mock_client = AsyncMock()
|
||||
mock_client.__aenter__.return_value = mock_client
|
||||
mock_client.__aexit__.return_value = None
|
||||
|
|
@ -149,7 +181,7 @@ class TestSessionManager:
|
|||
|
||||
session_token, _ = await manager.login("test@example.com", "password")
|
||||
|
||||
# Now logout
|
||||
# Logout
|
||||
result = await manager.logout(session_token)
|
||||
assert result is True
|
||||
|
||||
|
|
@ -158,12 +190,38 @@ class TestSessionManager:
|
|||
assert session is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_session_with_default_cookie(self):
|
||||
"""Test getting session with default cookie from environment."""
|
||||
async def test_logout_invalid_token(self):
|
||||
"""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()
|
||||
|
||||
# Should use default cookie from environment
|
||||
await manager.set_default_cookie("new_cookie")
|
||||
|
||||
session = await manager.get_session()
|
||||
assert session is not None
|
||||
assert session.ncfa_cookie == "test_cookie_value"
|
||||
assert session.ncfa_cookie == "new_cookie"
|
||||
0
src/tests/unit/models/__init__.py
Normal file
0
src/tests/unit/models/__init__.py
Normal file
45
src/tests/unit/models/test_achievement.py
Normal file
45
src/tests/unit/models/test_achievement.py
Normal 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
|
||||
62
src/tests/unit/models/test_game.py
Normal file
62
src/tests/unit/models/test_game.py
Normal 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
|
||||
46
src/tests/unit/models/test_season_stats.py
Normal file
46
src/tests/unit/models/test_season_stats.py
Normal 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"
|
||||
43
src/tests/unit/models/test_user_profile.py
Normal file
43
src/tests/unit/models/test_user_profile.py
Normal 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
|
||||
51
src/tests/unit/models/test_user_stats.py
Normal file
51
src/tests/unit/models/test_user_stats.py
Normal 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
|
||||
0
src/tests/unit/monitoring/__init__.py
Normal file
0
src/tests/unit/monitoring/__init__.py
Normal file
0
src/tests/unit/monitoring/endpoint/__init__.py
Normal file
0
src/tests/unit/monitoring/endpoint/__init__.py
Normal file
0
src/tests/unit/monitoring/schema/__init__.py
Normal file
0
src/tests/unit/monitoring/schema/__init__.py
Normal file
58
src/tests/unit/monitoring/schema/test_endpoint_schema.py
Normal file
58
src/tests/unit/monitoring/schema/test_endpoint_schema.py
Normal 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"
|
||||
131
src/tests/unit/monitoring/schema/test_schema_detector.py
Normal file
131
src/tests/unit/monitoring/schema/test_schema_detector.py
Normal 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
|
||||
93
src/tests/unit/monitoring/schema/test_schema_registry.py
Normal file
93
src/tests/unit/monitoring/schema/test_schema_registry.py
Normal 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
|
||||
0
src/tests/unit/services/__init__.py
Normal file
0
src/tests/unit/services/__init__.py
Normal file
1
src/tests/unit/services/test_analysis_service.py
Normal file
1
src/tests/unit/services/test_analysis_service.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# TODO
|
||||
1
src/tests/unit/services/test_game_service.py
Normal file
1
src/tests/unit/services/test_game_service.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# TODO
|
||||
1
src/tests/unit/services/test_profile_service.py
Normal file
1
src/tests/unit/services/test_profile_service.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# TODO
|
||||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue