mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-08-29 23:08:06 +02:00
Reorgenized test structure and added validation of inventory before deployment
This commit is contained in:
21
tests/integration/test_check_init_files.py
Normal file
21
tests/integration/test_check_init_files.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
class TestInitFiles(unittest.TestCase):
|
||||
def test_all_test_dirs_have_init(self):
|
||||
"""
|
||||
Ensure every subdirectory in the 'tests' folder (excluding '__pycache__') contains an '__init__.py' file.
|
||||
"""
|
||||
tests_root = Path(__file__).resolve().parents[2] / "tests"
|
||||
|
||||
for path in tests_root.rglob("*"):
|
||||
if path.is_dir() and "__pycache__" not in path.parts:
|
||||
init_file = path / "__init__.py"
|
||||
with self.subTest(directory=str(path.relative_to(tests_root))):
|
||||
self.assertTrue(
|
||||
init_file.exists(),
|
||||
f"Missing __init__.py in directory: {path}"
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
0
tests/unit/cli/__init__.py
Normal file
0
tests/unit/cli/__init__.py
Normal file
@@ -5,7 +5,7 @@ from unittest import mock
|
||||
|
||||
# Ensure cli module is importable
|
||||
dir_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), '../../cli')
|
||||
os.path.join(os.path.dirname(__file__), '../../../cli')
|
||||
)
|
||||
sys.path.insert(0, dir_path)
|
||||
|
@@ -43,7 +43,7 @@ class TestGenerateDefaultApplicationsUsers(unittest.TestCase):
|
||||
When a users.yml exists with defined users, the script should inject a 'users'
|
||||
mapping in the generated YAML, mapping each username to a Jinja2 reference.
|
||||
"""
|
||||
script_path = Path(__file__).resolve().parents[2] / "cli" / "generate_applications.py"
|
||||
script_path = Path(__file__).resolve().parents[3] / "cli" / "generate_applications.py"
|
||||
result = subprocess.run([
|
||||
"python3", str(script_path),
|
||||
"--roles-dir", str(self.roles_dir),
|
@@ -29,7 +29,7 @@ class TestGenerateDefaultApplications(unittest.TestCase):
|
||||
shutil.rmtree(self.temp_dir)
|
||||
|
||||
def test_script_generates_expected_yaml(self):
|
||||
script_path = Path(__file__).resolve().parent.parent.parent / "cli" / "generate_applications.py"
|
||||
script_path = Path(__file__).resolve().parent.parent.parent.parent / "cli" / "generate_applications.py"
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
@@ -7,7 +7,7 @@ import shutil
|
||||
import yaml
|
||||
|
||||
# Adjust path to include cli/ folder
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..", "cli")))
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../..", "cli")))
|
||||
|
||||
from generate_playbook import build_dependency_graph, topological_sort, generate_playbook_entries
|
||||
|
@@ -7,7 +7,7 @@ import yaml
|
||||
from collections import OrderedDict
|
||||
|
||||
# Add cli/ to import path
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..", "cli")))
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../..", "cli")))
|
||||
|
||||
import generate_users
|
||||
|
@@ -10,7 +10,7 @@ from unittest.mock import patch
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "../../cli")
|
||||
os.path.join(os.path.dirname(__file__), "../../../cli")
|
||||
),
|
||||
)
|
||||
|
134
tests/unit/cli/test_validate_inventory.py
Normal file
134
tests/unit/cli/test_validate_inventory.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import unittest
|
||||
import tempfile
|
||||
import shutil
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
SCRIPT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../cli/validate_inventory.py"))
|
||||
|
||||
class TestValidateInventory(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.group_vars_all = Path(self.temp_dir) / "group_vars" / "all"
|
||||
self.group_vars_all.mkdir(parents=True)
|
||||
|
||||
self.inventory_dir = Path(self.temp_dir) / "inventory"
|
||||
self.inventory_dir.mkdir()
|
||||
|
||||
# Create default applications file
|
||||
self.default_applications = {
|
||||
"defaults_applications": {
|
||||
"app1": {
|
||||
"port": 8080,
|
||||
"enabled": True,
|
||||
"settings": {
|
||||
"theme": "dark"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(self.group_vars_all / "01_applications.yml").write_text(
|
||||
yaml.dump(self.default_applications), encoding="utf-8"
|
||||
)
|
||||
|
||||
# Create default users file
|
||||
self.default_users = {
|
||||
"default_users": {
|
||||
"alice": {
|
||||
"email": "alice@example.com",
|
||||
"role": "admin"
|
||||
}
|
||||
}
|
||||
}
|
||||
(self.group_vars_all / "01_users.yml").write_text(
|
||||
yaml.dump(self.default_users), encoding="utf-8"
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.temp_dir)
|
||||
|
||||
def run_script(self, expected_code=0):
|
||||
result = subprocess.run(
|
||||
[sys.executable, SCRIPT_PATH, str(self.inventory_dir)],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
encoding="utf-8",
|
||||
cwd=self.temp_dir
|
||||
)
|
||||
if result.returncode != expected_code:
|
||||
print("STDOUT:", result.stdout)
|
||||
print("STDERR:", result.stderr)
|
||||
return result
|
||||
|
||||
def test_valid_inventory(self):
|
||||
(self.inventory_dir / "group_vars.yml").write_text(yaml.dump({
|
||||
"applications": {
|
||||
"app1": {
|
||||
"port": 8080,
|
||||
"enabled": True,
|
||||
"settings": {
|
||||
"theme": "dark"
|
||||
}
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"alice": {
|
||||
"email": "alice@example.com",
|
||||
"role": "admin",
|
||||
"password": "secret"
|
||||
}
|
||||
}
|
||||
}), encoding="utf-8")
|
||||
|
||||
result = self.run_script(expected_code=0)
|
||||
self.assertIn("Inventory directory is valid against defaults", result.stdout)
|
||||
|
||||
def test_unknown_user_warning(self):
|
||||
(self.inventory_dir / "invalid_users.yml").write_text(yaml.dump({
|
||||
"users": {
|
||||
"bob": {
|
||||
"email": "bob@example.com",
|
||||
"role": "user"
|
||||
}
|
||||
}
|
||||
}), encoding="utf-8")
|
||||
|
||||
result = self.run_script(expected_code=0)
|
||||
self.assertIn("Warning", result.stderr)
|
||||
|
||||
def test_missing_user_key_fails(self):
|
||||
(self.inventory_dir / "invalid_key.yml").write_text(yaml.dump({
|
||||
"users": {
|
||||
"alice": {
|
||||
"email": "alice@example.com",
|
||||
"role": "admin",
|
||||
"extra": "unexpected"
|
||||
}
|
||||
}
|
||||
}), encoding="utf-8")
|
||||
|
||||
result = self.run_script(expected_code=1)
|
||||
self.assertIn("Missing default for user 'alice': key 'extra'", result.stderr)
|
||||
|
||||
def test_missing_application_key_fails(self):
|
||||
(self.inventory_dir / "missing_key.yml").write_text(yaml.dump({
|
||||
"applications": {
|
||||
"app1": {
|
||||
"port": 8080,
|
||||
"enabled": True,
|
||||
"settings": {
|
||||
"theme": "dark"
|
||||
},
|
||||
"extra_setting": True
|
||||
}
|
||||
}
|
||||
}), encoding="utf-8")
|
||||
|
||||
result = self.run_script(expected_code=1)
|
||||
self.assertIn("Missing default for app1: extra_setting", result.stdout)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
0
tests/unit/filter_plugins/__init__.py
Normal file
0
tests/unit/filter_plugins/__init__.py
Normal file
@@ -6,7 +6,7 @@ from ansible.errors import AnsibleFilterError
|
||||
|
||||
# ensure filter_plugins is on the path
|
||||
dir_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), '../../filter_plugins')
|
||||
os.path.join(os.path.dirname(__file__), '../../../filter_plugins')
|
||||
)
|
||||
sys.path.insert(0, dir_path)
|
||||
|
@@ -6,7 +6,7 @@ import unittest
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "../../roles/docker-matrix")
|
||||
os.path.join(os.path.dirname(__file__), "../../../roles/docker-matrix")
|
||||
),
|
||||
)
|
||||
|
@@ -7,7 +7,7 @@ import os
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "../../")
|
||||
os.path.join(os.path.dirname(__file__), "../../../")
|
||||
),
|
||||
)
|
||||
|
@@ -7,7 +7,7 @@ import os
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "../../")
|
||||
os.path.join(os.path.dirname(__file__), "../../../")
|
||||
),
|
||||
)
|
||||
|
@@ -5,7 +5,7 @@ import sys
|
||||
import unittest
|
||||
|
||||
# Add filter_plugins/ to the import path
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..", "filter_plugins")))
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../..", "filter_plugins")))
|
||||
|
||||
from docker_image import FilterModule
|
||||
|
@@ -4,7 +4,7 @@ import unittest
|
||||
|
||||
# Add the filter_plugins directory to the import path
|
||||
dir_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), '../../filter_plugins')
|
||||
os.path.join(os.path.dirname(__file__), '../../../filter_plugins')
|
||||
)
|
||||
sys.path.insert(0, dir_path)
|
||||
|
@@ -5,7 +5,7 @@ import os
|
||||
# Ensure filter_plugins directory is on the path
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), '../../filter_plugins'))
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../filter_plugins'))
|
||||
)
|
||||
|
||||
from generate_all_domains import FilterModule
|
@@ -5,7 +5,7 @@ import os
|
||||
# Ensure filter_plugins directory is on the path
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), '../filter_plugins'))
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), '../../filter_plugins'))
|
||||
)
|
||||
|
||||
from generate_base_sld_domains import FilterModule
|
@@ -4,7 +4,7 @@ import unittest
|
||||
|
||||
# Add the filter_plugins directory to the import path
|
||||
dir_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), '../../filter_plugins')
|
||||
os.path.join(os.path.dirname(__file__), '../../../filter_plugins')
|
||||
)
|
||||
sys.path.insert(0, dir_path)
|
||||
|
@@ -4,7 +4,7 @@ import unittest
|
||||
|
||||
# Add the filter_plugins directory to the import path
|
||||
dir_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), '../../filter_plugins')
|
||||
os.path.join(os.path.dirname(__file__), '../../../filter_plugins')
|
||||
)
|
||||
sys.path.insert(0, dir_path)
|
||||
|
@@ -7,7 +7,7 @@ import os
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), '../../filter_plugins')
|
||||
os.path.join(os.path.dirname(__file__), '../../../filter_plugins')
|
||||
)
|
||||
)
|
||||
|
@@ -5,7 +5,7 @@ from unittest.mock import patch, mock_open
|
||||
from ansible.errors import AnsibleFilterError
|
||||
|
||||
# make sure our plugin is on PYTHONPATH
|
||||
root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../filter_plugins'))
|
||||
root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../filter_plugins'))
|
||||
sys.path.insert(0, root)
|
||||
|
||||
import load_configuration
|
@@ -5,7 +5,7 @@ import unittest
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "../../")
|
||||
os.path.join(os.path.dirname(__file__), "../../../")
|
||||
),
|
||||
)
|
||||
|
@@ -5,7 +5,7 @@ import os
|
||||
# Ensure filter_plugins directory is on the path
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), '../../filter_plugins'))
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../filter_plugins'))
|
||||
)
|
||||
|
||||
from safe_join import safe_join
|
@@ -5,7 +5,7 @@ import os
|
||||
# Ensure filter_plugins directory is on the path
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), '../../filter_plugins'))
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../filter_plugins'))
|
||||
)
|
||||
|
||||
from safe import safe_placeholders
|
@@ -6,7 +6,7 @@ from jinja2 import Undefined
|
||||
# Ensure filter_plugins directory is on the path
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), '../../filter_plugins'))
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../filter_plugins'))
|
||||
)
|
||||
|
||||
from safe import FilterModule
|
0
tests/unit/lookup_plugins/__init__.py
Normal file
0
tests/unit/lookup_plugins/__init__.py
Normal file
@@ -6,7 +6,7 @@ import unittest
|
||||
import yaml
|
||||
|
||||
dir_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), '../../lookup_plugins')
|
||||
os.path.join(os.path.dirname(__file__), '../../../lookup_plugins')
|
||||
)
|
||||
sys.path.insert(0, dir_path)
|
||||
|
@@ -5,7 +5,7 @@ import shutil
|
||||
import unittest
|
||||
|
||||
# Adjust the PYTHONPATH to include the lookup_plugins folder from the docker-portfolio role.
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../roles/docker-portfolio/lookup_plugins'))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../../roles/docker-portfolio/lookup_plugins'))
|
||||
|
||||
from docker_cards import LookupModule
|
||||
|
0
tests/unit/module_utils/__init__.py
Normal file
0
tests/unit/module_utils/__init__.py
Normal file
@@ -4,17 +4,10 @@ import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
# Add module_utils/ to the import path
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"../../..",
|
||||
"module_utils",
|
||||
)
|
||||
),
|
||||
)
|
||||
# Add the project root/module_utils to the import path
|
||||
CURRENT_DIR = os.path.dirname(__file__)
|
||||
PROJECT_ROOT = os.path.abspath(os.path.join(CURRENT_DIR, "../../.."))
|
||||
sys.path.insert(0, PROJECT_ROOT)
|
||||
|
||||
from module_utils.cert_utils import CertUtils
|
||||
|
0
tests/unit/roles/__init__.py
Normal file
0
tests/unit/roles/__init__.py
Normal file
@@ -8,7 +8,7 @@ import sys
|
||||
def load_optimizer_module():
|
||||
module_path = os.path.abspath(os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'..', "..", 'roles', 'system-storage-optimizer', 'files', 'system-storage-optimizer.py'
|
||||
'..', "..", "..","..",'roles', 'system-storage-optimizer', 'files', 'system-storage-optimizer.py'
|
||||
))
|
||||
spec = importlib.util.spec_from_file_location('storage_optimizer', module_path)
|
||||
optimizer = importlib.util.module_from_spec(spec)
|
Reference in New Issue
Block a user