"""Tests for the server module.""" import asyncio import contextlib from unittest.mock import AsyncMock, MagicMock, patch import pytest from mudlib import server from mudlib.world.terrain import World def test_port_constant(): assert server.PORT == 6789 assert isinstance(server.PORT, int) def test_shell_exists(): assert callable(server.shell) assert asyncio.iscoroutinefunction(server.shell) def test_run_server_exists(): assert callable(server.run_server) assert asyncio.iscoroutinefunction(server.run_server) def test_find_passable_start(): world = World(seed=42, width=100, height=100) x, y = server.find_passable_start(world, 50, 50) assert isinstance(x, int) assert isinstance(y, int) assert world.is_passable(x, y) @pytest.mark.asyncio async def test_shell_greets_and_accepts_commands(): server._world = World(seed=42, width=100, height=100) reader = AsyncMock() writer = MagicMock() writer.is_closing.side_effect = [False, False, False, True] writer.drain = AsyncMock() writer.close = MagicMock() readline = "mudlib.server.readline2" with patch(readline, new_callable=AsyncMock) as mock_readline: mock_readline.side_effect = ["TestPlayer", "look", "quit"] await server.shell(reader, writer) calls = [str(call) for call in writer.write.call_args_list] assert any("Welcome" in call for call in calls) assert any("TestPlayer" in call for call in calls) writer.close.assert_called() @pytest.mark.asyncio async def test_shell_handles_eof(): server._world = World(seed=42, width=100, height=100) reader = AsyncMock() writer = MagicMock() writer.is_closing.return_value = False writer.drain = AsyncMock() writer.close = MagicMock() readline = "mudlib.server.readline2" with patch(readline, new_callable=AsyncMock) as mock_readline: mock_readline.return_value = None await server.shell(reader, writer) writer.close.assert_called_once() @pytest.mark.asyncio async def test_shell_handles_quit(): server._world = World(seed=42, width=100, height=100) reader = AsyncMock() writer = MagicMock() writer.is_closing.side_effect = [False, False, True] writer.drain = AsyncMock() writer.close = MagicMock() readline = "mudlib.server.readline2" with patch(readline, new_callable=AsyncMock) as mock_readline: mock_readline.side_effect = ["TestPlayer", "quit"] await server.shell(reader, writer) calls = [str(call) for call in writer.write.call_args_list] assert any("Goodbye" in call for call in calls) writer.close.assert_called() def test_load_world_config(): """Config loader returns expected values from worlds/earth/config.toml.""" config = server.load_world_config() assert config["world"]["seed"] == 42 assert config["world"]["width"] == 1000 assert config["world"]["height"] == 1000 def test_load_world_config_missing(): """Config loader raises FileNotFoundError for nonexistent world.""" with pytest.raises(FileNotFoundError): server.load_world_config("nonexistent") def test_tick_constants(): """Tick rate and interval are configured correctly.""" assert server.TICK_RATE == 10 assert pytest.approx(0.1) == server.TICK_INTERVAL def test_game_loop_exists(): """Game loop is an async callable.""" assert callable(server.game_loop) assert asyncio.iscoroutinefunction(server.game_loop) @pytest.mark.asyncio async def test_game_loop_calls_clear_expired(): """Game loop calls clear_expired each tick.""" with patch("mudlib.server.clear_expired") as mock_clear: task = asyncio.create_task(server.game_loop()) await asyncio.sleep(0.25) task.cancel() with contextlib.suppress(asyncio.CancelledError): await task assert mock_clear.call_count >= 1