"""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 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 " in output assert "commands" in output assert "skills" in output @pytest.mark.asyncio async def test_help_known_command_shows_detail(player): """help 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 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()