"""Tests for sleep command.""" from unittest.mock import AsyncMock, MagicMock import pytest from mudlib.commands.sleep import cmd_sleep from mudlib.player import Player, players from mudlib.resting import STAMINA_PER_TICK, 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_sleep_when_full_sends_not_tired_message(player): """Test sleeping when at full stamina sends 'not tired' message.""" player.stamina = 100.0 player.max_stamina = 100.0 await cmd_sleep(player, "") player.writer.write.assert_called_once_with("You're not tired.\r\n") assert player.stamina == 100.0 assert not player.sleeping assert not player.resting @pytest.mark.asyncio async def test_sleep_when_not_sleeping_starts_sleeping(player): """Test sleep command when not sleeping starts the sleeping state.""" player.stamina = 50.0 player.max_stamina = 100.0 player.sleeping = False player.resting = False await cmd_sleep(player, "") assert player.sleeping assert player.resting # sleeping implies resting messages = [call[0][0] for call in player.writer.write.call_args_list] assert any("fall asleep" in msg or "go to sleep" in msg for msg in messages) @pytest.mark.asyncio async def test_sleep_when_already_sleeping_wakes_up(player): """Test sleep command when already sleeping wakes the player.""" player.stamina = 50.0 player.max_stamina = 100.0 player.sleeping = True player.resting = True await cmd_sleep(player, "") assert not player.sleeping assert not player.resting messages = [call[0][0] for call in player.writer.write.call_args_list] assert any("wake up" in msg or "wake" in msg for msg in messages) @pytest.mark.asyncio async def test_sleep_blocked_during_combat(player): """Test sleep is blocked when player is in combat mode.""" player.stamina = 50.0 player.mode_stack.append("combat") # Push combat mode onto the stack await cmd_sleep(player, "") assert not player.sleeping messages = [call[0][0] for call in player.writer.write.call_args_list] assert any( "can't sleep" in msg.lower() or "combat" in msg.lower() for msg in messages ) @pytest.mark.asyncio async def test_sleep_broadcasts_to_nearby_players(player, nearby_player): """Test sleeping broadcasts message to nearby players.""" player.stamina = 50.0 player.sleeping = False await cmd_sleep(player, "") nearby_messages = [call[0][0] for call in nearby_player.writer.write.call_args_list] assert any( "Goku" in msg and ("asleep" in msg or "sleep" in msg) for msg in nearby_messages ) @pytest.mark.asyncio async def test_wake_broadcasts_to_nearby_players(player, nearby_player): """Test waking up broadcasts message to nearby players.""" player.stamina = 50.0 player.sleeping = True player.resting = True await cmd_sleep(player, "") nearby_messages = [call[0][0] for call in nearby_player.writer.write.call_args_list] assert any("Goku" in msg and "wake" in msg for msg in nearby_messages) @pytest.mark.asyncio async def test_sleep_stamina_recovery_faster_than_rest(player): """Test sleeping provides faster stamina recovery than resting.""" player.stamina = 50.0 player.max_stamina = 100.0 player.sleeping = True player.resting = True await process_resting() # Sleep should give 3x the base rate (0.2 * 3 = 0.6) expected = 50.0 + (STAMINA_PER_TICK * 3) assert player.stamina == expected @pytest.mark.asyncio async def test_sleep_auto_stops_when_full(player): """Test sleep auto-stops when stamina reaches max.""" player.stamina = 99.5 player.max_stamina = 100.0 player.sleeping = True player.resting = True await process_resting() assert player.stamina == 100.0 assert not player.sleeping assert not player.resting messages = [call[0][0] for call in player.writer.write.call_args_list] assert any("fully rested" in msg or "wake" in msg for msg in messages) @pytest.mark.asyncio async def test_sleeping_player_does_not_see_nearby_messages(player, nearby_player): """Test sleeping players don't receive nearby messages.""" from mudlib.commands.movement import send_nearby_message player.sleeping = True # Nearby player does something that would normally broadcast await send_nearby_message( nearby_player, nearby_player.x, nearby_player.y, "Test message.\r\n" ) # Sleeping player should not have received it messages = [call[0][0] for call in player.writer.write.call_args_list] assert len(messages) == 0 @pytest.mark.asyncio async def test_awake_player_sees_nearby_messages(player, nearby_player): """Test awake players receive nearby messages normally.""" from mudlib.commands.movement import send_nearby_message player.sleeping = False await send_nearby_message( nearby_player, nearby_player.x, nearby_player.y, "Test message.\r\n" ) messages = [call[0][0] for call in player.writer.write.call_args_list] assert any("Test message" in msg for msg in messages)