mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-07-17 22:14:25 +02:00
Added basic integration test for get_app_path values to verify if they exists
This commit is contained in:
parent
a98332bfb9
commit
92245b5935
157
tests/integration/test_get_app_conf_paths.py
Normal file
157
tests/integration/test_get_app_conf_paths.py
Normal file
@ -0,0 +1,157 @@
|
||||
import os
|
||||
import re
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import yaml # requires PyYAML
|
||||
|
||||
|
||||
class TestGetAppConfPaths(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# Locate project root and load YAML configs
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
cls.app_config_path = root / "group_vars" / "all" / "04_applications.yml"
|
||||
with cls.app_config_path.open(encoding="utf-8") as f:
|
||||
app_cfg = yaml.safe_load(f)
|
||||
cls.defaults_app = app_cfg.get("defaults_applications", {})
|
||||
|
||||
cls.user_config_path = root / "group_vars" / "all" / "03_users.yml"
|
||||
with cls.user_config_path.open(encoding="utf-8") as f:
|
||||
user_cfg = yaml.safe_load(f)
|
||||
cls.defaults_users = user_cfg.get("default_users", {})
|
||||
|
||||
# Regex to match get_app_conf(application_id, 'some.path', ...)
|
||||
pattern = re.compile(
|
||||
r"get_app_conf\(\s*([^\),]+)\s*,\s*['\"]([^'\"]+)['\"]\s*,\s*[^\)]*\)"
|
||||
)
|
||||
|
||||
# Store occurrences: literal_ids -> {path: [(file, line), ...]}, variable paths likewise
|
||||
cls.literal_paths_by_id = {}
|
||||
cls.variable_paths = {}
|
||||
|
||||
# Recursively search all files (excluding tests/ directories)
|
||||
for dirpath, dirs, files in os.walk(root):
|
||||
if "tests" in Path(dirpath).parts:
|
||||
continue
|
||||
for fname in files:
|
||||
file_path = Path(dirpath) / fname
|
||||
try:
|
||||
text = file_path.read_text(encoding="utf-8")
|
||||
except (UnicodeDecodeError, PermissionError):
|
||||
continue
|
||||
for m in pattern.finditer(text):
|
||||
lineno = text.count("\n", 0, m.start()) + 1
|
||||
app_arg = m.group(1).strip()
|
||||
path_arg = m.group(2).strip()
|
||||
if (app_arg.startswith("'") and app_arg.endswith("'")) or (
|
||||
app_arg.startswith('"') and app_arg.endswith('"')
|
||||
):
|
||||
app_id = app_arg.strip("'\"")
|
||||
cls.literal_paths_by_id.setdefault(app_id, {}).setdefault(path_arg, []).append(
|
||||
(file_path, lineno)
|
||||
)
|
||||
else:
|
||||
cls.variable_paths.setdefault(path_arg, []).append(
|
||||
(file_path, lineno)
|
||||
)
|
||||
|
||||
def _assert_nested_key(self, mapping, dotted_path, context):
|
||||
"""
|
||||
Helper: assert that mapping contains the nested dict path defined by dotted_path.
|
||||
"""
|
||||
keys = dotted_path.split('.')
|
||||
current = mapping
|
||||
for key in keys:
|
||||
self.assertIsInstance(
|
||||
current, dict,
|
||||
f"Expected a dict at '{'.'.join(keys[:keys.index(key)])}' in {context}"
|
||||
)
|
||||
self.assertIn(
|
||||
key, current,
|
||||
f"Missing key '{key}' in path '{dotted_path}' under {context}"
|
||||
)
|
||||
current = current[key]
|
||||
|
||||
def _validate_path(self, app_id, dotted_path, occs):
|
||||
"""
|
||||
Validate that dotted_path exists under defaults_applications,
|
||||
or if path starts with 'users.' and not found there, under default_users.
|
||||
"""
|
||||
entry_app = self.defaults_app.get(app_id, {})
|
||||
try:
|
||||
# First, try in application defaults
|
||||
self._assert_nested_key(entry_app, dotted_path, app_id)
|
||||
return
|
||||
except AssertionError:
|
||||
# Only if path begins with users., check default_users next
|
||||
if dotted_path.startswith('users.'):
|
||||
subpath = dotted_path.split('.', 1)[1]
|
||||
try:
|
||||
self._assert_nested_key(self.defaults_users, subpath, 'default_users')
|
||||
return
|
||||
except AssertionError:
|
||||
pass
|
||||
# If still not found, fail with original context
|
||||
file_path, lineno = occs[0]
|
||||
self.fail(
|
||||
f"'{dotted_path}' not found for application '{app_id}' nor in default_users; "
|
||||
f"called at {file_path}:{lineno}"
|
||||
)
|
||||
|
||||
def test_literal_ids_have_all_requested_paths(self):
|
||||
"""Each literal application_id used must have entries for each requested path."""
|
||||
for app_id, paths in sorted(self.literal_paths_by_id.items()):
|
||||
with self.subTest(application_id=app_id):
|
||||
self.assertIn(
|
||||
app_id, self.defaults_app,
|
||||
f"Application '{app_id}' not found under defaults_applications in {self.app_config_path}"
|
||||
)
|
||||
for dotted_path, occs in sorted(paths.items()):
|
||||
with self.subTest(path=dotted_path):
|
||||
self._validate_path(app_id, dotted_path, occs)
|
||||
|
||||
def test_variable_ids_require_some_app_for_each_path(self):
|
||||
"""
|
||||
If dynamic application_id is used, at least one application or default_users
|
||||
must define each dynamic path.
|
||||
"""
|
||||
if not self.variable_paths:
|
||||
self.skipTest("No dynamic get_app_conf calls found.")
|
||||
for dotted_path, occs in sorted(self.variable_paths.items()):
|
||||
with self.subTest(dynamic_path=dotted_path):
|
||||
valid = False
|
||||
# Special: images.* dynamic paths only require 'images' to exist
|
||||
if dotted_path.startswith('images.'):
|
||||
for aid, cfg in self.defaults_app.items():
|
||||
if 'images' in cfg and isinstance(cfg['images'], dict):
|
||||
valid = True
|
||||
break
|
||||
if valid:
|
||||
continue # skip deeper check for this path
|
||||
# First, check each application default for full path
|
||||
for aid, cfg in self.defaults_app.items():
|
||||
try:
|
||||
self._assert_nested_key(cfg, dotted_path, aid)
|
||||
valid = True
|
||||
break
|
||||
except AssertionError:
|
||||
continue
|
||||
# If not found in any application, and path startswith 'users.', try default_users
|
||||
if not valid and dotted_path.startswith('users.'):
|
||||
subpath = dotted_path.split('.', 1)[1]
|
||||
try:
|
||||
self._assert_nested_key(self.defaults_users, subpath, 'default_users')
|
||||
valid = True
|
||||
except AssertionError:
|
||||
pass
|
||||
if not valid:
|
||||
file_path, lineno = occs[0]
|
||||
self.fail(
|
||||
f"No entry defines '{dotted_path}' in defaults_applications or default_users; "
|
||||
f"called at {file_path}:{lineno}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Loading…
x
Reference in New Issue
Block a user