From 8424404d2724aa5fe7feeea812c00989384d9797 Mon Sep 17 00:00:00 2001 From: Jared Miller Date: Sun, 15 Feb 2026 12:40:10 -0500 Subject: [PATCH] Normalize alias casing across command and persistence --- src/mudlib/commands/alias.py | 6 ++--- src/mudlib/store/__init__.py | 3 ++- tests/test_alias.py | 51 ++++++++++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/mudlib/commands/alias.py b/src/mudlib/commands/alias.py index 756dee7..b489781 100644 --- a/src/mudlib/commands/alias.py +++ b/src/mudlib/commands/alias.py @@ -28,7 +28,7 @@ async def cmd_alias(player: Player, args: str) -> None: # Check if this is a single-word lookup or a definition parts = args.split(None, 1) - alias_name = parts[0] + alias_name = parts[0].lower() if len(parts) == 1: # Show single alias @@ -39,7 +39,7 @@ async def cmd_alias(player: Player, args: str) -> None: return # Create alias - expansion = parts[1] + expansion = parts[1].strip() # Cannot alias over built-in commands if alias_name in _registry: @@ -56,7 +56,7 @@ async def cmd_unalias(player: Player, args: str) -> None: Usage: unalias """ - alias_name = args.strip() + alias_name = args.strip().lower() if not alias_name: await player.send("Usage: unalias \r\n") diff --git a/src/mudlib/store/__init__.py b/src/mudlib/store/__init__.py index 33caade..debf0b4 100644 --- a/src/mudlib/store/__init__.py +++ b/src/mudlib/store/__init__.py @@ -502,7 +502,8 @@ def load_aliases(name: str, db_path: str | Path | None = None) -> dict[str, str] (name,), ) - result = {alias: expansion for alias, expansion in cursor.fetchall()} + # Normalize keys to lowercase so dispatch can match consistently. + result = {alias.lower(): expansion for alias, expansion in cursor.fetchall()} conn.close() return result diff --git a/tests/test_alias.py b/tests/test_alias.py index d68db45..7cb32e3 100644 --- a/tests/test_alias.py +++ b/tests/test_alias.py @@ -25,11 +25,11 @@ def test_save_and_load_aliases_roundtrip(): db_path = Path(tmpdir) / "test.db" init_db(db_path) - aliases = {"pr": "punch right", "pl": "punch left", "l": "look"} + aliases = {"PR": "punch right", "pl": "punch left", "l": "look"} save_aliases("goku", aliases, db_path) loaded = load_aliases("goku", db_path) - assert loaded == aliases + assert loaded == {"pr": "punch right", "pl": "punch left", "l": "look"} def test_load_aliases_empty(): @@ -76,6 +76,16 @@ async def test_alias_create(player): player.writer.write.assert_called_with("Alias set: pr -> punch right\r\n") +@pytest.mark.asyncio +async def test_alias_normalizes_name_to_lowercase(player): + """Alias names are normalized so dispatch lookup is consistent.""" + from mudlib.commands.alias import cmd_alias + + await cmd_alias(player, "PR punch right") + assert "pr" in player.aliases + assert "PR" not in player.aliases + + @pytest.mark.asyncio async def test_alias_list_with_aliases(player): """alias with no args lists all aliases.""" @@ -113,6 +123,16 @@ async def test_unalias_removes_alias(player): player.writer.write.assert_called_with("Alias removed: pr\r\n") +@pytest.mark.asyncio +async def test_unalias_is_case_insensitive(player): + """unalias should remove aliases regardless of case.""" + from mudlib.commands.alias import cmd_unalias + + player.aliases = {"pr": "punch right"} + await cmd_unalias(player, "PR") + assert "pr" not in player.aliases + + @pytest.mark.asyncio async def test_unalias_no_such_alias(player): """unalias on non-existent alias shows error.""" @@ -135,6 +155,18 @@ async def test_alias_cannot_override_builtin(player): assert "look" not in player.aliases +@pytest.mark.asyncio +async def test_alias_builtin_collision_is_case_insensitive(player): + """Built-in collision checks should apply regardless of alias casing.""" + import mudlib.commands.look # noqa: F401 - needed to register look command + from mudlib.commands.alias import cmd_alias + + await cmd_alias(player, "LOOK punch right") + player.writer.write.assert_called_with( + "Cannot alias over built-in command: look\r\n" + ) + + # Dispatch integration tests @pytest.mark.asyncio async def test_alias_expands_in_dispatch(player): @@ -151,6 +183,21 @@ async def test_alias_expands_in_dispatch(player): assert called_with == ["hello"] +@pytest.mark.asyncio +async def test_alias_expands_in_dispatch_case_insensitive_key(player): + """Stored aliases with uppercase keys still load/use as lowercase.""" + called_with = [] + + async def test_handler(p, args): + called_with.append(args) + + register(CommandDefinition("testcmd", test_handler)) + player.aliases["pr"] = "testcmd" + + await dispatch(player, "PR hello") + assert called_with == ["hello"] + + @pytest.mark.asyncio async def test_alias_with_extra_args(player): """Alias expansion preserves additional arguments."""