"""Unit tests for muse.cli.domain_command_registry — muse#74 Phase 1. A dependency-free registry mapping a domain name to its dedicated `muse ` CLI subcommand names, populated as a side effect of argparse tree construction. Never hand-typed anywhere — see muse#74 for the bug (a hardcoded literal in `muse domains publish`) this replaces. """ import pytest from muse.cli.domain_command_registry import get_supported_commands, register_namespace @pytest.fixture(autouse=True) def _clean_registry() -> "object": """Reset module-level registry state between tests — it's a shared process-wide singleton by design (populated once per CLI invocation), which makes tests order-dependent unless explicitly isolated.""" from muse.cli import domain_command_registry saved = dict(domain_command_registry._REGISTRY) domain_command_registry._REGISTRY.clear() yield domain_command_registry._REGISTRY.clear() domain_command_registry._REGISTRY.update(saved) class TestRegistryContract: def test_unregistered_name_returns_empty_list(self) -> None: assert get_supported_commands("nonexistent-domain") == [] def test_register_then_query_returns_registered_commands(self) -> None: register_namespace("mist", ["create", "list", "read", "fork"]) assert get_supported_commands("mist") == ["create", "fork", "list", "read"] def test_returned_list_is_a_copy_not_a_live_reference(self) -> None: register_namespace("mist", ["create", "list"]) result = get_supported_commands("mist") result.append("mutated") assert get_supported_commands("mist") == ["create", "list"] class TestIdempotentReregistration: def test_registering_twice_replaces_not_appends(self) -> None: register_namespace("code", ["grep", "impact"]) register_namespace("code", ["grep", "impact", "gravity"]) result = get_supported_commands("code") assert result == ["grep", "gravity", "impact"] or sorted(result) == sorted( ["grep", "impact", "gravity"] ) # Must not have duplicated "grep"/"impact" from the first call. assert result.count("grep") == 1 assert result.count("impact") == 1 def test_registering_twice_does_not_merge_old_and_new(self) -> None: register_namespace("code", ["old-command"]) register_namespace("code", ["new-command"]) result = get_supported_commands("code") assert "old-command" not in result assert "new-command" in result class TestDeterministicOrdering: def test_output_is_sorted_regardless_of_insertion_order(self) -> None: register_namespace("social", ["zebra", "alpha", "mike"]) assert get_supported_commands("social") == ["alpha", "mike", "zebra"] def test_output_is_sorted_from_a_set_input(self) -> None: register_namespace("social", {"zebra", "alpha", "mike"}) assert get_supported_commands("social") == ["alpha", "mike", "zebra"] def test_output_is_sorted_from_dict_keys_input(self) -> None: # argparse's ._SubParsersAction.choices is a dict — this is the # real shape callers will pass in Phase 2. choices = {"zebra": object(), "alpha": object(), "mike": object()} register_namespace("social", choices.keys()) assert get_supported_commands("social") == ["alpha", "mike", "zebra"]