diff --git a/tests/test_strings.py b/tests/test_strings.py new file mode 100644 index 0000000..ce8c671 --- /dev/null +++ b/tests/test_strings.py @@ -0,0 +1,77 @@ +"""Guard tests for the strings/ package. + +strings/ is split into domain submodules whose names are re-exported from +strings/__init__.py so callers keep using `strings.NAME`. It's easy to add a +constant to a submodule and forget to re-export it (or to shadow a name across +two submodules) - both would only surface as a runtime crash in a command. +These tests catch that at test time instead. +""" + +import importlib +import pkgutil + +import strings + +# Auto-discover submodules so a newly added one is covered without editing this. +SUBMODULES = sorted(m.name for m in pkgutil.iter_modules(strings.__path__)) + + +def _submodule(name): + return importlib.import_module(f"strings.{name}") + + +class TestStringsPackage: + def test_submodules_discovered(self): + # Sanity: the split actually produced multiple domain modules. + assert len(SUBMODULES) >= 2, SUBMODULES + + def test_every_submodule_declares_all(self): + for name in SUBMODULES: + mod = _submodule(name) + assert hasattr(mod, "__all__"), f"strings.{name} is missing __all__" + + def test_all_entries_exist_in_their_submodule(self): + for name in SUBMODULES: + mod = _submodule(name) + for const in mod.__all__: + assert hasattr(mod, const), ( + f"{const} is listed in strings.{name}.__all__ " + f"but not defined in that module" + ) + + def test_every_name_is_reexported_from_package(self): + for name in SUBMODULES: + mod = _submodule(name) + for const in mod.__all__: + assert hasattr(strings, const), ( + f"{const} is defined in strings.{name} but not re-exported " + f"from strings/__init__.py - add it to the imports there" + ) + assert getattr(strings, const) is getattr(mod, const), ( + f"strings.{const} is not the same object as strings.{name}.{const}" + ) + + def test_no_name_defined_in_two_submodules(self): + origin = {} + for name in SUBMODULES: + for const in _submodule(name).__all__: + assert const not in origin, ( + f"{const} is defined in both strings.{origin[const]} " + f"and strings.{name}" + ) + origin[const] = name + + def test_package_all_matches_submodule_union(self): + union = set() + for name in SUBMODULES: + union |= set(_submodule(name).__all__) + assert set(strings.__all__) == union, { + "missing_from_package_all": sorted(union - set(strings.__all__)), + "extra_in_package_all": sorted(set(strings.__all__) - union), + } + + def test_public_constants_all_declared(self): + # Every UPPER_CASE constant exposed on the package is accounted for in + # __all__ (E, the emoji helper, is an implementation detail, not a string). + public = {n for n in vars(strings) if n.isupper() and n != "E"} + assert public == set(strings.__all__)