The server never proactively offered GMCP or MSDP to clients, so telnetlib3 logged "cannot send MSDP without negotiation" every second. Now the server sends WILL GMCP and WILL MSDP on connection, and send_msdp_vitals checks negotiation state before attempting to send.
238 lines
6.5 KiB
Python
238 lines
6.5 KiB
Python
"""Tests for paint mode - terrain editing admin tool."""
|
|
|
|
import pytest
|
|
|
|
from mudlib.player import Player
|
|
from mudlib.zone import Zone
|
|
|
|
|
|
@pytest.fixture
|
|
def player():
|
|
"""Create a test player with a mock writer."""
|
|
mock_writer = MockWriter()
|
|
player = Player(
|
|
name="TestPlayer",
|
|
x=5,
|
|
y=5,
|
|
writer=mock_writer,
|
|
)
|
|
# Create a simple zone for testing
|
|
zone = Zone(
|
|
"test_zone",
|
|
width=20,
|
|
height=20,
|
|
terrain=[["." for _ in range(20)] for _ in range(20)],
|
|
impassable={"^", "~", "#"}, # Add # as impassable
|
|
)
|
|
# Add a wall for passability testing
|
|
zone.terrain[5][6] = "#"
|
|
player.location = zone
|
|
return player
|
|
|
|
|
|
class MockWriter:
|
|
"""Mock writer that captures output."""
|
|
|
|
def __init__(self):
|
|
self.messages = []
|
|
# Mock option negotiation state
|
|
from unittest.mock import MagicMock
|
|
|
|
self.local_option = MagicMock()
|
|
self.local_option.enabled = MagicMock(return_value=False)
|
|
self.remote_option = MagicMock()
|
|
self.remote_option.enabled = MagicMock(return_value=False)
|
|
|
|
def write(self, message: str):
|
|
self.messages.append(message)
|
|
|
|
async def drain(self):
|
|
pass
|
|
|
|
def send_gmcp(self, package: str, data: dict):
|
|
"""Mock GMCP send (no-op for paint mode tests)."""
|
|
pass
|
|
|
|
def send_msdp(self, data: dict):
|
|
"""Mock MSDP send (no-op for paint mode tests)."""
|
|
pass
|
|
|
|
def get_output(self) -> str:
|
|
return "".join(self.messages)
|
|
|
|
def clear(self):
|
|
self.messages = []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_enter_paint_mode(player):
|
|
"""@paint command sets player.paint_mode = True and sends confirmation."""
|
|
from mudlib.commands.paint import cmd_paint
|
|
|
|
await cmd_paint(player, "")
|
|
assert player.paint_mode is True
|
|
output = player.writer.get_output()
|
|
assert "paint mode" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_exit_paint_mode(player):
|
|
"""@paint when already in paint mode exits it."""
|
|
from mudlib.commands.paint import cmd_paint
|
|
|
|
# Enter paint mode first
|
|
await cmd_paint(player, "")
|
|
player.writer.clear()
|
|
|
|
# Exit paint mode
|
|
await cmd_paint(player, "")
|
|
assert player.paint_mode is False
|
|
output = player.writer.get_output()
|
|
assert "exit" in output.lower() or "off" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_paint_mode_default_survey(player):
|
|
"""Entering paint mode starts in survey state (player.painting = False)."""
|
|
from mudlib.commands.paint import cmd_paint
|
|
|
|
await cmd_paint(player, "")
|
|
assert player.paint_mode is True
|
|
assert player.painting is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_toggle_painting(player):
|
|
"""p command toggles player.painting between True and False."""
|
|
from mudlib.commands.paint import cmd_toggle_painting
|
|
|
|
# Must be in paint mode first
|
|
player.paint_mode = True
|
|
|
|
# Toggle to painting
|
|
await cmd_toggle_painting(player, "")
|
|
assert player.painting is True
|
|
player.writer.clear()
|
|
|
|
# Toggle back to survey
|
|
await cmd_toggle_painting(player, "")
|
|
assert player.painting is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_toggle_painting_requires_paint_mode(player):
|
|
"""p command only works in paint mode."""
|
|
from mudlib.commands.paint import cmd_toggle_painting
|
|
|
|
player.paint_mode = False
|
|
await cmd_toggle_painting(player, "")
|
|
output = player.writer.get_output()
|
|
assert "paint mode" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_brush(player):
|
|
"""Typing a single character while in paint mode sets player.paint_brush."""
|
|
from mudlib.commands.paint import cmd_set_brush
|
|
|
|
player.paint_mode = True
|
|
|
|
await cmd_set_brush(player, "#")
|
|
assert player.paint_brush == "#"
|
|
|
|
await cmd_set_brush(player, "~")
|
|
assert player.paint_brush == "~"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_brush_requires_paint_mode(player):
|
|
"""Brush command only works in paint mode."""
|
|
from mudlib.commands.paint import cmd_set_brush
|
|
|
|
player.paint_mode = False
|
|
await cmd_set_brush(player, "#")
|
|
output = player.writer.get_output()
|
|
assert "paint mode" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_brush_requires_single_char(player):
|
|
"""Brush must be a single character."""
|
|
from mudlib.commands.paint import cmd_set_brush
|
|
|
|
player.paint_mode = True
|
|
await cmd_set_brush(player, "##")
|
|
output = player.writer.get_output()
|
|
assert "single character" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_paint_mode_movement_ignores_passability(player):
|
|
"""Movement in paint mode doesn't check passability."""
|
|
from mudlib.commands.movement import move_player
|
|
|
|
# There's a wall at (6, 5) - normally can't move there
|
|
player.paint_mode = True
|
|
|
|
# Should be able to move into the wall
|
|
await move_player(player, 1, 0, "east")
|
|
assert player.x == 6
|
|
assert player.y == 5
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_painting_places_tile(player):
|
|
"""Moving while painting sets terrain tile at old position to brush char."""
|
|
from mudlib.commands.movement import move_player
|
|
|
|
player.paint_mode = True
|
|
player.painting = True
|
|
player.paint_brush = "~"
|
|
|
|
# Move from (5,5) to (6,5)
|
|
old_x, old_y = player.x, player.y
|
|
await move_player(player, 1, 0, "east")
|
|
|
|
# Check that the old position now has the brush character
|
|
zone = player.location
|
|
assert isinstance(zone, Zone)
|
|
assert zone.terrain[old_y][old_x] == "~"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_painting_only_in_paint_mode(player):
|
|
"""Painting flag has no effect outside paint mode."""
|
|
from mudlib.commands.movement import move_player
|
|
|
|
player.paint_mode = False
|
|
player.painting = True
|
|
player.paint_brush = "~"
|
|
|
|
# Try to move into wall at (6, 5)
|
|
await move_player(player, 1, 0, "east")
|
|
|
|
# Should have failed - player still at (5, 5)
|
|
assert player.x == 5
|
|
assert player.y == 5
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_survey_mode_does_not_paint(player):
|
|
"""Survey mode (painting=False) allows movement but doesn't paint."""
|
|
from mudlib.commands.movement import move_player
|
|
|
|
player.paint_mode = True
|
|
player.painting = False
|
|
player.paint_brush = "~"
|
|
|
|
old_x, old_y = player.x, player.y
|
|
await move_player(player, 1, 0, "east")
|
|
|
|
# Movement should have happened
|
|
assert player.x == 6
|
|
assert player.y == 5
|
|
|
|
# But no painting should have occurred
|
|
zone = player.location
|
|
assert isinstance(zone, Zone)
|
|
assert zone.terrain[old_y][old_x] == "." # Still the original tile
|