1000x1000 tile world generated deterministically from a seed using layered Perlin noise. Terrain derived from elevation: mountains, forests, grasslands, sand, water, with rivers traced downhill from peaks. ANSI-colored viewport centered on player. Command system with registry/dispatch, 8-direction movement (n/s/e/w + diagonals), look/l, quit/q. Players see arrival/departure messages. Set connect_maxwait=0.5 on telnetlib3 to avoid the 4s CHARSET negotiation timeout — MUD clients reject CHARSET immediately via MTTS.
91 lines
2.6 KiB
Python
91 lines
2.6 KiB
Python
"""Tests for the server module."""
|
|
|
|
import asyncio
|
|
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()
|