mud/tests/test_get_drop.py
Jared Miller 7c12bf3318
Add Object.move_to(), get and drop commands
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.
2026-02-11 19:57:38 -05:00

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