345 lines
11 KiB
Python
345 lines
11 KiB
Python
"""Tests for TOML help topic loading."""
|
|
|
|
import textwrap
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
from mudlib import commands
|
|
from mudlib.commands import help as help_mod # noqa: F401
|
|
from mudlib.commands import (
|
|
helpadmin, # noqa: F401
|
|
look, # noqa: F401
|
|
movement, # noqa: F401
|
|
)
|
|
from mudlib.commands.help import _help_topics
|
|
from mudlib.content import HelpTopic, load_help_topics
|
|
|
|
|
|
@pytest.fixture
|
|
def help_dir(tmp_path):
|
|
"""Create a temp directory with sample help TOML files."""
|
|
topic = tmp_path / "combat.toml"
|
|
topic.write_text(
|
|
textwrap.dedent("""\
|
|
name = "combat"
|
|
title = "combat primer"
|
|
body = \"\"\"
|
|
combat is initiated when you attack another entity.
|
|
use skills to learn your available moves.
|
|
\"\"\"
|
|
""")
|
|
)
|
|
|
|
admin_topic = tmp_path / "building.toml"
|
|
admin_topic.write_text(
|
|
textwrap.dedent("""\
|
|
name = "building"
|
|
title = "builder's guide"
|
|
admin = true
|
|
body = \"\"\"
|
|
use @dig to create zones and @paint to edit terrain.
|
|
\"\"\"
|
|
""")
|
|
)
|
|
return tmp_path
|
|
|
|
|
|
def test_load_help_topics(help_dir):
|
|
topics = load_help_topics(help_dir)
|
|
assert "combat" in topics
|
|
assert "building" in topics
|
|
|
|
|
|
def test_help_topic_fields(help_dir):
|
|
topics = load_help_topics(help_dir)
|
|
combat = topics["combat"]
|
|
assert combat.name == "combat"
|
|
assert combat.title == "combat primer"
|
|
assert combat.admin is False
|
|
assert "combat is initiated" in combat.body
|
|
|
|
|
|
def test_help_topic_admin_flag(help_dir):
|
|
topics = load_help_topics(help_dir)
|
|
building = topics["building"]
|
|
assert building.admin is True
|
|
|
|
|
|
def test_help_topic_title_defaults_to_name(tmp_path):
|
|
topic = tmp_path / "simple.toml"
|
|
topic.write_text('name = "simple"\nbody = "just a test"\n')
|
|
topics = load_help_topics(tmp_path)
|
|
assert topics["simple"].title == "simple"
|
|
|
|
|
|
def test_load_help_topics_empty_dir(tmp_path):
|
|
topics = load_help_topics(tmp_path)
|
|
assert topics == {}
|
|
|
|
|
|
def test_load_help_topics_skips_bad_files(tmp_path):
|
|
bad = tmp_path / "broken.toml"
|
|
bad.write_text("not valid toml [[[")
|
|
good = tmp_path / "good.toml"
|
|
good.write_text('name = "good"\nbody = "works"\n')
|
|
topics = load_help_topics(tmp_path)
|
|
assert "good" in topics
|
|
assert "broken" not in topics
|
|
|
|
|
|
@pytest.fixture
|
|
def player(mock_reader, mock_writer):
|
|
from mudlib.player import Player
|
|
|
|
return Player(name="Tester", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
|
|
|
|
|
@pytest.fixture
|
|
def admin_player(mock_reader, mock_writer):
|
|
from mudlib.player import Player
|
|
|
|
p = Player(name="Admin", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
|
p.is_admin = True
|
|
return p
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_and_load_topics():
|
|
"""Clear help topics, load content topics, then clear again after tests."""
|
|
from pathlib import Path
|
|
|
|
_help_topics.clear()
|
|
help_dir = Path(__file__).resolve().parents[1] / "content" / "help"
|
|
if help_dir.exists():
|
|
loaded = load_help_topics(help_dir)
|
|
_help_topics.update(loaded)
|
|
yield
|
|
_help_topics.clear()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_help_shows_toml_topic(player):
|
|
_help_topics["combat"] = HelpTopic(
|
|
name="combat", body="fight stuff", title="combat primer"
|
|
)
|
|
await commands.dispatch(player, "help combat")
|
|
output = "".join(c[0][0] for c in player.writer.write.call_args_list)
|
|
assert "combat primer" in output
|
|
assert "fight stuff" in output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_help_admin_topic_hidden_from_players(player):
|
|
_help_topics["secret"] = HelpTopic(
|
|
name="secret", body="hidden", title="secret stuff", admin=True
|
|
)
|
|
await commands.dispatch(player, "help secret")
|
|
output = "".join(c[0][0] for c in player.writer.write.call_args_list)
|
|
assert "hidden" not in output
|
|
assert "unknown" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_help_admin_topic_visible_to_admins(admin_player):
|
|
_help_topics["secret"] = HelpTopic(
|
|
name="secret", body="hidden", title="secret stuff", admin=True
|
|
)
|
|
await commands.dispatch(admin_player, "help secret")
|
|
output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
|
|
assert "hidden" in output
|
|
|
|
|
|
def test_zones_toml_loads_from_content():
|
|
from pathlib import Path
|
|
|
|
help_dir = Path(__file__).resolve().parents[1] / "content" / "help"
|
|
topics = load_help_topics(help_dir)
|
|
assert "zones" in topics
|
|
assert topics["zones"].admin is True
|
|
assert "@zones" in topics["zones"].body
|
|
|
|
|
|
def test_player_has_pending_input():
|
|
from mudlib.player import Player
|
|
|
|
p = Player(name="Test", x=0, y=0, reader=MagicMock(), writer=MagicMock())
|
|
assert p.pending_input is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_at_help_lists_topics(admin_player):
|
|
_help_topics["combat"] = HelpTopic(
|
|
name="combat", body="fight", title="combat primer"
|
|
)
|
|
_help_topics["zones"] = HelpTopic(
|
|
name="zones", body="build", title="zone guide", admin=True
|
|
)
|
|
await commands.dispatch(admin_player, "@help")
|
|
output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
|
|
assert "combat" in output
|
|
assert "combat primer" in output
|
|
assert "zones" in output
|
|
assert "[admin]" in output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_at_help_requires_admin(player):
|
|
await commands.dispatch(player, "@help")
|
|
output = "".join(c[0][0] for c in player.writer.write.call_args_list)
|
|
# Should be rejected by dispatch (admin=True check)
|
|
assert "permission" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_at_help_create_with_name_prompts_title(admin_player):
|
|
"""@help create <name> should prompt for title."""
|
|
await commands.dispatch(admin_player, "@help create combat")
|
|
output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
|
|
assert "title" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_at_help_create_no_args_prompts_name(admin_player):
|
|
"""@help create with no args prompts for the topic name."""
|
|
await commands.dispatch(admin_player, "@help create")
|
|
output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
|
|
assert "name" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_at_help_edit_unknown_topic(admin_player):
|
|
await commands.dispatch(admin_player, "@help edit bogus")
|
|
output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
|
|
assert "not found" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_at_help_edit_no_args(admin_player):
|
|
await commands.dispatch(admin_player, "@help edit")
|
|
output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
|
|
assert "usage" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_at_help_edit_prompts_title(admin_player):
|
|
_help_topics["combat"] = HelpTopic(
|
|
name="combat", body="old body", title="combat primer"
|
|
)
|
|
await commands.dispatch(admin_player, "@help edit combat")
|
|
output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
|
|
# Should show current title as default
|
|
assert "combat primer" in output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_at_help_remove_unknown_topic(admin_player):
|
|
await commands.dispatch(admin_player, "@help remove bogus")
|
|
output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
|
|
assert "not found" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_at_help_remove_no_args(admin_player):
|
|
await commands.dispatch(admin_player, "@help remove")
|
|
output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
|
|
assert "usage" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_at_help_remove_prompts_confirmation(admin_player):
|
|
_help_topics["combat"] = HelpTopic(
|
|
name="combat", body="fight", title="combat primer"
|
|
)
|
|
await commands.dispatch(admin_player, "@help remove combat")
|
|
output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
|
|
assert "y/n" in output.lower() or "confirm" in output.lower()
|
|
|
|
|
|
def test_at_help_toml_loads_from_content():
|
|
from pathlib import Path
|
|
|
|
help_dir = Path(__file__).resolve().parents[1] / "content" / "help"
|
|
if help_dir.exists():
|
|
topics = load_help_topics(help_dir)
|
|
assert "@help" in topics
|
|
assert topics["@help"].admin is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_help_command_is_registered():
|
|
"""The help command should be registered in the command registry."""
|
|
assert "help" in commands._registry
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_help_has_wildcard_mode():
|
|
"""Help should work from any mode."""
|
|
cmd_def = commands._registry["help"]
|
|
assert cmd_def.mode == "*"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_help_no_args_shows_usage(player):
|
|
"""help with no args shows usage hint."""
|
|
await commands.dispatch(player, "help")
|
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
|
assert "help <command>" in output
|
|
assert "commands" in output
|
|
assert "skills" in output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_help_known_command_shows_detail(player):
|
|
"""help <known command> shows detail view."""
|
|
await commands.dispatch(player, "help look")
|
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
|
assert "look" in output.lower()
|
|
assert "mode:" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_help_unknown_command_shows_error(player):
|
|
"""help <unknown> shows error message."""
|
|
await commands.dispatch(player, "help nonexistent")
|
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
|
assert "nonexistent" in output.lower()
|
|
assert "unknown" in output.lower() or "not found" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_help_and_commands_both_exist():
|
|
"""Both help and commands should be registered independently."""
|
|
assert "help" in commands._registry
|
|
assert "commands" in commands._registry
|
|
# They should be different functions
|
|
assert commands._registry["help"].handler != commands._registry["commands"].handler
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_help_zones_shows_guide(admin_player):
|
|
"""help zones shows zone guide text with command references."""
|
|
await commands.dispatch(admin_player, "help zones")
|
|
output = "".join([call[0][0] for call in admin_player.writer.write.call_args_list])
|
|
assert "zones" in output
|
|
assert "@zones" in output
|
|
assert "@goto" in output
|
|
assert "@dig" in output
|
|
assert "@paint" in output
|
|
assert "@save" in output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_help_zones_shows_see_also(admin_player):
|
|
"""help zones output contains see also cross-references."""
|
|
await commands.dispatch(admin_player, "help zones")
|
|
output = "".join([call[0][0] for call in admin_player.writer.write.call_args_list])
|
|
assert "see:" in output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_help_zones_requires_admin(player):
|
|
"""Non-admin players cannot see admin help topics."""
|
|
await commands.dispatch(player, "help zones")
|
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
|
assert "unknown" in output.lower()
|