Implements portal-based zone transitions with the enter command. Players can enter portals at their position to move to target zones with specified coordinates. Includes departure/arrival messaging to nearby players and automatic look output in the destination zone. Portals are matched by partial name or exact alias match.
284 lines
7.1 KiB
Python
284 lines
7.1 KiB
Python
"""Tests for portal enter command."""
|
|
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
import pytest
|
|
|
|
from mudlib.commands import _registry
|
|
from mudlib.player import Player
|
|
from mudlib.portal import Portal
|
|
from mudlib.zone import Zone
|
|
from mudlib.zones import register_zone, zone_registry
|
|
|
|
|
|
@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 test_zone():
|
|
terrain = [["." for _ in range(10)] for _ in range(10)]
|
|
return Zone(
|
|
name="testzone",
|
|
width=10,
|
|
height=10,
|
|
toroidal=True,
|
|
terrain=terrain,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def target_zone():
|
|
terrain = [["." for _ in range(10)] for _ in range(10)]
|
|
return Zone(
|
|
name="targetzone",
|
|
width=10,
|
|
height=10,
|
|
toroidal=True,
|
|
terrain=terrain,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def player(mock_reader, mock_writer, test_zone):
|
|
p = Player(
|
|
name="TestPlayer",
|
|
x=5,
|
|
y=5,
|
|
reader=mock_reader,
|
|
writer=mock_writer,
|
|
location=test_zone,
|
|
)
|
|
return p
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clear_zones():
|
|
"""Clear zone registry before and after each test."""
|
|
zone_registry.clear()
|
|
yield
|
|
zone_registry.clear()
|
|
|
|
|
|
# --- cmd_enter ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_enter_moves_to_target_zone(player, test_zone, target_zone):
|
|
"""enter portal moves player to target zone."""
|
|
from mudlib.commands.portals import cmd_enter
|
|
|
|
register_zone("targetzone", target_zone)
|
|
Portal(
|
|
name="shimmering doorway",
|
|
aliases=["doorway"],
|
|
location=test_zone,
|
|
x=5,
|
|
y=5,
|
|
target_zone="targetzone",
|
|
target_x=3,
|
|
target_y=7,
|
|
)
|
|
|
|
await cmd_enter(player, "doorway")
|
|
assert player.location is target_zone
|
|
assert player.x == 3
|
|
assert player.y == 7
|
|
assert player in target_zone.contents
|
|
assert player not in test_zone.contents
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_enter_sends_departure_message(
|
|
player, test_zone, target_zone, mock_writer
|
|
):
|
|
"""enter portal sends departure message to old zone."""
|
|
from mudlib.commands.portals import cmd_enter
|
|
|
|
register_zone("targetzone", target_zone)
|
|
Portal(
|
|
name="portal",
|
|
location=test_zone,
|
|
x=5,
|
|
y=5,
|
|
target_zone="targetzone",
|
|
target_x=3,
|
|
target_y=7,
|
|
)
|
|
other_player = Player(
|
|
name="OtherPlayer",
|
|
x=5,
|
|
y=5,
|
|
reader=MagicMock(),
|
|
writer=MagicMock(write=MagicMock(), drain=AsyncMock()),
|
|
location=test_zone,
|
|
)
|
|
|
|
await cmd_enter(player, "portal")
|
|
|
|
# Check that other player received departure message
|
|
other_writer = other_player.writer
|
|
calls = [call[0][0] for call in other_writer.write.call_args_list]
|
|
assert any("TestPlayer" in call and "enter" in call.lower() for call in calls)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_enter_sends_arrival_message(player, test_zone, target_zone):
|
|
"""enter portal sends arrival message to new zone."""
|
|
from mudlib.commands.portals import cmd_enter
|
|
|
|
register_zone("targetzone", target_zone)
|
|
Portal(
|
|
name="portal",
|
|
location=test_zone,
|
|
x=5,
|
|
y=5,
|
|
target_zone="targetzone",
|
|
target_x=3,
|
|
target_y=7,
|
|
)
|
|
other_player = Player(
|
|
name="OtherPlayer",
|
|
x=3,
|
|
y=7,
|
|
reader=MagicMock(),
|
|
writer=MagicMock(write=MagicMock(), drain=AsyncMock()),
|
|
location=target_zone,
|
|
)
|
|
|
|
await cmd_enter(player, "portal")
|
|
|
|
# Check that other player in target zone received arrival message
|
|
other_writer = other_player.writer
|
|
calls = [call[0][0] for call in other_writer.write.call_args_list]
|
|
assert any("TestPlayer" in call and "arrive" in call.lower() for call in calls)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_enter_shows_look_in_new_zone(
|
|
player, test_zone, target_zone, mock_writer
|
|
):
|
|
"""enter portal triggers look command in new zone."""
|
|
from mudlib.commands.portals import cmd_enter
|
|
|
|
register_zone("targetzone", target_zone)
|
|
Portal(
|
|
name="portal",
|
|
location=test_zone,
|
|
x=5,
|
|
y=5,
|
|
target_zone="targetzone",
|
|
target_x=3,
|
|
target_y=7,
|
|
)
|
|
|
|
await cmd_enter(player, "portal")
|
|
|
|
# Check that look output was sent (viewport grid should be in output)
|
|
output = "".join([call[0][0] for call in mock_writer.write.call_args_list])
|
|
# Should have the @ symbol for player position
|
|
assert "@" in output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_enter_no_args(player, mock_writer):
|
|
"""enter with no arguments gives usage hint."""
|
|
from mudlib.commands.portals import cmd_enter
|
|
|
|
await cmd_enter(player, "")
|
|
output = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "enter what" in output.lower() or "usage" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_enter_portal_not_found(player, test_zone, mock_writer):
|
|
"""enter with portal not at position gives feedback."""
|
|
from mudlib.commands.portals import cmd_enter
|
|
|
|
await cmd_enter(player, "doorway")
|
|
output = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "don't see" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_enter_target_zone_not_found(player, test_zone, mock_writer):
|
|
"""enter with invalid target zone gives graceful error."""
|
|
from mudlib.commands.portals import cmd_enter
|
|
|
|
Portal(
|
|
name="portal",
|
|
location=test_zone,
|
|
x=5,
|
|
y=5,
|
|
target_zone="nonexistent",
|
|
target_x=3,
|
|
target_y=7,
|
|
)
|
|
|
|
await cmd_enter(player, "portal")
|
|
output = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "doesn't lead anywhere" in output.lower() or "nowhere" in output.lower()
|
|
# Player should still be in original zone
|
|
assert player.location is test_zone
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_enter_matches_aliases(player, test_zone, target_zone):
|
|
"""enter matches portal aliases."""
|
|
from mudlib.commands.portals import cmd_enter
|
|
|
|
register_zone("targetzone", target_zone)
|
|
Portal(
|
|
name="shimmering doorway",
|
|
aliases=["door", "doorway"],
|
|
location=test_zone,
|
|
x=5,
|
|
y=5,
|
|
target_zone="targetzone",
|
|
target_x=3,
|
|
target_y=7,
|
|
)
|
|
|
|
await cmd_enter(player, "door")
|
|
assert player.location is target_zone
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_enter_portal_elsewhere_in_zone(player, test_zone, mock_writer):
|
|
"""enter doesn't find portals not at player position."""
|
|
from mudlib.commands.portals import cmd_enter
|
|
|
|
Portal(
|
|
name="distant portal",
|
|
location=test_zone,
|
|
x=8,
|
|
y=8,
|
|
target_zone="targetzone",
|
|
target_x=3,
|
|
target_y=7,
|
|
)
|
|
|
|
await cmd_enter(player, "portal")
|
|
output = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "don't see" in output.lower()
|
|
# Player should still be in original zone
|
|
assert player.location is test_zone
|
|
|
|
|
|
# --- command registration ---
|
|
|
|
|
|
def test_enter_command_registered():
|
|
"""enter command is registered."""
|
|
import mudlib.commands.portals # noqa: F401
|
|
|
|
assert "enter" in _registry
|