Movement commands now access the zone through player.location instead of a module-level world variable. send_nearby_message uses zone.contents_near() to find nearby entities, eliminating the need for the global players dict and manual distance calculations. Tests updated to create zones and add entities via location assignment.
193 lines
5.3 KiB
Python
193 lines
5.3 KiB
Python
"""Tests for rest command."""
|
|
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
import pytest
|
|
|
|
from mudlib.commands.rest import cmd_rest
|
|
from mudlib.player import Player, players
|
|
from mudlib.resting import process_resting
|
|
from mudlib.zone import Zone
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clear_state():
|
|
"""Clear players before and after each test."""
|
|
players.clear()
|
|
yield
|
|
players.clear()
|
|
|
|
|
|
@pytest.fixture
|
|
def test_zone():
|
|
"""Create a test zone for players."""
|
|
terrain = [["." for _ in range(256)] for _ in range(256)]
|
|
zone = Zone(
|
|
name="testzone",
|
|
width=256,
|
|
height=256,
|
|
toroidal=True,
|
|
terrain=terrain,
|
|
impassable=set(),
|
|
)
|
|
return zone
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_writer():
|
|
writer = MagicMock()
|
|
writer.write = MagicMock()
|
|
writer.drain = AsyncMock()
|
|
return writer
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_reader():
|
|
return MagicMock()
|
|
|
|
|
|
@pytest.fixture
|
|
def player(mock_reader, mock_writer, test_zone):
|
|
p = Player(name="Goku", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
|
p.location = test_zone
|
|
test_zone._contents.append(p)
|
|
players[p.name] = p
|
|
return p
|
|
|
|
|
|
@pytest.fixture
|
|
def nearby_player(mock_reader, mock_writer, test_zone):
|
|
p = Player(name="Vegeta", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
|
p.location = test_zone
|
|
test_zone._contents.append(p)
|
|
players[p.name] = p
|
|
return p
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_rest_when_full_sends_not_tired_message(player):
|
|
"""Test resting when at full stamina sends 'not tired' message."""
|
|
player.stamina = 100.0
|
|
player.max_stamina = 100.0
|
|
|
|
await cmd_rest(player, "")
|
|
|
|
player.writer.write.assert_called_once_with("You're not tired.\r\n")
|
|
assert player.stamina == 100.0
|
|
assert not player.resting
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_rest_when_not_resting_starts_resting(player):
|
|
"""Test rest command when not resting starts the resting state."""
|
|
player.stamina = 50.0
|
|
player.max_stamina = 100.0
|
|
player.resting = False
|
|
|
|
await cmd_rest(player, "")
|
|
|
|
assert player.resting
|
|
messages = [call[0][0] for call in player.writer.write.call_args_list]
|
|
assert any("begin to rest" in msg for msg in messages)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_rest_when_already_resting_stops_resting(player):
|
|
"""Test rest command when already resting stops the resting state."""
|
|
player.stamina = 50.0
|
|
player.max_stamina = 100.0
|
|
player.resting = True
|
|
|
|
await cmd_rest(player, "")
|
|
|
|
assert not player.resting
|
|
messages = [call[0][0] for call in player.writer.write.call_args_list]
|
|
assert any("stop resting" in msg for msg in messages)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_rest_broadcasts_begin_to_nearby_players(player, nearby_player):
|
|
"""Test resting broadcasts begin message to nearby players."""
|
|
player.stamina = 50.0
|
|
player.resting = False
|
|
|
|
await cmd_rest(player, "")
|
|
|
|
nearby_messages = [call[0][0] for call in nearby_player.writer.write.call_args_list]
|
|
assert any("Goku begins to rest" in msg for msg in nearby_messages)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_rest_broadcasts_stop_to_nearby_players(player, nearby_player):
|
|
"""Test stopping rest broadcasts stop message to nearby players."""
|
|
player.stamina = 50.0
|
|
player.resting = True
|
|
|
|
await cmd_rest(player, "")
|
|
|
|
nearby_messages = [call[0][0] for call in nearby_player.writer.write.call_args_list]
|
|
assert any("Goku stops resting" in msg for msg in nearby_messages)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_resting_ticks_up_stamina(player):
|
|
"""Test process_resting increases stamina for resting players."""
|
|
player.stamina = 50.0
|
|
player.max_stamina = 100.0
|
|
player.resting = True
|
|
|
|
await process_resting()
|
|
|
|
assert player.stamina == 50.2 # 50 + 0.2 per tick
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_resting_auto_stops_when_full(player):
|
|
"""Test process_resting auto-stops resting when stamina reaches max."""
|
|
player.stamina = 99.9
|
|
player.max_stamina = 100.0
|
|
player.resting = True
|
|
|
|
await process_resting()
|
|
|
|
assert player.stamina == 100.0
|
|
assert not player.resting
|
|
messages = [call[0][0] for call in player.writer.write.call_args_list]
|
|
assert any("fully rested" in msg for msg in messages)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_resting_broadcasts_when_auto_stopping(player, nearby_player):
|
|
"""Test process_resting broadcasts when auto-stopping rest."""
|
|
player.stamina = 99.9
|
|
player.max_stamina = 100.0
|
|
player.resting = True
|
|
|
|
await process_resting()
|
|
|
|
nearby_messages = [call[0][0] for call in nearby_player.writer.write.call_args_list]
|
|
assert any("Goku stops resting" in msg for msg in nearby_messages)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_resting_ignores_non_resting_players(player):
|
|
"""Test process_resting doesn't modify stamina for non-resting players."""
|
|
player.stamina = 50.0
|
|
player.max_stamina = 100.0
|
|
player.resting = False
|
|
|
|
await process_resting()
|
|
|
|
assert player.stamina == 50.0 # unchanged
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stamina_doesnt_exceed_max(player):
|
|
"""Test stamina doesn't exceed max_stamina during resting."""
|
|
player.stamina = 99.95
|
|
player.max_stamina = 100.0
|
|
player.resting = True
|
|
|
|
await process_resting()
|
|
|
|
assert player.stamina == 100.0 # capped at max
|