Fail safed more parts of the code

This commit is contained in:
2025-07-12 21:35:33 +02:00
parent 066b4d59d6
commit ead60dab84
24 changed files with 493 additions and 102 deletions

View File

@@ -0,0 +1,51 @@
#!/usr/bin/env python3
import os
import unittest
import yaml
from filter_plugins.get_all_application_ids import get_all_application_ids
class TestApplicationIDsInPorts(unittest.TestCase):
def test_all_ports_application_ids_are_valid(self):
# Path to the ports definition file
ports_file = os.path.abspath(
os.path.join(
os.path.dirname(__file__), '..', '..', 'group_vars', 'all', '09_ports.yml'
)
)
with open(ports_file, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
# Collect all referenced application IDs under ports.hosttype.porttype
refs = set()
ports = data.get('ports', {}) or {}
for hosttype, porttypes in ports.items():
if not isinstance(porttypes, dict):
continue
for porttype, apps in porttypes.items():
if not isinstance(apps, dict):
continue
for app_id in apps.keys():
refs.add(app_id)
# Retrieve valid application IDs from Ansible roles
valid_ids = set(get_all_application_ids())
# Identify IDs that are neither valid nor have a valid prefix before the first underscore
missing = []
for app_id in refs:
if app_id in valid_ids:
continue
prefix = app_id.split('_', 1)[0]
if prefix in valid_ids:
continue
missing.append(app_id)
if missing:
self.fail(
f"Undefined application IDs in ports definition: {', '.join(sorted(missing))}"
)
if __name__ == '__main__':
unittest.main()

View File

@@ -9,10 +9,15 @@ class TestAnsibleRolesMetadata(unittest.TestCase):
@classmethod
def setUpClass(cls):
if not os.path.isdir(cls.ROLES_DIR):
raise unittest.SkipTest(f"Roles directory not found at {cls.ROLES_DIR}")
cls.roles = [d for d in os.listdir(cls.ROLES_DIR)
if os.path.isdir(os.path.join(cls.ROLES_DIR, d))]
all_dirs = os.listdir(cls.ROLES_DIR)
cls.roles = [
d for d in all_dirs
if (
os.path.isdir(os.path.join(cls.ROLES_DIR, d))
and d != '__pycache__'
)
]
def test_each_role_has_valid_meta(self):
"""

View File

@@ -1,51 +1,74 @@
import os
import unittest
import yaml
import glob
import yaml
import warnings
import unittest
class TestApplicationIdMatchesRoleName(unittest.TestCase):
def test_application_id_matches_role_directory(self):
"""
Warn if application_id in vars/main.yml does not match
the role directory basename, to avoid confusion.
If vars/main.yml is missing, do nothing.
"""
# locate the 'roles' directory (two levels up)
# import your filters
from filter_plugins.invokable_paths import get_invokable_paths, get_non_invokable_paths
class TestApplicationIdAndInvocability(unittest.TestCase):
@classmethod
def setUpClass(cls):
# locate roles dir (two levels up)
base_dir = os.path.dirname(__file__)
roles_dir = os.path.abspath(os.path.join(base_dir, '..', '..', 'roles'))
cls.roles_dir = os.path.abspath(os.path.join(base_dir, '..', '..', 'roles'))
# iterate over each role folder
for role_path in glob.glob(os.path.join(roles_dir, '*')):
# get lists of invokable and non-invokable role *names*
# filters return dash-joined paths; for top-level roles names are just the basename
cls.invokable = {
p.split('-', 1)[0]
for p in get_invokable_paths()
}
cls.non_invokable = {
p.split('-', 1)[0]
for p in get_non_invokable_paths()
}
def test_application_id_presence_and_match(self):
"""
- Invokable roles must have application_id defined (else fail).
- Non-invokable roles must NOT have application_id (else fail).
- If application_id exists but != folder name, warn and recommend aligning.
"""
for role_path in glob.glob(os.path.join(self.roles_dir, '*')):
if not os.path.isdir(role_path):
continue
role_name = os.path.basename(role_path)
vars_main = os.path.join(role_path, 'vars', 'main.yml')
# skip roles without vars/main.yml
if not os.path.exists(vars_main):
continue
with open(vars_main, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f) or {}
# load vars/main.yml if it exists
data = {}
if os.path.exists(vars_main):
with open(vars_main, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f) or {}
app_id = data.get('application_id')
if app_id is None:
if role_name in self.invokable:
# must have application_id
if app_id is None:
self.fail(f"{role_name}: invokable role is missing 'application_id' in vars/main.yml")
elif role_name in self.non_invokable:
# must NOT have application_id
if app_id is not None:
self.fail(f"{role_name}: non-invokable role should not define 'application_id' in vars/main.yml")
else:
# roles not mentioned in categories.yml? we'll skip them
continue
# if present but mismatched, warn
if app_id is not None and app_id != role_name:
warnings.warn(
f"{role_name}: 'application_id' is missing in vars/main.yml. "
f"Consider setting it to '{role_name}' to avoid confusion."
)
elif app_id != role_name:
warnings.warn(
f"{role_name}: 'application_id' is '{app_id}', "
f"but the folder name is '{role_name}'. "
"This can lead to confusion—using the directory name "
"as the application_id is recommended."
f"{role_name}: 'application_id' is '{app_id}',"
f" but the folder name is '{role_name}'."
" Consider setting application_id to exactly the role folder name to avoid confusion."
)
# always pass
# if we get here, all presence/absence checks passed
self.assertTrue(True)
if __name__ == '__main__':
unittest.main()

View File

@@ -20,7 +20,8 @@ class TestDependencyApplicationId(unittest.TestCase):
vars_file = os.path.join(role_path, 'vars', 'main.yml')
if not os.path.isfile(vars_file):
return None
data = yaml.safe_load(open(vars_file, encoding='utf-8')) or {}
with open(vars_file, encoding='utf-8') as f:
data = yaml.safe_load(f) or {}
return data.get('application_id')
# Iterate all roles
@@ -33,7 +34,9 @@ class TestDependencyApplicationId(unittest.TestCase):
if not os.path.isfile(meta_file):
continue
meta = yaml.safe_load(open(meta_file, encoding='utf-8')) or {}
with open(meta_file, encoding='utf-8') as f:
meta = yaml.safe_load(f) or {}
deps = meta.get('dependencies', [])
if not isinstance(deps, list):
continue

View File

@@ -0,0 +1,46 @@
import os
import re
import sys
import unittest
# Ensure filter_plugins is on the path
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
sys.path.insert(0, PROJECT_ROOT)
from filter_plugins.get_all_application_ids import get_all_application_ids
class TestGetDomainApplicationIds(unittest.TestCase):
"""
Integration test to verify that all string literals passed to get_domain()
correspond to valid application_id values defined in roles/*/vars/main.yml.
"""
GET_DOMAIN_PATTERN = re.compile(r"get_domain\(\s*['\"]([^'\"]+)['\"]\s*\)")
def test_get_domain_literals_are_valid_ids(self):
# Collect all application IDs from roles
valid_ids = set(get_all_application_ids())
# Walk through project files
invalid_usages = []
for root, dirs, files in os.walk(PROJECT_ROOT):
# Skip tests directory to avoid matching in test code
if 'tests' in root.split(os.sep):
continue
for fname in files:
if not fname.endswith('.py'):
continue
path = os.path.join(root, fname)
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
for match in self.GET_DOMAIN_PATTERN.finditer(content):
literal = match.group(1)
if literal not in valid_ids:
invalid_usages.append((path, literal))
if invalid_usages:
msgs = [f"{path}: '{lit}' is not a valid application_id" for path, lit in invalid_usages]
self.fail("Found invalid get_domain() usages:\n" + "\n".join(msgs))
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env python3
import os
import unittest
class TestRolesFolderNames(unittest.TestCase):
def test_no_underscore_in_role_folder_names(self):
"""
Integration test that verifies none of the folders under 'roles' contain an underscore in their name,
ignoring the '__pycache__' folder.
"""
# Determine the absolute path to the 'roles' directory
roles_dir = os.path.abspath(
os.path.join(
os.path.dirname(__file__), '..', '..', 'roles'
)
)
# List all entries in the roles directory
try:
entries = os.listdir(roles_dir)
except FileNotFoundError:
self.fail(f"Roles directory not found at expected location: {roles_dir}")
# Identify any role folders containing underscores, excluding '__pycache__'
invalid = []
for name in entries:
# Skip the '__pycache__' directory
if name == '__pycache__':
continue
path = os.path.join(roles_dir, name)
if os.path.isdir(path) and '_' in name:
invalid.append(name)
# Fail the test if any invalid folder names are found
if invalid:
self.fail(
f"Role folder names must not contain underscores: {', '.join(sorted(invalid))}"
)
if __name__ == '__main__':
unittest.main()