import os import glob import yaml import warnings import unittest # 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__) cls.roles_dir = os.path.abspath(os.path.join(base_dir, '..', '..', 'roles')) # 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') # 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 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 '{app_id}'," f" but the folder name is '{role_name}'." " Consider setting application_id to exactly the role folder name to avoid confusion." ) # if we get here, all presence/absence checks passed self.assertTrue(True) if __name__ == '__main__': unittest.main()