"""Tests for verb dispatch fallback in command system.""" 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 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")