mud/tests/test_commands_list.py

243 lines
7.7 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_clean_names_no_parens(player):
"""Test that command names are shown cleanly without alias parens."""
await commands.dispatch(player, "commands")
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
# Commands should appear without parens
assert "north" in output
assert "look" in output
assert "quit" in output
assert "commands" in output
# Should NOT show aliases in parens
assert "north(n)" not in output
assert "look(l)" not in output
assert "quit(q)" not in output
assert "commands(cmds)" not 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} shifts {his} weight back..." 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} retracts {his} left arm..." in output
assert "countered by: dodge right, parry high" in output
assert "punch right" in output
assert "{attacker} retracts {his} right arm..." 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} retracts {his} left arm..." 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