mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-08-29 15:06:26 +02:00
In between commit domain restruturing
This commit is contained in:
55
tests/integration/test_domain_uniqueness.py
Normal file
55
tests/integration/test_domain_uniqueness.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import unittest
|
||||
import yaml
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from collections import Counter
|
||||
|
||||
class TestDomainUniqueness(unittest.TestCase):
|
||||
def test_no_duplicate_domains(self):
|
||||
"""
|
||||
Load the applications YAML (generating it via `make build` if missing),
|
||||
collect all entries under domains.canonical and domains.aliases across all applications,
|
||||
and assert that no domain appears more than once.
|
||||
"""
|
||||
repo_root = Path(__file__).resolve().parents[2]
|
||||
yaml_file = repo_root / 'group_vars' / 'all' / '03_applications.yml'
|
||||
|
||||
# Generate the file if it doesn't exist
|
||||
if not yaml_file.exists():
|
||||
subprocess.run(['make', 'build'], cwd=repo_root, check=True)
|
||||
|
||||
# Load the applications configuration
|
||||
cfg = yaml.safe_load(yaml_file.read_text(encoding='utf-8')) or {}
|
||||
apps = cfg.get('defaults_applications', {})
|
||||
|
||||
all_domains = []
|
||||
for app_name, app_cfg in apps.items():
|
||||
domains_cfg = app_cfg.get('domains', {})
|
||||
|
||||
# canonical entries may be a list or a mapping
|
||||
canonical = domains_cfg.get('canonical', [])
|
||||
if isinstance(canonical, dict):
|
||||
values = list(canonical.values())
|
||||
else:
|
||||
values = canonical or []
|
||||
all_domains.extend(values)
|
||||
|
||||
# aliases entries may be a list or a mapping
|
||||
aliases = domains_cfg.get('aliases', [])
|
||||
if isinstance(aliases, dict):
|
||||
values = list(aliases.values())
|
||||
else:
|
||||
values = aliases or []
|
||||
all_domains.extend(values)
|
||||
|
||||
# Filter out any empty or non-string entries
|
||||
domain_list = [d for d in all_domains if isinstance(d, str) and d.strip()]
|
||||
counts = Counter(domain_list)
|
||||
|
||||
# Find duplicates
|
||||
duplicates = [domain for domain, count in counts.items() if count > 1]
|
||||
if duplicates:
|
||||
self.fail(f"Duplicate domain entries found: {duplicates}\n (May 'make build' solves this issue.)")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
89
tests/integration/test_domains_structure.py
Normal file
89
tests/integration/test_domains_structure.py
Normal file
@@ -0,0 +1,89 @@
|
||||
import os
|
||||
import yaml
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from collections import Counter
|
||||
|
||||
ROLES_DIR = Path(__file__).resolve().parent.parent.parent / "roles"
|
||||
|
||||
class TestDomainsStructure(unittest.TestCase):
|
||||
def test_domains_keys_types_and_uniqueness(self):
|
||||
"""Ensure that under 'domains' only 'canonical' and 'aliases' keys exist,
|
||||
'aliases' is a list of strings, 'canonical' is either a list of strings
|
||||
or a dict with string values, and no domain is defined more than once
|
||||
across all roles."""
|
||||
failed_roles = []
|
||||
all_domains = []
|
||||
|
||||
for role_path in ROLES_DIR.iterdir():
|
||||
if not role_path.is_dir():
|
||||
continue
|
||||
vars_dir = role_path / "vars"
|
||||
if not vars_dir.exists():
|
||||
continue
|
||||
|
||||
for vars_file in vars_dir.glob("*.yml"):
|
||||
try:
|
||||
with open(vars_file, 'r') as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
except yaml.YAMLError as e:
|
||||
failed_roles.append((role_path.name, vars_file.name, f"YAML error: {e}"))
|
||||
continue
|
||||
|
||||
if 'domains' not in data:
|
||||
continue
|
||||
|
||||
domains = data['domains']
|
||||
if not isinstance(domains, dict):
|
||||
failed_roles.append((role_path.name, vars_file.name, "'domains' should be a dict"))
|
||||
continue
|
||||
|
||||
# Check allowed keys
|
||||
allowed_keys = {'canonical', 'aliases'}
|
||||
extra_keys = set(domains.keys()) - allowed_keys
|
||||
if extra_keys:
|
||||
failed_roles.append((role_path.name, vars_file.name,
|
||||
f"Unexpected keys in 'domains': {extra_keys}"))
|
||||
|
||||
# Validate and collect 'aliases'
|
||||
if 'aliases' in domains:
|
||||
aliases = domains['aliases']
|
||||
if not isinstance(aliases, list) or not all(isinstance(item, str) for item in aliases):
|
||||
failed_roles.append((role_path.name, vars_file.name,
|
||||
"'aliases' must be a list of strings"))
|
||||
else:
|
||||
all_domains.extend(aliases)
|
||||
|
||||
# Validate and collect 'canonical'
|
||||
if 'canonical' in domains:
|
||||
canonical = domains['canonical']
|
||||
if isinstance(canonical, list):
|
||||
if not all(isinstance(item, str) for item in canonical):
|
||||
failed_roles.append((role_path.name, vars_file.name,
|
||||
"'canonical' list items must be strings"))
|
||||
else:
|
||||
all_domains.extend(canonical)
|
||||
elif isinstance(canonical, dict):
|
||||
if not all(isinstance(k, str) and isinstance(v, str) for k, v in canonical.items()):
|
||||
failed_roles.append((role_path.name, vars_file.name,
|
||||
"All keys and values in 'canonical' dict must be strings"))
|
||||
else:
|
||||
all_domains.extend(canonical.values())
|
||||
else:
|
||||
failed_roles.append((role_path.name, vars_file.name,
|
||||
"'canonical' must be a list or a dict"))
|
||||
|
||||
# Check for duplicate domains across all roles
|
||||
duplicates = [domain for domain, count in Counter(all_domains).items() if count > 1]
|
||||
if duplicates:
|
||||
failed_roles.append(("GLOBAL", "", f"Duplicate domain entries found: {duplicates}"))
|
||||
|
||||
if failed_roles:
|
||||
messages = []
|
||||
for role, file, reason in failed_roles:
|
||||
entry = f"{role}/{file}: {reason}" if file else f"{role}: {reason}"
|
||||
messages.append(entry)
|
||||
self.fail("Domain structure errors found:\n" + "\n".join(messages))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Reference in New Issue
Block a user