mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-13 06:06:38 +00:00
Cleans up the lint debt the main->feature/web-app-kix merge surfaced.
Each change addresses a real failure surfaced by `make test`; no
``# nocheck`` blanket suppressions added.
Migrations to the shared cache helpers:
- ~25 test files swap raw ``yaml.safe_load`` / ``yaml.safe_dump`` /
``yaml.dump`` for ``utils.cache.yaml.{load_yaml_any,load_yaml_all,
load_yaml_str,dump_yaml,load_yaml_all_str}``.
- ~30 test files swap raw ``glob.glob`` / ``os.walk`` / ``Path.rglob``
for ``utils.cache.files.iter_project_files`` (or
``iter_non_ignored_files`` for the dependabot fallback walker, or
per-role ``Path.iterdir`` / ``Path.glob`` where the scope is a
single subtree).
- 3 ``Path.read_text`` lint helpers now route through
``utils.cache.files.read_text``.
Variant + service-flag fixes:
- web-app-kix: shift its subnet from 192.168.105.80/28 to
192.168.105.128/28 to clear the collision with web-app-hugo and
svc-prx-openresty.
- web-app-fediwall: add the missing ``mastodon`` / ``friendica``
service flags referenced by ``meta/variants.yml``; pin the css /
logout / dashboard / matomo / prometheus literals with
``# nocheck: dynamic-flag`` and add a third "everything off"
variant so the variant-coverage matrix sees both true and false
pinning for every dynamic flag.
- web-app-magento, web-app-shopware, web-svc-xmpp: switch the
``run_after: web-app-keycloak`` matcher from a literal-false
oauth2/oidc opt-out to the ``"{{ 'web-app-keycloak' in group_names }}"``
dynamic form, and extend each role's ``meta/variants.yml`` with the
matching true/false (and shopware's LDAP-only) coverage.
- web-app-fusiondirectory, web-app-matrix: declare the
``matomo`` flag so their ``run_after: web-app-matomo`` resolves
through an explicit services entry; add matomo true/false to both
variants files.
- web-app-fediwall, web-app-mini-qr: add the
``# nocheck: logout`` opt-out directly above ``logout:`` so the
logout-dashboard guard accepts the legitimately-disabled state.
- ~30 ``services.yml`` files: insert
``# nocheck: dynamic-flag`` directly above the literal
``css`` / ``redis`` / ``view`` / ``worker`` / ``email`` / ``html``
/ ``mariadb`` / ``opensearch`` / ``talk`` / ``logout`` blocks that
the dynamic-flag guard surfaced.
Code-quality follow-ups uncovered by ruff:
- utils/cache/yaml.py: collapse ``load_yaml_all_str`` to a one-line
return + on-call ``# nocheck: direct-yaml`` so the lint accepts
the cache module's own ``yaml.safe_load_all`` invocation.
- utils/templating.py: replace the ``Optional[Any]`` import that
slipped in from main with the modern ``Any | None``; rewrite the
cleanup ``try/except/pass`` blocks as
``contextlib.suppress(Exception)`` so the imported
``contextlib`` module is consumed consistently.
- 7 new-from-main lint test files (test_inline_script_block_size,
test_no_inline_multiline_php_in_sh, test_no_lookup_config_jinja_default,
test_no_inline_multiline_sql, test_web_role_no_web_dependency,
test_shared_primary_entity_run_once, test_fediwall_active): drop
their local ``Path(__file__).parents[N]`` PROJECT_ROOT
computations and import the package-level constant instead.
- roles/web-app-moodle/templates/playwright.env.j2: drop the
unreferenced ``KEYCLOAK_REALM_NAME`` env key that the
playwright-env-keys-used guard flagged.
305 lines
11 KiB
Python
305 lines
11 KiB
Python
import importlib.util
|
|
import sys
|
|
import unittest
|
|
from types import ModuleType
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
|
|
def _load_lookup_module() -> ModuleType:
|
|
"""Load the fediwall_active lookup plugin directly from the global
|
|
plugins/lookup/ path. Stubs out ansible imports so the unit test
|
|
does not require the ansible runtime."""
|
|
|
|
# Stub minimal ansible.errors / ansible.plugins.lookup /
|
|
# ansible.plugins.loader so the plugin module imports cleanly.
|
|
if "ansible" not in sys.modules:
|
|
ansible_pkg = ModuleType("ansible")
|
|
sys.modules["ansible"] = ansible_pkg
|
|
|
|
if "ansible.errors" not in sys.modules:
|
|
errors_mod = ModuleType("ansible.errors")
|
|
|
|
class _AnsibleError(Exception):
|
|
pass
|
|
|
|
errors_mod.AnsibleError = _AnsibleError
|
|
sys.modules["ansible.errors"] = errors_mod
|
|
|
|
if "ansible.plugins" not in sys.modules:
|
|
plugins_pkg = ModuleType("ansible.plugins")
|
|
sys.modules["ansible.plugins"] = plugins_pkg
|
|
|
|
if "ansible.plugins.lookup" not in sys.modules:
|
|
lookup_mod = ModuleType("ansible.plugins.lookup")
|
|
|
|
class _LookupBase:
|
|
pass
|
|
|
|
lookup_mod.LookupBase = _LookupBase
|
|
sys.modules["ansible.plugins.lookup"] = lookup_mod
|
|
|
|
if "ansible.plugins.loader" not in sys.modules:
|
|
loader_mod = ModuleType("ansible.plugins.loader")
|
|
loader_mod.lookup_loader = MagicMock()
|
|
sys.modules["ansible.plugins.loader"] = loader_mod
|
|
|
|
from . import PROJECT_ROOT
|
|
|
|
plugin_path = PROJECT_ROOT / "plugins" / "lookup" / "fediwall_active.py"
|
|
spec = importlib.util.spec_from_file_location("fediwall_active", plugin_path)
|
|
module = importlib.util.module_from_spec(spec)
|
|
assert spec.loader is not None
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
class TestSelectActive(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls._module = _load_lookup_module()
|
|
|
|
def test_returns_only_siblings_present_in_group_names(self):
|
|
siblings = ["web-app-mastodon", "web-app-pixelfed", "web-app-friendica"]
|
|
group_names = [
|
|
"all",
|
|
"web-app-mastodon",
|
|
"web-app-friendica",
|
|
"svc-db-postgres",
|
|
]
|
|
self.assertEqual(
|
|
type(self)._module.select_active(siblings, group_names),
|
|
["web-app-mastodon", "web-app-friendica"],
|
|
)
|
|
|
|
def test_preserves_input_order(self):
|
|
siblings = ["web-app-friendica", "web-app-mastodon", "web-app-pixelfed"]
|
|
group_names = ["web-app-mastodon", "web-app-pixelfed", "web-app-friendica"]
|
|
self.assertEqual(
|
|
type(self)._module.select_active(siblings, group_names),
|
|
["web-app-friendica", "web-app-mastodon", "web-app-pixelfed"],
|
|
)
|
|
|
|
def test_returns_empty_when_no_overlap(self):
|
|
siblings = ["web-app-mastodon", "web-app-pixelfed"]
|
|
group_names = ["all", "svc-db-postgres", "web-app-nextcloud"]
|
|
self.assertEqual(type(self)._module.select_active(siblings, group_names), [])
|
|
|
|
def test_returns_empty_when_siblings_is_empty(self):
|
|
self.assertEqual(type(self)._module.select_active([], ["web-app-mastodon"]), [])
|
|
|
|
def test_returns_empty_when_siblings_is_none(self):
|
|
self.assertEqual(
|
|
type(self)._module.select_active(None, ["web-app-mastodon"]), []
|
|
)
|
|
|
|
def test_returns_empty_when_group_names_is_empty(self):
|
|
self.assertEqual(type(self)._module.select_active(["web-app-mastodon"], []), [])
|
|
|
|
def test_returns_empty_when_group_names_is_none(self):
|
|
self.assertEqual(
|
|
type(self)._module.select_active(["web-app-mastodon"], None), []
|
|
)
|
|
|
|
def test_preserves_duplicates_in_siblings(self):
|
|
siblings = ["web-app-mastodon", "web-app-mastodon"]
|
|
group_names = ["web-app-mastodon"]
|
|
self.assertEqual(
|
|
type(self)._module.select_active(siblings, group_names),
|
|
["web-app-mastodon", "web-app-mastodon"],
|
|
)
|
|
|
|
|
|
class TestResolveForWant(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls._module = _load_lookup_module()
|
|
cls._AnsibleError = sys.modules["ansible.errors"].AnsibleError
|
|
|
|
def test_siblings_want_does_not_call_resolver(self):
|
|
def boom(_):
|
|
raise AssertionError("resolver must not be invoked for want='siblings'")
|
|
|
|
out = type(self)._module.resolve_for_want(
|
|
["web-app-mastodon", "web-app-pixelfed"],
|
|
["web-app-mastodon"],
|
|
"siblings",
|
|
boom,
|
|
)
|
|
self.assertEqual(out, ["web-app-mastodon"])
|
|
|
|
def test_url_bases_want_uses_resolver_per_active_sibling(self):
|
|
siblings = ["web-app-mastodon", "web-app-pixelfed", "web-app-friendica"]
|
|
group_names = ["web-app-mastodon", "web-app-friendica"]
|
|
|
|
def resolver(s):
|
|
return f"https://{s}.example/"
|
|
|
|
out = type(self)._module.resolve_for_want(
|
|
siblings, group_names, "url_bases", resolver
|
|
)
|
|
self.assertEqual(
|
|
out,
|
|
[
|
|
"https://web-app-mastodon.example/",
|
|
"https://web-app-friendica.example/",
|
|
],
|
|
)
|
|
|
|
def test_domains_want_uses_resolver_per_active_sibling(self):
|
|
siblings = ["web-app-mastodon", "web-app-pixelfed"]
|
|
group_names = ["web-app-pixelfed"]
|
|
|
|
def resolver(s):
|
|
return s.removeprefix("web-app-") + ".example"
|
|
|
|
out = type(self)._module.resolve_for_want(
|
|
siblings, group_names, "domains", resolver
|
|
)
|
|
self.assertEqual(out, ["pixelfed.example"])
|
|
|
|
def test_returns_empty_when_no_active_sibling(self):
|
|
out = type(self)._module.resolve_for_want(
|
|
["web-app-mastodon"],
|
|
["svc-db-postgres"],
|
|
"url_bases",
|
|
lambda _: "https://nope/",
|
|
)
|
|
self.assertEqual(out, [])
|
|
|
|
def test_invalid_want_raises(self):
|
|
with self.assertRaises(type(self)._AnsibleError):
|
|
type(self)._module.resolve_for_want(
|
|
["web-app-mastodon"],
|
|
["web-app-mastodon"],
|
|
"bogus",
|
|
lambda s: s,
|
|
)
|
|
|
|
def test_resolver_required_for_non_siblings_want(self):
|
|
with self.assertRaises(type(self)._AnsibleError):
|
|
type(self)._module.resolve_for_want(
|
|
["web-app-mastodon"],
|
|
["web-app-mastodon"],
|
|
"url_bases",
|
|
None,
|
|
)
|
|
|
|
|
|
class TestLookupModuleRun(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls._module = _load_lookup_module()
|
|
cls._AnsibleError = sys.modules["ansible.errors"].AnsibleError
|
|
|
|
def _make_lookup(self):
|
|
lookup = type(self)._module.LookupModule()
|
|
lookup._templar = MagicMock()
|
|
lookup._loader = MagicMock()
|
|
return lookup
|
|
|
|
def test_rejects_missing_term(self):
|
|
lookup = self._make_lookup()
|
|
with self.assertRaises(type(self)._AnsibleError):
|
|
lookup.run([], variables={"group_names": []})
|
|
|
|
def test_rejects_extra_terms(self):
|
|
lookup = self._make_lookup()
|
|
with self.assertRaises(type(self)._AnsibleError):
|
|
lookup.run(["url_bases", "extra"], variables={"group_names": []})
|
|
|
|
def test_rejects_unknown_want(self):
|
|
lookup = self._make_lookup()
|
|
with self.assertRaises(type(self)._AnsibleError):
|
|
lookup.run(["bogus"], variables={"group_names": []})
|
|
|
|
def test_siblings_want_returns_active_set_only(self):
|
|
config = MagicMock()
|
|
config.run.return_value = [
|
|
["web-app-mastodon", "web-app-pixelfed", "web-app-friendica"]
|
|
]
|
|
with patch.object(type(self)._module.lookup_loader, "get", return_value=config):
|
|
lookup = self._make_lookup()
|
|
result = lookup.run(
|
|
["siblings"],
|
|
variables={"group_names": ["web-app-mastodon", "web-app-friendica"]},
|
|
)
|
|
self.assertEqual(result, [["web-app-mastodon", "web-app-friendica"]])
|
|
config.run.assert_called_once()
|
|
|
|
def test_url_bases_want_calls_tls_lookup_per_active_sibling(self):
|
|
config = MagicMock()
|
|
config.run.return_value = [
|
|
["web-app-mastodon", "web-app-pixelfed", "web-app-friendica"]
|
|
]
|
|
tls = MagicMock()
|
|
tls.run.side_effect = [
|
|
["https://microblog.example/"],
|
|
["https://social.example/"],
|
|
]
|
|
|
|
def fake_get(name, **_):
|
|
if name == "config":
|
|
return config
|
|
if name == "tls":
|
|
return tls
|
|
raise AssertionError(f"unexpected lookup '{name}'")
|
|
|
|
with patch.object(
|
|
type(self)._module.lookup_loader, "get", side_effect=fake_get
|
|
):
|
|
lookup = self._make_lookup()
|
|
result = lookup.run(
|
|
["url_bases"],
|
|
variables={"group_names": ["web-app-mastodon", "web-app-friendica"]},
|
|
)
|
|
|
|
self.assertEqual(
|
|
result,
|
|
[["https://microblog.example/", "https://social.example/"]],
|
|
)
|
|
self.assertEqual(tls.run.call_count, 2)
|
|
called_with = [call.args[0] for call in tls.run.call_args_list]
|
|
self.assertEqual(
|
|
called_with,
|
|
[
|
|
["web-app-mastodon", "url.base"],
|
|
["web-app-friendica", "url.base"],
|
|
],
|
|
)
|
|
|
|
def test_domains_want_calls_domain_lookup_per_active_sibling(self):
|
|
config = MagicMock()
|
|
config.run.return_value = [["web-app-mastodon", "web-app-pixelfed"]]
|
|
domain = MagicMock()
|
|
domain.run.side_effect = [["microblog.example"], ["photos.example"]]
|
|
|
|
def fake_get(name, **_):
|
|
if name == "config":
|
|
return config
|
|
if name == "domain":
|
|
return domain
|
|
raise AssertionError(f"unexpected lookup '{name}'")
|
|
|
|
with patch.object(
|
|
type(self)._module.lookup_loader, "get", side_effect=fake_get
|
|
):
|
|
lookup = self._make_lookup()
|
|
result = lookup.run(
|
|
["domains"],
|
|
variables={"group_names": ["web-app-mastodon", "web-app-pixelfed"]},
|
|
)
|
|
self.assertEqual(result, [["microblog.example", "photos.example"]])
|
|
|
|
def test_falls_back_to_templar_available_variables_when_variables_missing(self):
|
|
config = MagicMock()
|
|
config.run.return_value = [["web-app-mastodon"]]
|
|
with patch.object(type(self)._module.lookup_loader, "get", return_value=config):
|
|
lookup = self._make_lookup()
|
|
lookup._templar.available_variables = {"group_names": ["web-app-mastodon"]}
|
|
result = lookup.run(["siblings"], variables=None)
|
|
self.assertEqual(result, [["web-app-mastodon"]])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|