mud/tests/test_commands_list.py

237 lines
7.6 KiB
Python

"""Tests for the commands listing command."""
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock
import pytest
from mudlib import commands
# Import command modules to register their commands
from mudlib.commands import (
edit, # noqa: F401
fly, # noqa: F401
help, # noqa: F401
look, # noqa: F401
movement, # noqa: F401
quit, # noqa: F401
)
@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,
)
@pytest.fixture
def combat_moves():
"""Load and register combat moves from content directory."""
from mudlib.combat.commands import register_combat_commands
combat_dir = Path(__file__).resolve().parents[1] / "content" / "combat"
register_combat_commands(combat_dir)
from mudlib.combat import commands as combat_cmds
yield combat_cmds.combat_moves
# Clean up
combat_cmds.combat_moves = {}
# Note: commands stay registered in _registry, but that's ok for tests
@pytest.mark.asyncio
async def test_commands_command_exists():
"""Test that commands command is registered."""
assert "commands" in commands._registry
assert "cmds" in commands._registry
assert commands._registry["commands"] is commands._registry["cmds"]
@pytest.mark.asyncio
async def test_commands_has_wildcard_mode():
"""Test that commands works from any mode."""
assert commands._registry["commands"].mode == "*"
@pytest.mark.asyncio
async def test_commands_lists_all_commands(player):
"""Test that commands command lists all unique commands."""
# Execute the command
await commands.dispatch(player, "commands")
# Collect all output
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
# Should show some specific commands with their primary names
assert "north" in output
assert "look" in output
assert "quit" in output
assert "commands" in output
# Should not contain combat moves
assert "punch" not in output
assert "roundhouse" not in output
@pytest.mark.asyncio
async def test_commands_shows_aliases(player):
"""Test that aliases are shown in parens after command names."""
await commands.dispatch(player, "commands")
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
# Check that some aliases are shown in parentheses
assert "north(n)" in output or "north (n)" in output
assert "look(l)" in output or "look (l)" in output
assert "quit(q)" in output or "quit (q)" in output
assert "commands(cmds)" in output or "commands (cmds)" in output
@pytest.mark.asyncio
async def test_commands_deduplicates_entries(player):
"""Test that aliases don't create duplicate command entries."""
await commands.dispatch(player, "commands")
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
# Count how many times "north" appears as a primary command
# It should appear once in the movement group, not multiple times
# (Note: it might appear in parens as part of another command, but
# we're checking it doesn't appear as a standalone entry multiple times)
lines_with_north_command = [
line for line in output.split("\n") if line.strip().startswith("north")
]
# Should only have one line starting with "north" as a command
assert len(lines_with_north_command) <= 1
@pytest.mark.asyncio
async def test_commands_sorted_alphabetically(player):
"""Test that commands are sorted alphabetically."""
await commands.dispatch(player, "commands")
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
# All commands should appear in a single sorted list
assert "north" in output
assert "east" in output
assert "quit" in output
assert "look" in output
@pytest.mark.asyncio
async def test_commands_via_alias(player):
"""Test that the cmds alias works."""
await commands.dispatch(player, "cmds")
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
# Should produce the same output as commands
assert "north" in output
assert "look" in output
@pytest.mark.asyncio
async def test_commands_detail_regular_command(player):
"""Test commands detail view for a regular command."""
await commands.dispatch(player, "commands look")
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
assert "look" in output
assert "aliases: l" in output
assert "mode: normal" in output
@pytest.mark.asyncio
async def test_commands_detail_simple_combat_move(player, combat_moves):
"""Test commands detail view for a simple combat move."""
await commands.dispatch(player, "commands roundhouse")
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
assert "roundhouse" in output
assert "type: attack" in output
assert "stamina: 8.0" in output
assert "timing window: 2000ms" in output
assert "damage: 25%" in output
assert "{attacker} spins into a roundhouse kick!" in output
assert "countered by: duck, parry high, parry low" in output
@pytest.mark.asyncio
async def test_commands_detail_variant_base(player, combat_moves):
"""Test commands detail view for a variant base command."""
await commands.dispatch(player, "commands punch")
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
assert "punch" in output
assert "type: attack" in output
# Should show both variants
assert "punch left" in output
assert "{attacker} winds up a left hook!" in output
assert "countered by: dodge right, parry high" in output
assert "punch right" in output
assert "{attacker} winds up a right hook!" in output
assert "countered by: dodge left, parry high" in output
# Should show shared properties in each variant
assert "stamina: 5.0" in output
assert "timing window: 1800ms" in output
assert "damage: 15%" in output
@pytest.mark.asyncio
async def test_commands_detail_specific_variant(player, combat_moves):
"""Test commands detail view for a specific variant."""
await commands.dispatch(player, "commands punch left")
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
assert "punch left" in output
assert "type: attack" in output
assert "stamina: 5.0" in output
assert "timing window: 1800ms" in output
assert "damage: 15%" in output
assert "{attacker} winds up a left hook!" in output
assert "countered by: dodge right, parry high" in output
# Should NOT show "punch right"
assert "punch right" not in output
@pytest.mark.asyncio
async def test_commands_detail_unknown_command(player):
"""Test commands detail view for an unknown command."""
await commands.dispatch(player, "commands nonexistent")
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
assert "Unknown command: nonexistent" in output
@pytest.mark.asyncio
async def test_commands_detail_via_prefix(player, combat_moves):
"""Test commands detail view via prefix matching."""
await commands.dispatch(player, "commands ro")
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
# Should show the full command details via prefix match
assert "roundhouse" in output
assert "type: attack" in output