Implements a TDD-built 'use' command that lets players invoke object verbs with optional targets: - use X - calls X's use verb - use X on Y - calls X's use verb with Y as args - Proper error messages for missing objects/verbs - Tests cover all edge cases including inventory/ground search Also fixes type checking issue in verb dispatch where get_verb could return None.
180 lines
4.8 KiB
Python
180 lines
4.8 KiB
Python
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
import pytest
|
|
|
|
from mudlib.commands.use import cmd_use
|
|
from mudlib.player import Player
|
|
from mudlib.thing import Thing
|
|
from mudlib.verbs import verb
|
|
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, terrain=terrain)
|
|
|
|
|
|
@pytest.fixture
|
|
def player(mock_reader, mock_writer, test_zone):
|
|
return Player(
|
|
name="TestPlayer",
|
|
x=5,
|
|
y=5,
|
|
reader=mock_reader,
|
|
writer=mock_writer,
|
|
location=test_zone,
|
|
)
|
|
|
|
|
|
def get_output(mock_writer):
|
|
"""Extract all text written to the mock writer."""
|
|
return "".join(call[0][0] for call in mock_writer.write.call_args_list)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_use_no_args(player, mock_writer):
|
|
"""use with no args should prompt for what to use"""
|
|
await cmd_use(player, "")
|
|
output = get_output(mock_writer)
|
|
assert "Use what?" in output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_use_object_with_verb(player, mock_writer, test_zone):
|
|
"""use rock should find rock with use verb and call it"""
|
|
|
|
class Rock(Thing):
|
|
@verb("use")
|
|
async def use_verb(self, player, args):
|
|
await player.send("You use the rock.\r\n")
|
|
|
|
_rock = Rock(
|
|
name="rock", description="a smooth rock", portable=True, location=test_zone
|
|
)
|
|
_rock.x = 5
|
|
_rock.y = 5
|
|
|
|
await cmd_use(player, "rock")
|
|
output = get_output(mock_writer)
|
|
assert "You use the rock." in output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_use_object_without_verb(player, mock_writer, test_zone):
|
|
"""use rock when rock has no use verb should give error"""
|
|
_rock = Thing(
|
|
name="rock", description="a smooth rock", portable=True, location=test_zone
|
|
)
|
|
_rock.x = 5
|
|
_rock.y = 5
|
|
|
|
await cmd_use(player, "rock")
|
|
output = get_output(mock_writer)
|
|
assert "You can't use that." in output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_use_object_not_found(player, mock_writer):
|
|
"""use flurb when nothing matches should give error"""
|
|
await cmd_use(player, "flurb")
|
|
output = get_output(mock_writer)
|
|
assert "You don't see that here." in output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_use_object_on_target(player, mock_writer, test_zone):
|
|
"""use key on chest should pass chest as args to verb"""
|
|
|
|
class Key(Thing):
|
|
@verb("use")
|
|
async def use_verb(self, player, args):
|
|
await player.send(f"You use the key on {args}.\r\n")
|
|
|
|
_key = Key(name="key", description="a brass key", portable=True, location=test_zone)
|
|
_key.x = 5
|
|
_key.y = 5
|
|
|
|
await cmd_use(player, "key on chest")
|
|
output = get_output(mock_writer)
|
|
assert "You use the key on chest." in output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_use_object_on_nonexistent_target(player, mock_writer, test_zone):
|
|
"""use key on flurb should still work, passing flurb to verb handler"""
|
|
|
|
class Key(Thing):
|
|
@verb("use")
|
|
async def use_verb(self, player, args):
|
|
await player.send(f"You use the key on {args}.\r\n")
|
|
|
|
_key = Key(name="key", description="a brass key", portable=True, location=test_zone)
|
|
_key.x = 5
|
|
_key.y = 5
|
|
|
|
await cmd_use(player, "key on flurb")
|
|
output = get_output(mock_writer)
|
|
assert "You use the key on flurb." in output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_use_object_in_inventory(player, mock_writer):
|
|
"""use should find objects in inventory"""
|
|
|
|
class Potion(Thing):
|
|
@verb("use")
|
|
async def use_verb(self, player, args):
|
|
await player.send("You drink the potion.\r\n")
|
|
|
|
_potion = Potion(
|
|
name="potion", description="a healing potion", portable=True, location=player
|
|
)
|
|
|
|
await cmd_use(player, "potion")
|
|
output = get_output(mock_writer)
|
|
assert "You drink the potion." in output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_use_passes_correct_args(player, mock_writer, test_zone):
|
|
"""verify verb handler receives correct args string"""
|
|
received_args = None
|
|
|
|
class Tool(Thing):
|
|
@verb("use")
|
|
async def use_verb(self, player, args):
|
|
nonlocal received_args
|
|
received_args = args
|
|
await player.send(f"Used with args: '{args}'\r\n")
|
|
|
|
_tool = Tool(
|
|
name="tool", description="a multi-tool", portable=True, location=test_zone
|
|
)
|
|
_tool.x = 5
|
|
_tool.y = 5
|
|
|
|
# Test with no target
|
|
await cmd_use(player, "tool")
|
|
assert received_args == ""
|
|
|
|
# Reset writer
|
|
mock_writer.write.reset_mock()
|
|
received_args = None
|
|
|
|
# Test with target
|
|
await cmd_use(player, "tool on something")
|
|
assert received_args == "something"
|