test_domain_command_registry.py
python
sha256:2562dffa0a0822ac1bdea854f9b267843c6ce95b497a9dc5c55837c80a3ebd0a
feat: domain_command_registry — Phase 1 of muse#74 (SCR_01-03)
Sonnet 4.6
patch
13 days ago
| 1 | """Unit tests for muse.cli.domain_command_registry — muse#74 Phase 1. |
| 2 | |
| 3 | A dependency-free registry mapping a domain name to its dedicated |
| 4 | `muse <name> <verb>` CLI subcommand names, populated as a side effect of |
| 5 | argparse tree construction. Never hand-typed anywhere — see muse#74 for |
| 6 | the bug (a hardcoded literal in `muse domains publish`) this replaces. |
| 7 | """ |
| 8 | |
| 9 | import pytest |
| 10 | |
| 11 | from muse.cli.domain_command_registry import get_supported_commands, register_namespace |
| 12 | |
| 13 | |
| 14 | @pytest.fixture(autouse=True) |
| 15 | def _clean_registry() -> "object": |
| 16 | """Reset module-level registry state between tests — it's a shared |
| 17 | process-wide singleton by design (populated once per CLI invocation), |
| 18 | which makes tests order-dependent unless explicitly isolated.""" |
| 19 | from muse.cli import domain_command_registry |
| 20 | |
| 21 | saved = dict(domain_command_registry._REGISTRY) |
| 22 | domain_command_registry._REGISTRY.clear() |
| 23 | yield |
| 24 | domain_command_registry._REGISTRY.clear() |
| 25 | domain_command_registry._REGISTRY.update(saved) |
| 26 | |
| 27 | |
| 28 | class TestRegistryContract: |
| 29 | def test_unregistered_name_returns_empty_list(self) -> None: |
| 30 | assert get_supported_commands("nonexistent-domain") == [] |
| 31 | |
| 32 | def test_register_then_query_returns_registered_commands(self) -> None: |
| 33 | register_namespace("mist", ["create", "list", "read", "fork"]) |
| 34 | assert get_supported_commands("mist") == ["create", "fork", "list", "read"] |
| 35 | |
| 36 | def test_returned_list_is_a_copy_not_a_live_reference(self) -> None: |
| 37 | register_namespace("mist", ["create", "list"]) |
| 38 | result = get_supported_commands("mist") |
| 39 | result.append("mutated") |
| 40 | assert get_supported_commands("mist") == ["create", "list"] |
| 41 | |
| 42 | |
| 43 | class TestIdempotentReregistration: |
| 44 | def test_registering_twice_replaces_not_appends(self) -> None: |
| 45 | register_namespace("code", ["grep", "impact"]) |
| 46 | register_namespace("code", ["grep", "impact", "gravity"]) |
| 47 | result = get_supported_commands("code") |
| 48 | assert result == ["grep", "gravity", "impact"] or sorted(result) == sorted( |
| 49 | ["grep", "impact", "gravity"] |
| 50 | ) |
| 51 | # Must not have duplicated "grep"/"impact" from the first call. |
| 52 | assert result.count("grep") == 1 |
| 53 | assert result.count("impact") == 1 |
| 54 | |
| 55 | def test_registering_twice_does_not_merge_old_and_new(self) -> None: |
| 56 | register_namespace("code", ["old-command"]) |
| 57 | register_namespace("code", ["new-command"]) |
| 58 | result = get_supported_commands("code") |
| 59 | assert "old-command" not in result |
| 60 | assert "new-command" in result |
| 61 | |
| 62 | |
| 63 | class TestDeterministicOrdering: |
| 64 | def test_output_is_sorted_regardless_of_insertion_order(self) -> None: |
| 65 | register_namespace("social", ["zebra", "alpha", "mike"]) |
| 66 | assert get_supported_commands("social") == ["alpha", "mike", "zebra"] |
| 67 | |
| 68 | def test_output_is_sorted_from_a_set_input(self) -> None: |
| 69 | register_namespace("social", {"zebra", "alpha", "mike"}) |
| 70 | assert get_supported_commands("social") == ["alpha", "mike", "zebra"] |
| 71 | |
| 72 | def test_output_is_sorted_from_dict_keys_input(self) -> None: |
| 73 | # argparse's ._SubParsersAction.choices is a dict — this is the |
| 74 | # real shape callers will pass in Phase 2. |
| 75 | choices = {"zebra": object(), "alpha": object(), "mike": object()} |
| 76 | register_namespace("social", choices.keys()) |
| 77 | assert get_supported_commands("social") == ["alpha", "mike", "zebra"] |
File History
1 commit
sha256:2562dffa0a0822ac1bdea854f9b267843c6ce95b497a9dc5c55837c80a3ebd0a
feat: domain_command_registry — Phase 1 of muse#74 (SCR_01-03)
Sonnet 4.6
patch
13 days ago