"""
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 ( )