Object.move_to() handles containment transfer: removes from old location's contents, updates location pointer and coordinates, adds to new location. get/drop commands use move_to to transfer Things between zone and inventory. Supports name and alias matching for item lookup.
290 lines
8.3 KiB
Python
290 lines
8.3 KiB
Python
"""Tests for get and drop commands."""
|
|
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
import pytest
|
|
|
|
from mudlib.commands import CommandDefinition, _registry
|
|
from mudlib.entity import Entity
|
|
from mudlib.object import Object
|
|
from mudlib.player import Player
|
|
from mudlib.thing import Thing
|
|
from mudlib.zone import 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 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 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
|
|
|
|
|
|
# --- Object.move_to ---
|
|
|
|
|
|
def test_move_to_updates_location():
|
|
"""move_to changes the object's location pointer."""
|
|
terrain = [["." for _ in range(10)] for _ in range(10)]
|
|
zone = Zone(name="test", width=10, height=10, terrain=terrain)
|
|
entity = Entity(name="player", location=zone, x=5, y=5)
|
|
rock = Thing(name="rock", location=zone, x=5, y=5)
|
|
|
|
rock.move_to(entity)
|
|
assert rock.location is entity
|
|
|
|
|
|
def test_move_to_removes_from_old_contents():
|
|
"""move_to removes object from old location's contents."""
|
|
terrain = [["." for _ in range(10)] for _ in range(10)]
|
|
zone = Zone(name="test", width=10, height=10, terrain=terrain)
|
|
entity = Entity(name="player", location=zone, x=5, y=5)
|
|
rock = Thing(name="rock", location=zone, x=5, y=5)
|
|
|
|
rock.move_to(entity)
|
|
assert rock not in zone.contents
|
|
|
|
|
|
def test_move_to_adds_to_new_contents():
|
|
"""move_to adds object to new location's contents."""
|
|
terrain = [["." for _ in range(10)] for _ in range(10)]
|
|
zone = Zone(name="test", width=10, height=10, terrain=terrain)
|
|
entity = Entity(name="player", location=zone, x=5, y=5)
|
|
rock = Thing(name="rock", location=zone, x=5, y=5)
|
|
|
|
rock.move_to(entity)
|
|
assert rock in entity.contents
|
|
|
|
|
|
def test_move_to_from_none():
|
|
"""move_to works from location=None (template to world)."""
|
|
terrain = [["." for _ in range(10)] for _ in range(10)]
|
|
zone = Zone(name="test", width=10, height=10, terrain=terrain)
|
|
rock = Thing(name="rock")
|
|
|
|
rock.move_to(zone, x=3, y=7)
|
|
assert rock.location is zone
|
|
assert rock.x == 3
|
|
assert rock.y == 7
|
|
assert rock in zone.contents
|
|
|
|
|
|
def test_move_to_sets_coordinates():
|
|
"""move_to can set x/y coordinates."""
|
|
terrain = [["." for _ in range(10)] for _ in range(10)]
|
|
zone = Zone(name="test", width=10, height=10, terrain=terrain)
|
|
rock = Thing(name="rock", location=zone, x=1, y=1)
|
|
|
|
entity = Entity(name="player", location=zone, x=5, y=5)
|
|
rock.move_to(entity)
|
|
# When moving to inventory, coordinates should be cleared
|
|
assert rock.x is None
|
|
assert rock.y is None
|
|
|
|
|
|
def test_move_to_zone_sets_coordinates():
|
|
"""move_to a zone sets x/y from args."""
|
|
terrain = [["." for _ in range(10)] for _ in range(10)]
|
|
zone = Zone(name="test", width=10, height=10, terrain=terrain)
|
|
entity = Entity(name="player", location=zone, x=5, y=5)
|
|
rock = Thing(name="rock", location=entity)
|
|
|
|
rock.move_to(zone, x=3, y=7)
|
|
assert rock.x == 3
|
|
assert rock.y == 7
|
|
|
|
|
|
# --- cmd_get ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_picks_up_thing(player, test_zone):
|
|
"""get moves a thing from zone to player inventory."""
|
|
from mudlib.commands.things import cmd_get
|
|
|
|
rock = Thing(name="rock", location=test_zone, x=5, y=5)
|
|
await cmd_get(player, "rock")
|
|
assert rock.location is player
|
|
assert rock in player.contents
|
|
assert rock not in test_zone.contents
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_sends_confirmation(player, test_zone, mock_writer):
|
|
"""get sends feedback to the player."""
|
|
from mudlib.commands.things import cmd_get
|
|
|
|
Thing(name="rock", location=test_zone, x=5, y=5)
|
|
await cmd_get(player, "rock")
|
|
output = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "rock" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_nothing_there(player, test_zone, mock_writer):
|
|
"""get with no matching item gives feedback."""
|
|
from mudlib.commands.things import cmd_get
|
|
|
|
await cmd_get(player, "sword")
|
|
output = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "don't see" in output.lower() or "nothing" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_non_portable(player, test_zone, mock_writer):
|
|
"""get rejects non-portable things."""
|
|
from mudlib.commands.things import cmd_get
|
|
|
|
fountain = Thing(
|
|
name="fountain", location=test_zone, x=5, y=5, portable=False,
|
|
)
|
|
await cmd_get(player, "fountain")
|
|
# Fountain should still be in zone, not in player
|
|
assert fountain.location is test_zone
|
|
assert fountain not in player.contents
|
|
output = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "can't" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_no_args(player, mock_writer):
|
|
"""get with no arguments gives usage hint."""
|
|
from mudlib.commands.things import cmd_get
|
|
|
|
await cmd_get(player, "")
|
|
output = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "get what" in output.lower() or "what" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_matches_aliases(player, test_zone):
|
|
"""get matches thing aliases."""
|
|
from mudlib.commands.things import cmd_get
|
|
|
|
can = Thing(
|
|
name="pepsi can",
|
|
aliases=["can", "pepsi"],
|
|
location=test_zone, x=5, y=5,
|
|
)
|
|
await cmd_get(player, "pepsi")
|
|
assert can.location is player
|
|
|
|
|
|
# --- cmd_drop ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_drop_puts_thing_on_ground(player, test_zone):
|
|
"""drop moves a thing from inventory to zone at player's position."""
|
|
from mudlib.commands.things import cmd_drop
|
|
|
|
rock = Thing(name="rock", location=player)
|
|
await cmd_drop(player, "rock")
|
|
assert rock.location is test_zone
|
|
assert rock.x == player.x
|
|
assert rock.y == player.y
|
|
assert rock in test_zone.contents
|
|
assert rock not in player.contents
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_drop_sends_confirmation(player, test_zone, mock_writer):
|
|
"""drop sends feedback to the player."""
|
|
from mudlib.commands.things import cmd_drop
|
|
|
|
Thing(name="rock", location=player)
|
|
await cmd_drop(player, "rock")
|
|
output = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "rock" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_drop_not_carrying(player, mock_writer):
|
|
"""drop with item not in inventory gives feedback."""
|
|
from mudlib.commands.things import cmd_drop
|
|
|
|
await cmd_drop(player, "sword")
|
|
output = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "not carrying" in output.lower() or "don't have" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_drop_no_args(player, mock_writer):
|
|
"""drop with no arguments gives usage hint."""
|
|
from mudlib.commands.things import cmd_drop
|
|
|
|
await cmd_drop(player, "")
|
|
output = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "drop what" in output.lower() or "what" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_drop_requires_zone(player, mock_writer):
|
|
"""drop without a zone location gives error."""
|
|
from mudlib.commands.things import cmd_drop
|
|
|
|
player.location = None
|
|
Thing(name="rock", location=player)
|
|
await cmd_drop(player, "rock")
|
|
output = mock_writer.write.call_args_list[-1][0][0]
|
|
assert "nowhere" in output.lower() or "can't" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_drop_matches_aliases(player, test_zone):
|
|
"""drop matches thing aliases."""
|
|
from mudlib.commands.things import cmd_drop
|
|
|
|
can = Thing(
|
|
name="pepsi can",
|
|
aliases=["can", "pepsi"],
|
|
location=player,
|
|
)
|
|
await cmd_drop(player, "can")
|
|
assert can.location is test_zone
|
|
|
|
|
|
# --- command registration ---
|
|
|
|
|
|
def test_get_command_registered():
|
|
"""get command is registered."""
|
|
import mudlib.commands.things # noqa: F401
|
|
|
|
assert "get" in _registry
|
|
|
|
|
|
def test_drop_command_registered():
|
|
"""drop command is registered."""
|
|
import mudlib.commands.things # noqa: F401
|
|
|
|
assert "drop" in _registry
|