Add prefix matching tests
This commit is contained in:
parent
529320acb5
commit
f1e4cfa4dd
1 changed files with 221 additions and 0 deletions
221
tests/test_prefix_matching.py
Normal file
221
tests/test_prefix_matching.py
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
"""Tests for prefix matching in command dispatch."""
|
||||||
|
|
||||||
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from mudlib import commands
|
||||||
|
from mudlib.commands import CommandDefinition
|
||||||
|
|
||||||
|
|
||||||
|
@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 player(mock_reader, mock_writer):
|
||||||
|
from mudlib.player import Player
|
||||||
|
|
||||||
|
return Player(
|
||||||
|
name="TestPlayer",
|
||||||
|
x=5,
|
||||||
|
y=5,
|
||||||
|
reader=mock_reader,
|
||||||
|
writer=mock_writer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def dummy_handler(player, args):
|
||||||
|
"""A simple handler for testing."""
|
||||||
|
player.writer.write(f"Handler called with args: {args}\r\n")
|
||||||
|
await player.writer.drain()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def clean_registry():
|
||||||
|
"""Clear registry before each test."""
|
||||||
|
commands._registry.clear()
|
||||||
|
yield
|
||||||
|
commands._registry.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_exact_match_still_works(player):
|
||||||
|
"""Test that exact matches work as before."""
|
||||||
|
commands.register(
|
||||||
|
CommandDefinition(name="look", handler=dummy_handler, mode="normal")
|
||||||
|
)
|
||||||
|
|
||||||
|
await commands.dispatch(player, "look")
|
||||||
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
||||||
|
|
||||||
|
assert "Handler called" in output
|
||||||
|
assert "Unknown command" not in output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_prefix_match_resolves_unique_prefix(player):
|
||||||
|
"""Test that a unique prefix resolves to the full command."""
|
||||||
|
commands.register(
|
||||||
|
CommandDefinition(name="sweep", handler=dummy_handler, mode="normal")
|
||||||
|
)
|
||||||
|
|
||||||
|
await commands.dispatch(player, "swe")
|
||||||
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
||||||
|
|
||||||
|
assert "Handler called" in output
|
||||||
|
assert "Unknown command" not in output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_ambiguous_prefix_two_matches(player):
|
||||||
|
"""Test that ambiguous prefix with 2 matches shows 'A or B?' message."""
|
||||||
|
commands.register(
|
||||||
|
CommandDefinition(name="sweep", handler=dummy_handler, mode="normal")
|
||||||
|
)
|
||||||
|
commands.register(
|
||||||
|
CommandDefinition(
|
||||||
|
name="southwest", handler=dummy_handler, mode="normal"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await commands.dispatch(player, "sw")
|
||||||
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
||||||
|
|
||||||
|
# Check for either order
|
||||||
|
assert (
|
||||||
|
"sweep or southwest?" in output or "southwest or sweep?" in output
|
||||||
|
)
|
||||||
|
assert "Handler called" not in output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_ambiguous_prefix_three_plus_matches(player):
|
||||||
|
"""Test that ambiguous prefix with 3+ matches shows comma-separated."""
|
||||||
|
commands.register(
|
||||||
|
CommandDefinition(name="send", handler=dummy_handler, mode="normal")
|
||||||
|
)
|
||||||
|
commands.register(
|
||||||
|
CommandDefinition(name="set", handler=dummy_handler, mode="normal")
|
||||||
|
)
|
||||||
|
commands.register(
|
||||||
|
CommandDefinition(
|
||||||
|
name="settings", handler=dummy_handler, mode="normal"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await commands.dispatch(player, "se")
|
||||||
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
||||||
|
|
||||||
|
# Should contain all three with commas and "or"
|
||||||
|
assert "send" in output
|
||||||
|
assert "set" in output
|
||||||
|
assert "settings" in output
|
||||||
|
assert ", or " in output
|
||||||
|
assert "Handler called" not in output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_no_match_shows_unknown_command(player):
|
||||||
|
"""Test that no match shows unknown command message."""
|
||||||
|
commands.register(
|
||||||
|
CommandDefinition(name="look", handler=dummy_handler, mode="normal")
|
||||||
|
)
|
||||||
|
|
||||||
|
await commands.dispatch(player, "xyz")
|
||||||
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
||||||
|
|
||||||
|
assert "Unknown command: xyz" in output
|
||||||
|
assert "Handler called" not in output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_exact_alias_match_wins_over_prefix(player):
|
||||||
|
"""Test that exact match on alias takes priority over prefix match."""
|
||||||
|
commands.register(
|
||||||
|
CommandDefinition(
|
||||||
|
name="southwest",
|
||||||
|
aliases=["sw"],
|
||||||
|
handler=dummy_handler,
|
||||||
|
mode="normal",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
commands.register(
|
||||||
|
CommandDefinition(name="sweep", handler=dummy_handler, mode="normal")
|
||||||
|
)
|
||||||
|
|
||||||
|
await commands.dispatch(player, "sw")
|
||||||
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
||||||
|
|
||||||
|
# Should call handler (exact match on alias), not show ambiguous
|
||||||
|
assert "Handler called" in output
|
||||||
|
assert "or" not in output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_single_char_ambiguous_prefix(player):
|
||||||
|
"""Test that single-char ambiguous prefix shows disambiguation."""
|
||||||
|
commands.register(
|
||||||
|
CommandDefinition(name="quit", handler=dummy_handler, mode="normal")
|
||||||
|
)
|
||||||
|
commands.register(
|
||||||
|
CommandDefinition(name="query", handler=dummy_handler, mode="normal")
|
||||||
|
)
|
||||||
|
|
||||||
|
await commands.dispatch(player, "q")
|
||||||
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
||||||
|
|
||||||
|
assert "quit or query?" in output or "query or quit?" in output
|
||||||
|
assert "Handler called" not in output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_prefix_match_deduplicates_aliases(player):
|
||||||
|
"""Test that aliases to the same command don't create multiple matches."""
|
||||||
|
commands.register(
|
||||||
|
CommandDefinition(
|
||||||
|
name="look",
|
||||||
|
aliases=["l", "lo"],
|
||||||
|
handler=dummy_handler,
|
||||||
|
mode="normal",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
commands.register(
|
||||||
|
CommandDefinition(name="lock", handler=dummy_handler, mode="normal")
|
||||||
|
)
|
||||||
|
|
||||||
|
await commands.dispatch(player, "lo")
|
||||||
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
||||||
|
|
||||||
|
# Should show just "lock or look?" not multiple entries for look's aliases
|
||||||
|
assert "lock" in output
|
||||||
|
assert "look" in output
|
||||||
|
# Should not list look multiple times
|
||||||
|
output_lower = output.lower()
|
||||||
|
# Count occurrences of the word "look" (not as substring)
|
||||||
|
look_count = len(
|
||||||
|
[word for word in output_lower.split() if word.startswith("look")]
|
||||||
|
)
|
||||||
|
assert look_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_prefix_match_with_args(player):
|
||||||
|
"""Test that prefix matching preserves arguments."""
|
||||||
|
commands.register(
|
||||||
|
CommandDefinition(name="sweep", handler=dummy_handler, mode="normal")
|
||||||
|
)
|
||||||
|
|
||||||
|
await commands.dispatch(player, "swe the floor")
|
||||||
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
||||||
|
|
||||||
|
assert "Handler called with args: the floor" in output
|
||||||
Loading…
Reference in a new issue