"""Tests for verb dispatch fallback in command system.""" from unittest.mock import AsyncMock, MagicMock import pytest from mudlib.commands import dispatch 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, ) class Fountain(Thing): """Test object with drink verb.""" @verb("drink") async def drink(self, player, args): await player.send("You drink from the fountain.\r\n") @verb("splash") async def splash(self, player, args): await player.send(f"You splash the fountain{' ' + args if args else ''}.\r\n") @pytest.mark.asyncio async def test_verb_dispatch_simple(player, test_zone): """Test 'drink fountain' dispatches to fountain's drink verb.""" _fountain = Fountain( name="fountain", description="a stone fountain", portable=False, location=test_zone, x=5, y=5, ) await dispatch(player, "drink fountain") player.writer.write.assert_called_once_with("You drink from the fountain.\r\n") player.writer.drain.assert_awaited_once() @pytest.mark.asyncio async def test_verb_dispatch_with_extra_args(player, test_zone): """Test 'splash fountain hard' passes 'hard' as extra args.""" _fountain = Fountain( name="fountain", description="a stone fountain", portable=False, location=test_zone, x=5, y=5, ) await dispatch(player, "splash fountain hard") player.writer.write.assert_called_once_with("You splash the fountain hard.\r\n") player.writer.drain.assert_awaited_once() @pytest.mark.asyncio async def test_verb_dispatch_object_lacks_verb(player, test_zone): """Test 'drink rock' fails if rock doesn't have drink verb.""" _rock = Thing( name="rock", description="a plain rock", portable=True, location=test_zone, x=5, y=5, ) await dispatch(player, "drink rock") player.writer.write.assert_called_once_with("Unknown command: drink\r\n") player.writer.drain.assert_awaited_once() @pytest.mark.asyncio async def test_verb_dispatch_object_not_found(player, test_zone): """Test 'drink flurb' fails if flurb doesn't exist.""" await dispatch(player, "drink flurb") player.writer.write.assert_called_once_with("Unknown command: drink\r\n") player.writer.drain.assert_awaited_once() @pytest.mark.asyncio async def test_verb_dispatch_no_args(player, test_zone): """Test 'drink' with no args doesn't try verb dispatch.""" await dispatch(player, "drink") player.writer.write.assert_called_once_with("Unknown command: drink\r\n") player.writer.drain.assert_awaited_once() @pytest.mark.asyncio async def test_verb_dispatch_doesnt_intercept_real_commands(player, test_zone): """Test that verb dispatch doesn't interfere with registered commands.""" from mudlib.commands import CommandDefinition, register, unregister called = False async def test_command(player, args): nonlocal called called = True await player.send("Real command executed.\r\n") register(CommandDefinition("test", test_command)) try: await dispatch(player, "test something") assert called player.writer.write.assert_called_once_with("Real command executed.\r\n") finally: unregister("test") @pytest.mark.asyncio async def test_verb_dispatch_finds_inventory_object(player, test_zone): """Test verb dispatch finds object in inventory.""" class Potion(Thing): @verb("drink") async def drink(self, player, args): await player.send("You drink the potion.\r\n") _potion = Potion( name="potion", description="a health potion", portable=True, location=player, ) await dispatch(player, "drink potion") player.writer.write.assert_called_once_with("You drink the potion.\r\n") player.writer.drain.assert_awaited_once() @pytest.mark.asyncio async def test_verb_dispatch_doesnt_fire_on_ambiguous_command(player, test_zone): """Test verb dispatch doesn't fire when command is ambiguous.""" from mudlib.commands import CommandDefinition, register, unregister async def test_one(player, args): await player.send("test one\r\n") async def test_two(player, args): await player.send("test two\r\n") register(CommandDefinition("testone", test_one)) register(CommandDefinition("testtwo", test_two)) try: # "test" matches both testone and testtwo await dispatch(player, "test something") # Should get ambiguous command message, not verb dispatch written_text = player.writer.write.call_args[0][0].lower() # The ambiguous message is "testone or testtwo?" not "ambiguous" assert "or" in written_text and "?" in written_text finally: unregister("testone") unregister("testtwo")