""" Integration tests for Combat API endpoints. Tests the REST API endpoints for combat functionality. """ import pytest from unittest.mock import Mock, patch, MagicMock from flask import Flask import json from app import create_app from app.api.combat import combat_bp from app.models.combat import CombatEncounter, Combatant, CombatStatus from app.models.stats import Stats from app.models.enemy import EnemyTemplate, EnemyDifficulty from app.services.combat_service import CombatService, ActionResult, CombatRewards # ============================================================================= # Test Fixtures # ============================================================================= @pytest.fixture def app(): """Create test Flask application.""" app = create_app('development') app.config['TESTING'] = True return app @pytest.fixture def client(app): """Create test client.""" return app.test_client() @pytest.fixture def sample_stats(): """Sample stats for testing.""" return Stats( strength=12, dexterity=14, constitution=10, intelligence=10, wisdom=10, charisma=10, luck=10 ) @pytest.fixture def sample_combatant(sample_stats): """Sample player combatant.""" return Combatant( combatant_id="test_char_001", name="Test Hero", is_player=True, current_hp=50, max_hp=50, current_mp=30, max_mp=30, stats=sample_stats, abilities=["basic_attack", "power_strike"], ) @pytest.fixture def sample_enemy_combatant(sample_stats): """Sample enemy combatant.""" return Combatant( combatant_id="test_goblin_0", name="Test Goblin", is_player=False, current_hp=25, max_hp=25, current_mp=10, max_mp=10, stats=sample_stats, abilities=["basic_attack"], ) @pytest.fixture def sample_encounter(sample_combatant, sample_enemy_combatant): """Sample combat encounter.""" encounter = CombatEncounter( encounter_id="test_encounter_001", combatants=[sample_combatant, sample_enemy_combatant], turn_order=[sample_combatant.combatant_id, sample_enemy_combatant.combatant_id], round_number=1, current_turn_index=0, status=CombatStatus.ACTIVE, ) return encounter # ============================================================================= # List Enemies Endpoint Tests # ============================================================================= class TestListEnemiesEndpoint: """Tests for GET /api/v1/combat/enemies endpoint.""" def test_list_enemies_success(self, client): """Test listing all enemy templates.""" response = client.get('/api/v1/combat/enemies') assert response.status_code == 200 data = response.get_json() assert data['status'] == 200 assert 'result' in data assert 'enemies' in data['result'] enemies = data['result']['enemies'] assert isinstance(enemies, list) assert len(enemies) >= 6 # We have 6 sample enemies # Verify enemy structure enemy_ids = [e['enemy_id'] for e in enemies] assert 'goblin' in enemy_ids def test_list_enemies_filter_by_difficulty(self, client): """Test filtering enemies by difficulty.""" response = client.get('/api/v1/combat/enemies?difficulty=easy') assert response.status_code == 200 data = response.get_json() enemies = data['result']['enemies'] for enemy in enemies: assert enemy['difficulty'] == 'easy' def test_list_enemies_filter_by_tag(self, client): """Test filtering enemies by tag.""" response = client.get('/api/v1/combat/enemies?tag=humanoid') assert response.status_code == 200 data = response.get_json() enemies = data['result']['enemies'] for enemy in enemies: assert 'humanoid' in [t.lower() for t in enemy['tags']] # ============================================================================= # Get Enemy Details Endpoint Tests # ============================================================================= class TestGetEnemyEndpoint: """Tests for GET /api/v1/combat/enemies/ endpoint.""" def test_get_enemy_success(self, client): """Test getting enemy details.""" response = client.get('/api/v1/combat/enemies/goblin') assert response.status_code == 200 data = response.get_json() assert data['status'] == 200 # Enemy data is returned directly in result (not nested under 'enemy' key) assert data['result']['enemy_id'] == 'goblin' assert 'base_stats' in data['result'] assert 'loot_table' in data['result'] def test_get_enemy_not_found(self, client): """Test getting non-existent enemy.""" response = client.get('/api/v1/combat/enemies/nonexistent_12345') assert response.status_code == 404 data = response.get_json() assert data['status'] == 404 # ============================================================================= # Start Combat Endpoint Tests # ============================================================================= class TestStartCombatEndpoint: """Tests for POST /api/v1/combat/start endpoint.""" def test_start_combat_requires_auth(self, client): """Test that start combat endpoint requires authentication.""" response = client.post( '/api/v1/combat/start', json={ 'session_id': 'test_session_001', 'enemy_ids': ['goblin', 'goblin'] } ) # Should return 401 Unauthorized without valid session assert response.status_code == 401 def test_start_combat_missing_session_id(self, client): """Test starting combat without session_id.""" response = client.post( '/api/v1/combat/start', json={'enemy_ids': ['goblin']}, ) assert response.status_code in [400, 401] def test_start_combat_missing_enemies(self, client): """Test starting combat without enemies.""" response = client.post( '/api/v1/combat/start', json={'session_id': 'test_session'}, ) assert response.status_code in [400, 401] # ============================================================================= # Execute Action Endpoint Tests # ============================================================================= class TestExecuteActionEndpoint: """Tests for POST /api/v1/combat//action endpoint.""" def test_action_requires_auth(self, client): """Test that action endpoint requires authentication.""" response = client.post( '/api/v1/combat/test_session/action', json={ 'action_type': 'attack', 'target_ids': ['enemy_001'] } ) # Should return 401 Unauthorized without valid session assert response.status_code == 401 def test_action_missing_type(self, client): """Test action with missing action_type still requires auth.""" # Without auth, returns 401 regardless of payload issues response = client.post( '/api/v1/combat/test_session/action', json={'target_ids': ['enemy_001']} ) assert response.status_code == 401 # ============================================================================= # Enemy Turn Endpoint Tests # ============================================================================= class TestEnemyTurnEndpoint: """Tests for POST /api/v1/combat//enemy-turn endpoint.""" def test_enemy_turn_requires_auth(self, client): """Test that enemy turn endpoint requires authentication.""" response = client.post('/api/v1/combat/test_session/enemy-turn') assert response.status_code == 401 # ============================================================================= # Flee Endpoint Tests # ============================================================================= class TestFleeEndpoint: """Tests for POST /api/v1/combat//flee endpoint.""" def test_flee_requires_auth(self, client): """Test that flee endpoint requires authentication.""" response = client.post('/api/v1/combat/test_session/flee') assert response.status_code == 401 # ============================================================================= # Get Combat State Endpoint Tests # ============================================================================= class TestGetCombatStateEndpoint: """Tests for GET /api/v1/combat//state endpoint.""" def test_state_requires_auth(self, client): """Test that state endpoint requires authentication.""" response = client.get('/api/v1/combat/test_session/state') assert response.status_code == 401 # ============================================================================= # End Combat Endpoint Tests # ============================================================================= class TestEndCombatEndpoint: """Tests for POST /api/v1/combat//end endpoint.""" def test_end_requires_auth(self, client): """Test that end combat endpoint requires authentication.""" response = client.post('/api/v1/combat/test_session/end') assert response.status_code == 401 # ============================================================================= # Response Format Tests # ============================================================================= class TestCombatAPIResponseFormat: """Tests for API response format consistency.""" def test_enemies_response_format(self, client): """Test that enemies list has standard response format.""" response = client.get('/api/v1/combat/enemies') data = response.get_json() # Standard response fields assert 'app' in data assert 'version' in data assert 'status' in data assert 'timestamp' in data assert 'result' in data # Should not have error for successful request assert data['error'] is None or 'error' not in data or data['error'] == {} def test_enemy_details_response_format(self, client): """Test that enemy details has standard response format.""" response = client.get('/api/v1/combat/enemies/goblin') data = response.get_json() assert data['status'] == 200 assert 'result' in data # Enemy data is returned directly in result enemy = data['result'] # Required enemy fields assert 'enemy_id' in enemy assert 'name' in enemy assert 'description' in enemy assert 'base_stats' in enemy assert 'difficulty' in enemy def test_not_found_response_format(self, client): """Test 404 response format.""" response = client.get('/api/v1/combat/enemies/nonexistent_enemy_xyz') data = response.get_json() assert data['status'] == 404 assert 'error' in data assert data['error'] is not None # ============================================================================= # Content Type Tests # ============================================================================= class TestCombatAPIContentType: """Tests for content type handling.""" def test_json_content_type_response(self, client): """Test that API returns JSON content type.""" response = client.get('/api/v1/combat/enemies') assert response.content_type == 'application/json' def test_accepts_json_payload(self, client): """Test that API accepts JSON payloads.""" response = client.post( '/api/v1/combat/start', data=json.dumps({ 'session_id': 'test', 'enemy_ids': ['goblin'] }), content_type='application/json' ) # Should process JSON (even if auth fails) assert response.status_code in [200, 400, 401]