computer-playbook/tests/integration/test_domain_application_ids.py

82 lines
3.3 KiB
Python

import glob
import os
import re
import unittest
from filter_plugins.get_all_application_ids import get_all_application_ids
def collect_domain_keys(base_dir="."):
"""
Scan all YAML and Python files under the project for usages of domains.get('...') and domains['...']
and return a dict mapping each domain key to a list of file:line locations where it's used.
Ignores the integration test file itself, but will scan other test files.
"""
pattern = re.compile(r"domains(?:\.get\(\s*['\"](?P<id>[^'\"]+)['\"]\s*\)|\[['\"](?P<id2>[^'\"]+)['\"]\])")
locations = {}
# Path of this test file to ignore
ignore_path = os.path.normpath(os.path.join(base_dir, 'tests', 'integration', 'test_domain_application_ids.py'))
for root, dirs, files in os.walk(base_dir):
for fname in files:
# only scan YAML, YAML and Python files
if not fname.endswith(('.yml', '.yaml', '.py')):
continue
path = os.path.normpath(os.path.join(root, fname))
# skip this integration test file
if path == ignore_path:
continue
try:
with open(path, 'r', encoding='utf-8') as f:
for lineno, line in enumerate(f, start=1):
for m in pattern.finditer(line):
key = m.group('id') or m.group('id2')
loc = f"{path}:{lineno}"
locations.setdefault(key, []).append(loc)
except (OSError, UnicodeDecodeError):
continue
return locations
class TestDomainApplicationIds(unittest.TestCase):
@classmethod
def setUpClass(cls):
# Load valid application IDs from roles
cls.valid_ids = set(get_all_application_ids(roles_dir='roles'))
if not cls.valid_ids:
raise RuntimeError("No application_ids found in roles/*/vars/main.yml")
# Collect domain keys and their locations, excluding this test file
cls.domain_locations = collect_domain_keys(base_dir='.')
if not cls.domain_locations:
raise RuntimeError("No domains.get(...) or domains[...] usages found to validate")
# Define keys to ignore (placeholders or meta-fields)
cls.ignore_keys = {"canonical", "aliases"}
def test_all_keys_are_valid(self):
"""Ensure every domains.get/[] key matches a valid application_id (excluding ignored keys)."""
def is_placeholder(key):
# Treat keys with curly braces as placeholders
return bool(re.match(r"^\{.+\}$", key))
invalid = {}
for key, locs in self.domain_locations.items():
if key in self.ignore_keys or is_placeholder(key):
continue
if key not in self.valid_ids:
invalid[key] = locs
if invalid:
details = []
for key, locs in invalid.items():
locations_str = ", ".join(locs)
details.append(f"'{key}' at {locations_str}")
detail_msg = "; ".join(details)
self.fail(
f"Found usages of domains with invalid application_ids: {detail_msg}. "
f"Valid application_ids are: {sorted(self.valid_ids)}"
)
if __name__ == '__main__':
unittest.main()