"""Offline Compose rendering; never builds images or starts containers.""" import json import os from pathlib import Path import shutil import subprocess import unittest PROJECTS = Path(__file__).resolve().parents[3] CASES = { "VacatureRadar": ("app", ["docker-compose.unraid.yml"], { "MIGRATION_LOCAL_ROOT": "/migration-fixture/local", "VACATURERADAR_ENV_FILE": "/migration-fixture/runtime.env", }, {"/app/local", "/app/media", "/app/logs"}), "LumaOps": ("lumaops", ["docker-compose.yml", "docker-compose.unraid.yml"], { "MIGRATION_CONFIG_ROOT": "/migration-fixture/config", "MIGRATION_OPENRGB_ROOT": "/migration-fixture/openrgb", "MIGRATION_LUMA_CONFIG_ROOT": "/migration-fixture/luma-config", "MIGRATION_DATA_ROOT": "/migration-fixture/data", "MIGRATION_LOGS_ROOT": "/migration-fixture/logs", "MIGRATION_ENV_FILE": "/migration-fixture/runtime.env", }, {"/config", "/config/openrgb", "/config/lumaops", "/data", "/logs"}), } @unittest.skipUnless(shutil.which("docker"), "Docker Compose CLI is required; no daemon used") class ComposeTests(unittest.TestCase): def render(self, name, missing=None): _, files, variables, _ = CASES[name] environment = dict(os.environ) for key in list(environment): if key.startswith("MIGRATION_") or key.startswith("VACATURERADAR_"): environment.pop(key) environment.update({ "MIGRATION_IMAGE": "migration-fixture:candidate", "MIGRATION_NETWORK": name.lower() + "_default", "MIGRATION_ATTEMPT": "fixture-migration-attempt", "MIGRATION_SOURCE_REVISION": "a" * 40, "MIGRATION_SOURCE_TREE": "b" * 40, "MIGRATION_BUILD_ID": "a" * 12, "MIGRATION_BUILD_DATE": "2026-09-09T00:00:00.000Z", "LUMAOPS_ADMIN_TOKEN": "fixture-only-not-a-production-token-000000", **variables, }) if missing: environment.pop(missing) command = ["docker", "compose", "--env-file", os.devnull] for file in [*files, "docker-compose.migration.yml"]: command.extend(["-f", file]) command.extend(["config", "--no-env-resolution", "--no-path-resolution", "--format", "json"]) return subprocess.run(command, cwd=PROJECTS / name, env=environment, capture_output=True, text=True, timeout=20) def test_merged_mounts_are_exclusively_explicit_clones(self): for name, (service, _, _, destinations) in CASES.items(): with self.subTest(name=name): result = self.render(name) self.assertEqual(result.returncode, 0, "Fixture Compose rendering failed") config = json.loads(result.stdout) runtime = config["services"][service] self.assertEqual(runtime["network_mode"], name.lower() + "_default") self.assertEqual(config["name"], name.lower() + "-managed") self.assertEqual({m["target"] for m in runtime["volumes"]}, destinations) for mount in runtime["volumes"]: self.assertEqual(mount["type"], "bind") self.assertTrue(mount["source"].startswith("/migration-fixture/")) self.assertFalse(mount["bind"]["create_host_path"]) labels = runtime["build"]["labels"] self.assertEqual(labels["org.opencontainers.image.revision"], "a" * 40) self.assertEqual(labels["org.opencontainers.image.source-tree"], "b" * 40) self.assertEqual(labels["org.opencontainers.image.source"], "jens/" + name.lower()) def test_missing_required_inputs_fail_closed(self): for name, (_, _, variables, _) in CASES.items(): for variable in [*variables, "MIGRATION_NETWORK", "MIGRATION_IMAGE", "MIGRATION_ATTEMPT", "MIGRATION_SOURCE_REVISION", "MIGRATION_SOURCE_TREE", "MIGRATION_BUILD_ID", "MIGRATION_BUILD_DATE"]: with self.subTest(name=name, variable=variable): self.assertNotEqual(self.render(name, variable).returncode, 0) if __name__ == "__main__": unittest.main()