""" Fixed economy system tests - validates building costs, income calculations, road connectivity bonuses, and offline processing using isolated fixtures """ import pytest import asyncio from tests.test_client import test_clients as client_manager from server.models import BuildingType, BUILDING_CONFIGS class TestEconomySystemFixed: """Test the economy system calculations and behaviors with proper isolation""" def test_building_costs(self, isolated_game_components): """Test that building configurations have correct costs""" game_state, economy_engine, database = isolated_game_components player = game_state.get_or_create_player("test_player", "test_id_123") # Test known building costs assert BUILDING_CONFIGS[BuildingType.SMALL_HOUSE].cost == 5000 assert BUILDING_CONFIGS[BuildingType.ROAD].cost == 500 assert BUILDING_CONFIGS[BuildingType.POWER_PLANT].cost == 100000 assert BUILDING_CONFIGS[BuildingType.MALL].cost == 80000 # Test that player starts with enough money assert player.money == 100000 assert player.can_afford(BUILDING_CONFIGS[BuildingType.SMALL_HOUSE].cost) # Player can afford power plant with starting money (exactly $100,000) assert player.can_afford(BUILDING_CONFIGS[BuildingType.POWER_PLANT].cost) def test_basic_building_placement_costs(self, isolated_game_components, unique_coordinates): """Test that placing buildings deducts correct money""" game_state, economy_engine, database = isolated_game_components player = game_state.get_or_create_player("test_player", "test_id_123") initial_money = player.money # Get unique coordinates to avoid conflicts x1, y1 = unique_coordinates(0) x2, y2 = unique_coordinates(1) # Place a small house (costs $5000) result = game_state.place_building("test_id_123", "small_house", x1, y1) assert result["success"] == True assert player.money == initial_money - 5000 # Place a road (costs $500) result = game_state.place_building("test_id_123", "road", x2, y2) assert result["success"] == True assert player.money == initial_money - 5000 - 500 def test_insufficient_funds(self, isolated_game_components, unique_coordinates): """Test that buildings can't be placed without enough money""" game_state, economy_engine, database = isolated_game_components player = game_state.get_or_create_player("test_player", "test_id_123") # Drain most money player.money = 1000 # Try to place expensive building x, y = unique_coordinates(0) result = game_state.place_building("test_id_123", "power_plant", x, y) assert result["success"] == False assert "Not enough money" in result["error"] assert player.money == 1000 # Money unchanged def test_population_requirements(self, isolated_game_components, unique_coordinates): """Test that commercial buildings require population""" game_state, economy_engine, database = isolated_game_components player = game_state.get_or_create_player("test_player", "test_id_123") x1, y1 = unique_coordinates(0) x2, y2 = unique_coordinates(1) # Try to place shop without population assert player.population == 0 result = game_state.place_building("test_id_123", "small_shop", x1, y1) assert result["success"] == False assert "population" in result["error"].lower() # Add population via two houses (need 20+ population for small shop) game_state.place_building("test_id_123", "small_house", x2, y2) x3, y3 = unique_coordinates(2) game_state.place_building("test_id_123", "small_house", x3, y3) economy_engine.tick() # Update population assert player.population == 20 # 2 houses × 10 population each # Now shop should work result = game_state.place_building("test_id_123", "small_shop", x1, y1) assert result["success"] == True def test_power_requirements(self, isolated_game_components, unique_coordinates): """Test that large buildings require power plant""" game_state, economy_engine, database = isolated_game_components player = game_state.get_or_create_player("test_player", "test_id_123") # Add enough money for power plant player.money = 200000 x1, y1 = unique_coordinates(0) x2, y2 = unique_coordinates(1) # Try to place large factory without power plant result = game_state.place_building("test_id_123", "large_factory", x1, y1) assert result["success"] == False assert "power plant" in result["error"].lower() # Place power plant first game_state.place_building("test_id_123", "power_plant", x2, y2) # Now large factory should work result = game_state.place_building("test_id_123", "large_factory", x1, y1) assert result["success"] == True def test_basic_economy_tick(self, isolated_game_components, unique_coordinates): """Test basic income/expense calculations""" game_state, economy_engine, database = isolated_game_components player = game_state.get_or_create_player("test_player", "test_id_123") x1, y1 = unique_coordinates(0) x2, y2 = unique_coordinates(1) x3, y3 = unique_coordinates(2) # Place houses for population first game_state.place_building("test_id_123", "small_house", x1, y1) game_state.place_building("test_id_123", "small_house", x2, y2) economy_engine.tick() # Update population money_after_houses = player.money # Place income-generating building (now we have 20 population) game_state.place_building("test_id_123", "small_shop", x3, y3) money_after_placement = player.money # Run economy tick economy_engine.tick() # Small shop generates +$100, two houses cost -$100 total = $0 net # But houses were already giving income before, so we expect same or slight change assert player.money >= money_after_placement - 100 # Allow for house costs def test_building_stats_calculation(self, isolated_game_components): """Test building stats calculation for UI""" game_state, economy_engine, database = isolated_game_components stats = economy_engine.calculate_building_stats("test_id_123", BuildingType.SMALL_SHOP) expected_config = BUILDING_CONFIGS[BuildingType.SMALL_SHOP] assert stats["cost"] == expected_config.cost assert stats["income"] == expected_config.income assert stats["population"] == expected_config.population assert stats["power_required"] == expected_config.power_required assert stats["requires_population"] == expected_config.requires_population class TestEconomyIntegrationFixed: """Integration tests for economy with WebSocket clients using fresh server state""" @pytest.mark.asyncio async def test_economy_through_websocket(self, unique_coordinates): """Test economy system through WebSocket client actions""" async with client_manager("economy_test") as [client]: initial_money = client.get_money() # Note: initial money varies based on previous tests and economy ticks assert initial_money > 5000 # Ensure player has enough for a small house # Get unique coordinates x, y = unique_coordinates(0) # Place a building that costs money await client.place_building("small_house", x, y) # Wait for server response message = await client.receive_message(timeout=2.0) assert message is not None if message["type"] == "building_placed": # Success case - building was placed assert message["building"]["type"] == "small_house" # Wait for economy update (server triggers immediate economy tick after building placement) stats_message = await client.receive_message(timeout=2.0) if stats_message and stats_message["type"] == "player_stats_update": # Money should be deducted by building cost plus potential maintenance costs new_money = stats_message["player"]["money"] # Verify money decreased by at least the building cost (may be more due to maintenance) money_decrease = initial_money - new_money assert money_decrease >= 5000 # At least the building cost # Should be building cost + some maintenance, but exact amount depends on existing buildings elif message["type"] == "error": # Expected if coordinates conflict - that's ok for this test print(f"Building placement error (expected): {message['message']}") else: pytest.fail(f"Unexpected message type: {message['type']}") @pytest.mark.asyncio async def test_building_requirements_through_websocket(self, unique_coordinates): """Test building requirements validation through WebSocket""" async with client_manager("requirements_test") as [client]: # Get unique coordinates x, y = unique_coordinates(0) # Try to place shop without population - should fail await client.place_building("small_shop", x, y) message = await client.receive_message(timeout=2.0) assert message is not None assert message["type"] == "error" # Check for either population error or coordinate conflict assert "population" in message["message"].lower() or "occupied" in message["message"].lower()