Spaces:
Sleeping
Sleeping
| """ | |
| Tests for ChessMaster main agent. | |
| Tests cover: | |
| - Agent initialization | |
| - Response generation with player context | |
| - JSON response parsing | |
| - Action execution (send_message, save_memory, set_emotion, stop) | |
| - Memory retrieval and integration | |
| - Conversation history saving | |
| """ | |
| import pytest | |
| import json | |
| import tempfile | |
| from pathlib import Path | |
| from datetime import datetime | |
| from unittest.mock import AsyncMock, MagicMock, patch | |
| from agent.main_agent import ChessMaster | |
| from db import DatabaseManager, PlayerProfile, save_conversation_message | |
| def temp_db(): | |
| """Create a temporary SQLite database for testing.""" | |
| with tempfile.TemporaryDirectory() as tmpdir: | |
| db_path = Path(tmpdir) / "test_chess_club.db" | |
| db_url = f"sqlite:///{db_path}" | |
| manager = DatabaseManager(db_url) | |
| manager.initialize() | |
| yield manager | |
| manager.close() | |
| def test_player(temp_db): | |
| """Create a test player in the database.""" | |
| with temp_db.get_session() as session: | |
| player = PlayerProfile( | |
| player_id="test-player-1", | |
| player_name="TestPlayer", | |
| estimated_elo=1400, | |
| preferred_difficulty="intermediate", | |
| ) | |
| session.add(player) | |
| session.commit() | |
| # Set global database manager | |
| import db.database as db_module | |
| old_manager = db_module._db_manager | |
| db_module._db_manager = temp_db | |
| yield player | |
| db_module._db_manager = old_manager | |
| def mock_api_client(): | |
| """Create a mock API client.""" | |
| mock = AsyncMock() | |
| mock.respond = AsyncMock() | |
| return mock | |
| def chess_master(mock_api_client): | |
| """Create a ChessMaster instance with mocked API client.""" | |
| agent = ChessMaster(api_provider="gemini", memory_client=None) | |
| agent.api_client = mock_api_client | |
| return agent | |
| class TestChessMasterInitialization: | |
| """Test ChessMaster agent initialization.""" | |
| def test_init_default(self): | |
| """Test default initialization.""" | |
| agent = ChessMaster() | |
| assert agent.api_client is not None | |
| assert agent.memory_client is None | |
| assert agent.emotion_state == "neutral" | |
| assert agent.last_response_time is None | |
| def test_init_with_memory_client(self): | |
| """Test initialization with memory client.""" | |
| mock_memory = MagicMock() # Don't specify spec since WeaviateClient may not be available | |
| agent = ChessMaster(api_provider="gemini", memory_client=mock_memory) | |
| assert agent.memory_client == mock_memory | |
| def test_init_with_custom_model(self): | |
| """Test initialization with custom model.""" | |
| agent = ChessMaster(api_provider="gemini", model="gemini-1.5-flash") | |
| assert agent.api_client is not None | |
| class TestSystemPromptGeneration: | |
| """Test system prompt generation.""" | |
| def test_build_system_prompt_new_player(self, chess_master): | |
| """Test system prompt for new player.""" | |
| player = PlayerProfile( | |
| player_id="new-player", | |
| player_name="NewPlayer", | |
| total_games=0, | |
| estimated_elo=1400, | |
| relationship_state="new", | |
| ) | |
| prompt = chess_master._build_system_prompt(player) | |
| assert "Viktor Petrov" in prompt | |
| assert "NewPlayer" in prompt | |
| assert "new" in prompt | |
| assert "JSON" in prompt | |
| assert "send_message" in prompt | |
| def test_build_system_prompt_rival(self, chess_master): | |
| """Test system prompt for rival player.""" | |
| player = PlayerProfile( | |
| player_id="rival-player", | |
| player_name="Rival", | |
| total_games=10, | |
| wins_against_agent=5, | |
| estimated_elo=1600, | |
| relationship_state="rival", | |
| ) | |
| prompt = chess_master._build_system_prompt(player) | |
| assert "Rival" in prompt | |
| assert "rival" in prompt | |
| assert "1600" in prompt | |
| assert "50.0%" in prompt # win rate | |
| class TestResponseParsing: | |
| """Test JSON response parsing.""" | |
| def test_parse_valid_json_response(self, chess_master): | |
| """Test parsing valid JSON response.""" | |
| response = json.dumps({ | |
| "thinking": "The player is asking for advice.", | |
| "action": "send_message", | |
| "content": "Your position is weak.", | |
| "tone": "analytical", | |
| "metadata": {"emotion": "focused"}, | |
| }) | |
| parsed = chess_master._parse_agent_response(response) | |
| assert parsed["action"] == "send_message" | |
| assert parsed["content"] == "Your position is weak." | |
| assert parsed["tone"] == "analytical" | |
| assert parsed["metadata"]["emotion"] == "focused" | |
| def test_parse_json_with_markdown_code_block(self, chess_master): | |
| """Test parsing JSON from markdown code block.""" | |
| response = """Here's my response: | |
| ```json | |
| { | |
| "thinking": "Analyzing the position", | |
| "action": "send_message", | |
| "content": "That was a brilliant move.", | |
| "tone": "warm" | |
| } | |
| ``` | |
| """ | |
| parsed = chess_master._parse_agent_response(response) | |
| assert parsed["action"] == "send_message" | |
| assert parsed["content"] == "That was a brilliant move." | |
| assert parsed["tone"] == "warm" | |
| def test_parse_json_defaults(self, chess_master): | |
| """Test that parsing adds default values.""" | |
| response = '{"content": "Hello"}' | |
| parsed = chess_master._parse_agent_response(response) | |
| assert parsed["action"] == "send_message" | |
| assert parsed["tone"] == "neutral" | |
| assert "metadata" in parsed | |
| def test_parse_invalid_json(self, chess_master): | |
| """Test parsing invalid JSON returns safe defaults.""" | |
| response = "This is not JSON at all" | |
| parsed = chess_master._parse_agent_response(response) | |
| assert parsed["action"] == "stop" | |
| assert "Error" in parsed["content"] | |
| class TestUserPromptBuilding: | |
| """Test user prompt construction.""" | |
| def test_build_user_prompt_basic(self, chess_master, test_player): | |
| """Test basic user prompt building.""" | |
| prompt = chess_master._build_user_prompt( | |
| input_text="I want to play again", | |
| player=test_player, | |
| memory_context={"memories": [], "conversation_history": []}, | |
| ) | |
| assert "I want to play again" in prompt | |
| assert "TestPlayer" in prompt | |
| def test_build_user_prompt_with_history(self, chess_master, test_player): | |
| """Test user prompt with conversation history.""" | |
| memory_context = { | |
| "memories": [], | |
| "conversation_history": [ | |
| {"speaker": "chess_master", "content": "Welcome back.", "timestamp": None}, | |
| {"speaker": "player", "content": "Let's play", "timestamp": None}, | |
| ], | |
| } | |
| prompt = chess_master._build_user_prompt( | |
| input_text="I'm ready", | |
| player=test_player, | |
| memory_context=memory_context, | |
| ) | |
| assert "# Conversation History" in prompt | |
| assert "Welcome back" in prompt | |
| def test_build_user_prompt_with_context_data(self, chess_master, test_player): | |
| """Test user prompt with game context.""" | |
| context_data = { | |
| "game_state": "mid-game", | |
| "move": "e2-e4", | |
| "event": "game_started", | |
| } | |
| prompt = chess_master._build_user_prompt( | |
| input_text="Your move", | |
| player=test_player, | |
| memory_context={"memories": [], "conversation_history": []}, | |
| context_data=context_data, | |
| ) | |
| assert "Context" in prompt | |
| assert "e2-e4" in prompt | |
| class TestActionExecution: | |
| """Test action execution.""" | |
| async def test_execute_send_message_action(self, chess_master): | |
| """Test executing send_message action.""" | |
| parsed_response = { | |
| "action": "send_message", | |
| "content": "Your move.", | |
| "tone": "sharp", | |
| "metadata": {}, | |
| } | |
| result = await chess_master._execute_action( | |
| player_id="test-player-1", | |
| parsed_response=parsed_response, | |
| ) | |
| assert result["action"] == "send_message" | |
| assert result["content"] == "Your move." | |
| assert result["tone"] == "sharp" | |
| async def test_execute_set_emotion_action(self, chess_master): | |
| """Test executing set_emotion action.""" | |
| parsed_response = { | |
| "action": "set_emotion", | |
| "metadata": {"emotion": "excited"}, | |
| } | |
| result = await chess_master._execute_action( | |
| player_id="test-player-1", | |
| parsed_response=parsed_response, | |
| ) | |
| assert result["action"] == "emotion_changed" | |
| assert result["emotion"] == "excited" | |
| assert chess_master.emotion_state == "excited" | |
| async def test_execute_stop_action(self, chess_master): | |
| """Test executing stop action.""" | |
| parsed_response = { | |
| "action": "stop", | |
| "metadata": {"reason": "player_disconnected"}, | |
| } | |
| result = await chess_master._execute_action( | |
| player_id="test-player-1", | |
| parsed_response=parsed_response, | |
| ) | |
| assert result["action"] == "stop" | |
| assert result["reason"] == "player_disconnected" | |
| async def test_execute_unknown_action(self, chess_master): | |
| """Test executing unknown action defaults to send_message.""" | |
| parsed_response = { | |
| "action": "unknown_action", | |
| "content": "Fallback response", | |
| } | |
| result = await chess_master._execute_action( | |
| player_id="test-player-1", | |
| parsed_response=parsed_response, | |
| ) | |
| assert result["action"] == "send_message" | |
| assert result["content"] == "Fallback response" | |
| class TestMemoryRetrieval: | |
| """Test memory context retrieval.""" | |
| async def test_retrieve_context_without_history(self, chess_master, test_player): | |
| """Test retrieving context when there's no history.""" | |
| context = await chess_master._retrieve_context("test-player-1") | |
| assert "memories" in context | |
| assert "conversation_history" in context | |
| assert isinstance(context["memories"], list) | |
| assert isinstance(context["conversation_history"], list) | |
| async def test_retrieve_context_with_history(self, chess_master, test_player): | |
| """Test retrieving context with conversation history.""" | |
| # Save some messages | |
| save_conversation_message( | |
| player_id="test-player-1", | |
| speaker="chess_master", | |
| content="Hello, welcome back.", | |
| ) | |
| save_conversation_message( | |
| player_id="test-player-1", | |
| speaker="player", | |
| content="Thanks! I'm ready to play.", | |
| ) | |
| context = await chess_master._retrieve_context("test-player-1") | |
| assert len(context["conversation_history"]) == 2 | |
| assert context["conversation_history"][0]["content"] == "Hello, welcome back." | |
| class TestFullResponseGeneration: | |
| """Test full response generation flow.""" | |
| async def test_respond_success(self, chess_master, test_player, mock_api_client): | |
| """Test successful response generation.""" | |
| # Mock API response | |
| api_response = json.dumps({ | |
| "thinking": "Player is new, so I'll be respectful", | |
| "action": "send_message", | |
| "content": "Welcome to the board.", | |
| "tone": "warm", | |
| "metadata": {}, | |
| }) | |
| mock_api_client.respond.return_value = api_response | |
| result = await chess_master.respond( | |
| player_id="test-player-1", | |
| input_text="I want to play", | |
| ) | |
| assert result["action"] == "send_message" | |
| assert result["content"] == "Welcome to the board." | |
| assert result["tone"] == "warm" | |
| assert chess_master.last_response_time is not None | |
| async def test_respond_missing_player(self, chess_master): | |
| """Test response when player doesn't exist.""" | |
| result = await chess_master.respond( | |
| player_id="nonexistent-player", | |
| input_text="Hello", | |
| ) | |
| assert result["action"] == "stop" | |
| assert result["error"] is True | |
| async def test_respond_api_error(self, chess_master, test_player, mock_api_client): | |
| """Test response when API call fails.""" | |
| mock_api_client.respond.side_effect = Exception("API Error") | |
| result = await chess_master.respond( | |
| player_id="test-player-1", | |
| input_text="Hello", | |
| ) | |
| assert result["action"] == "stop" | |
| assert result["error"] is True | |
| class TestAgentStatus: | |
| """Test agent status reporting.""" | |
| def test_get_status(self, chess_master): | |
| """Test getting agent status.""" | |
| status = chess_master.get_status() | |
| assert "emotion" in status | |
| assert "last_response" in status | |
| assert "api_provider" in status | |
| assert status["emotion"] == "neutral" | |
| assert status["last_response"] is None | |
| def test_get_status_after_response(self, chess_master): | |
| """Test status after changing emotion.""" | |
| chess_master.emotion_state = "excited" | |
| chess_master.last_response_time = datetime.now() | |
| status = chess_master.get_status() | |
| assert status["emotion"] == "excited" | |
| assert status["last_response"] is not None | |