mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
test_sso.py, test_sso_oidc_plugin.py and test_sso_proxy_consumers.py evict utils, utils.cache, utils.cache.applications and the roles packages from sys.modules to import their plugin against fresh modules, and never put the originals back. Ansible's plugin loader keeps the applications lookup it loaded earlier in sys.modules, so that lookup stayed bound to the evicted utils.cache.applications, whose _MERGED_APPLICATIONS_CACHE utils.cache._reset_cache_for_tests() no longer reaches. On main this turned test_webserver_worker_limits red whenever one xdist worker ran a test that loads the applications lookup through Ansible, then one of these sso tests, then the worker-limits test: the stale cache served the cpus 0.5 payload of test_a_sub_cpu_proxy_still_runs_one_worker, so the later methods rendered worker_processes 1 instead of 4 (main run 41139196554, Workspace centos and fedora, second make test on gw1). _load_module now runs inside mock.patch.dict(sys.modules): the plugin keeps the fresh modules it was built against, and sys.modules returns to its prior state afterwards. This is the change store-i18n carries sinceec0fde6561, ported unchanged so both lines stay identical. Evidence: - Before: pytest -n 0 test_tor_ports.py test_sso_oidc_plugin.py test_webserver_worker_limits.py -> "3 failed, 33 passed", the CI failure set ('1' != '4' twice, worker_processes 1 vs 4). - After: test_tor_ports.py, the four sso test files and test_webserver_worker_limits.py -> "68 passed". (cherry picked from commitec0fde6561) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
134 lines
4.5 KiB
Python
134 lines
4.5 KiB
Python
"""Unit tests for the ``sso_proxy_consumers`` lookup plugin.
|
|
|
|
The plugin enumerates roles whose merged config satisfies
|
|
``services.sso.flavor == 'oauth2'`` AND ``services.sso.enabled``
|
|
potentially-truthy. The shape test (no terms) and the predicate test
|
|
(only oauth2-flavored, enabled consumers appear) are pinned here so
|
|
future contract changes are caught.
|
|
"""
|
|
|
|
import importlib
|
|
import importlib.util
|
|
import sys
|
|
import unittest
|
|
import unittest.mock as mock
|
|
|
|
from ansible.errors import AnsibleError
|
|
|
|
from . import PROJECT_ROOT
|
|
|
|
|
|
def _load_module(rel_path: str, name: str):
|
|
with mock.patch.dict(sys.modules):
|
|
for key in (
|
|
"utils.roles.applications.config",
|
|
"utils.roles.applications",
|
|
"utils",
|
|
"utils.cache.applications",
|
|
"utils.cache",
|
|
"utils.roles.applications.services.sso",
|
|
"utils.roles.applications.services",
|
|
):
|
|
sys.modules.pop(key, None)
|
|
importlib.import_module("utils.roles.applications.config")
|
|
importlib.import_module("utils.cache.applications")
|
|
importlib.import_module("utils.roles.applications.services.sso")
|
|
|
|
path = PROJECT_ROOT / rel_path
|
|
spec = importlib.util.spec_from_file_location(name, str(path))
|
|
mod = importlib.util.module_from_spec(spec)
|
|
assert spec and spec.loader
|
|
spec.loader.exec_module(mod)
|
|
return mod
|
|
|
|
|
|
def _role(*, enabled=True, flavor="oauth2"):
|
|
sso: dict = {"enabled": enabled, "flavor": flavor}
|
|
return {"services": {"sso": sso}}
|
|
|
|
|
|
class SsoProxyConsumersLookupTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.mod = _load_module(
|
|
"plugins/lookup/sso_proxy_consumers.py", "sso_proxy_consumers"
|
|
)
|
|
|
|
def setUp(self):
|
|
self._original_loader = self.mod.lookup_loader
|
|
self._stub_payload = None
|
|
|
|
loader_mock = mock.MagicMock()
|
|
loader_mock.get.return_value = mock.MagicMock(
|
|
run=lambda *_a, **_k: [self._stub_payload]
|
|
)
|
|
self.mod.lookup_loader = loader_mock
|
|
|
|
def tearDown(self):
|
|
self.mod.lookup_loader = self._original_loader
|
|
|
|
def _run(self, applications, terms=None):
|
|
self._stub_payload = applications
|
|
lk = self.mod.LookupModule()
|
|
lk._loader = mock.MagicMock()
|
|
return lk.run(terms or [], variables={"applications": applications})
|
|
|
|
def test_positional_terms_raise(self):
|
|
with self.assertRaises(AnsibleError):
|
|
self._run({}, ["extra-arg"])
|
|
|
|
def test_empty_applications_returns_empty_list(self):
|
|
result = self._run({})
|
|
self.assertEqual(result, [[]])
|
|
|
|
def test_oauth2_enabled_consumer_is_included(self):
|
|
apps = {"web-app-a": _role(enabled=True, flavor="oauth2")}
|
|
result = self._run(apps)
|
|
self.assertEqual(result, [["web-app-a"]])
|
|
|
|
def test_oidc_consumer_is_excluded(self):
|
|
apps = {"web-app-a": _role(enabled=True, flavor="oidc")}
|
|
self.assertEqual(self._run(apps), [[]])
|
|
|
|
def test_disabled_oauth2_consumer_is_excluded(self):
|
|
apps = {"web-app-a": _role(enabled=False, flavor="oauth2")}
|
|
self.assertEqual(self._run(apps), [[]])
|
|
|
|
def test_mixed_apps_only_oauth2_enabled_returned(self):
|
|
apps = {
|
|
"web-app-a": _role(enabled=True, flavor="oauth2"),
|
|
"web-app-b": _role(enabled=True, flavor="oidc"),
|
|
"web-app-c": _role(enabled=False, flavor="oauth2"),
|
|
"web-app-d": _role(enabled=True, flavor="oauth2"),
|
|
"web-app-e": {"services": {}},
|
|
}
|
|
result = self._run(apps)
|
|
self.assertEqual(result, [["web-app-a", "web-app-d"]])
|
|
|
|
def test_result_is_alphabetically_sorted(self):
|
|
apps = {
|
|
"web-app-zeta": _role(enabled=True, flavor="oauth2"),
|
|
"web-app-alpha": _role(enabled=True, flavor="oauth2"),
|
|
"web-app-mu": _role(enabled=True, flavor="oauth2"),
|
|
}
|
|
result = self._run(apps)
|
|
self.assertEqual(result, [["web-app-alpha", "web-app-mu", "web-app-zeta"]])
|
|
|
|
def test_jinja_enabled_string_is_treated_as_potentially_truthy(self):
|
|
apps = {
|
|
"web-app-a": {
|
|
"services": {
|
|
"sso": {
|
|
"enabled": "{{ 'web-app-keycloak' in group_names }}",
|
|
"flavor": "oauth2",
|
|
}
|
|
}
|
|
}
|
|
}
|
|
result = self._run(apps)
|
|
self.assertEqual(result, [["web-app-a"]])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|