mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-08-26 21:45:20 +02:00
- Added detect_env_file() to both sys-ctl-rpr-docker-soft and sys-ctl-rpr-docker-hard * prefer .env, fallback to .env/env * append --env-file parameter automatically - Refactored soft script to use compose_cmd() for consistent command building - Adjusted error recovery path in soft script to also respect env-file - Extended unit tests for soft script to cover env-file priority and restart commands - Added new unit tests for hard script verifying env-file priority, cwd handling, and --only filter logic Ref: https://chatgpt.com/share/68ad7b30-7510-800f-8172-56f03a2f40f5
141 lines
4.8 KiB
Python
141 lines
4.8 KiB
Python
import unittest
|
|
import sys
|
|
from pathlib import Path
|
|
from importlib.util import spec_from_file_location, module_from_spec
|
|
|
|
|
|
def load_script_module():
|
|
"""
|
|
Import the script under test from roles/sys-ctl-rpr-docker-hard/files/script.py
|
|
"""
|
|
test_file = Path(__file__).resolve()
|
|
repo_root = test_file.parents[5] # .../tests/unit/roles/sys-ctl-rpr-docker-hard/files -> repo root
|
|
script_path = repo_root / "roles" / "sys-ctl-rpr-docker-hard" / "files" / "script.py"
|
|
if not script_path.exists():
|
|
raise FileNotFoundError(f"script.py not found at {script_path}")
|
|
spec = spec_from_file_location("rpr_hard_script", str(script_path))
|
|
mod = module_from_spec(spec)
|
|
assert spec.loader is not None
|
|
spec.loader.exec_module(mod) # type: ignore[attr-defined]
|
|
return mod
|
|
|
|
|
|
class TestRepairDockerHard(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.script = load_script_module()
|
|
|
|
def test_detect_env_file_priority(self):
|
|
s = self.script
|
|
base = "/proj"
|
|
old_isfile = s.os.path.isfile
|
|
try:
|
|
# only .env
|
|
s.os.path.isfile = lambda p: p == f"{base}/.env"
|
|
self.assertEqual(s.detect_env_file(base), f"{base}/.env")
|
|
|
|
# only .env/env
|
|
s.os.path.isfile = lambda p: p == f"{base}/.env/env"
|
|
self.assertEqual(s.detect_env_file(base), f"{base}/.env/env")
|
|
|
|
# both -> prefer .env
|
|
s.os.path.isfile = lambda p: p in (f"{base}/.env", f"{base}/.env/env")
|
|
self.assertEqual(s.detect_env_file(base), f"{base}/.env")
|
|
|
|
# none
|
|
s.os.path.isfile = lambda p: False
|
|
self.assertIsNone(s.detect_env_file(base))
|
|
finally:
|
|
s.os.path.isfile = old_isfile
|
|
|
|
def test_hard_restart_uses_envfile_and_cwd(self):
|
|
s = self.script
|
|
calls = []
|
|
|
|
def fake_run(cmd, cwd=None, check=None):
|
|
calls.append({"cmd": cmd, "cwd": cwd, "check": check})
|
|
class R: pass
|
|
return R()
|
|
|
|
old_run = s.subprocess.run
|
|
old_detect = s.detect_env_file
|
|
try:
|
|
s.subprocess.run = fake_run
|
|
s.detect_env_file = lambda d: f"{d}/.env/env" # erzwinge .env/env
|
|
|
|
s.hard_restart_docker_services("/X/APP")
|
|
|
|
# Wir erwarten zwei Aufrufe: docker-compose --env-file ... down / up -d
|
|
self.assertEqual(len(calls), 2)
|
|
self.assertEqual(calls[0]["cwd"], "/X/APP")
|
|
self.assertEqual(calls[1]["cwd"], "/X/APP")
|
|
# down
|
|
self.assertIn("docker-compose", calls[0]["cmd"])
|
|
self.assertIn("--env-file", calls[0]["cmd"])
|
|
self.assertIn("/X/APP/.env/env", calls[0]["cmd"])
|
|
self.assertIn("down", calls[0]["cmd"])
|
|
# up -d
|
|
self.assertIn("docker-compose", calls[1]["cmd"])
|
|
self.assertIn("--env-file", calls[1]["cmd"])
|
|
self.assertIn("/X/APP/.env/env", calls[1]["cmd"])
|
|
self.assertIn("up", calls[1]["cmd"])
|
|
self.assertIn("-d", calls[1]["cmd"])
|
|
finally:
|
|
s.subprocess.run = old_run
|
|
s.detect_env_file = old_detect
|
|
|
|
def test_main_scans_parent_and_filters_only(self):
|
|
s = self.script
|
|
seen = {"scandir": [], "called": []}
|
|
|
|
class FakeDirEntry:
|
|
def __init__(self, path, is_dir=True):
|
|
self.path = path
|
|
self._is_dir = is_dir
|
|
def is_dir(self):
|
|
return self._is_dir
|
|
|
|
def fake_scandir(parent):
|
|
seen["scandir"].append(parent)
|
|
return [
|
|
FakeDirEntry(f"{parent}/app1"),
|
|
FakeDirEntry(f"{parent}/app2"),
|
|
FakeDirEntry(f"{parent}/notdir", is_dir=False),
|
|
]
|
|
|
|
def fake_isdir(p):
|
|
return p == "/PARENT"
|
|
|
|
def fake_isfile(p):
|
|
# Nur app2 hat docker-compose.yml
|
|
return p in ("/PARENT/app2/docker-compose.yml",)
|
|
|
|
def fake_hard_restart(dir_path):
|
|
seen["called"].append(dir_path)
|
|
|
|
old_scandir = s.os.scandir
|
|
old_isdir = s.os.path.isdir
|
|
old_isfile = s.os.path.isfile
|
|
old_restart = s.hard_restart_docker_services
|
|
try:
|
|
s.os.scandir = fake_scandir
|
|
s.os.path.isdir = fake_isdir
|
|
s.os.path.isfile = fake_isfile
|
|
s.hard_restart_docker_services = fake_hard_restart
|
|
|
|
# Mit --only app2 -> nur app2 wird aufgerufen
|
|
sys_argv = sys.argv
|
|
sys.argv = ["x", "/PARENT", "--only", "app2"]
|
|
s.main()
|
|
self.assertEqual(seen["called"], ["/PARENT/app2"])
|
|
finally:
|
|
s.os.scandir = old_scandir
|
|
s.os.path.isdir = old_isdir
|
|
s.os.path.isfile = old_isfile
|
|
s.hard_restart_docker_services = old_restart
|
|
sys.argv = sys_argv
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|