gabriel / muse public

test_domain_command_registry.py file-level

at sha256:2 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:6 chore(timeline): remove unused RationalRate import in entity.py · · Jul 10, 2026
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"]