diff --git a/tests/test_compose_contract.py b/tests/test_compose_contract.py index 8eaf3b6..f2c39a6 100644 --- a/tests/test_compose_contract.py +++ b/tests/test_compose_contract.py @@ -16,16 +16,6 @@ COMPOSE_FILE = ROOT / "compose.yaml" DOCKERFILE = ROOT / "Dockerfile" COMPOSE_BIN = os.environ.get("COMPOSE_BIN", "/opt/data/tmp/docker-compose-birweb") SYNTHETIC_RESOURCE_UUID = "contract-test-resource" - -ROUTER_PRIORITY_LABELS = { - f"traefik.http.routers.http-0-{SYNTHETIC_RESOURCE_UUID}-site.priority": "500", - f"traefik.http.routers.https-0-{SYNTHETIC_RESOURCE_UUID}-site.priority": "500", -} -SOURCE_ROUTER_PRIORITY_TEMPLATES = { - "traefik.http.routers.http-0-${UUID}-site.priority=500", - "traefik.http.routers.https-0-${UUID}-site.priority=500", -} - DOCKERFILE_SHA256 = "748bd362b43475779d241074a1955ae880967c923a7f68c693cb1c67f8b242dd" BASE_IMAGE = ( "nginxinc/nginx-unprivileged:1.27-alpine@" @@ -176,44 +166,33 @@ class ComposeContract(unittest.TestCase): self.assertGreaterEqual(memory, 16 * 1024**2) self.assertLessEqual(memory, 512 * 1024**2) - def test_rendered_traefik_router_priorities_are_exact(self) -> None: - rendered_labels = self.service().get("labels") - self.assertIsInstance(rendered_labels, dict) - labels = rendered_labels if isinstance(rendered_labels, dict) else {} - self.assertEqual(labels, ROUTER_PRIORITY_LABELS) - self.assertTrue(all(isinstance(value, str) for value in labels.values())) - - def test_priority_labels_use_source_list_syntax_and_uuid_interpolation(self) -> None: - source_label_items = { - match.group(1) + def test_source_defines_no_traefik_labels(self) -> None: + source_label_names = { + match.group(1).strip("'\"") for match in re.finditer( - r'^\s*-\s*["\']?(traefik\.(?:http\.)?[^"\']+?)["\']?\s*$', + r'^\s*-\s*([^\s=]+)(?:=.*)?$', self.source, - re.MULTILINE, + re.IGNORECASE | re.MULTILINE, ) } - required_interpolation = re.compile( - r"\$\{COOLIFY_RESOURCE_UUID:\?[^}\r\n]+\}" - ) - normalized_items = { - required_interpolation.sub("${UUID}", item) - for item in source_label_items - } - self.assertEqual(normalized_items, SOURCE_ROUTER_PRIORITY_TEMPLATES) - self.assertTrue( - all(required_interpolation.search(item) for item in source_label_items), - "each Traefik label must use required COOLIFY_RESOURCE_UUID interpolation", - ) - self.assertNotIn(SYNTHETIC_RESOURCE_UUID, self.source) - - mapping_labels = [ - (key, line) + source_label_names.update( + key for _, key, line in source_mapping_keys(self.source) - if key.startswith("traefik.") - ] - self.assertEqual(mapping_labels, [], "Traefik labels must use Compose list syntax") + ) + traefik_labels = sorted( + name + for name in source_label_names + if re.search(r"(?:^|[._-])traefik(?:[._-]|$)", name, re.IGNORECASE) + ) + # Interpolating a label key is unsafe: Coolify preprocesses list labels + # into a mapping before Compose interpolation, leaving that key literal. + self.assertEqual( + traefik_labels, + [], + f"source Traefik labels are forbidden; Coolify owns them: {traefik_labels}", + ) - def test_resource_uuid_interpolation_fails_closed_when_missing_or_empty(self) -> None: + def test_compose_renders_without_resource_uuid(self) -> None: missing_env = os.environ.copy() missing_env.pop("COOLIFY_RESOURCE_UUID", None) empty_env = os.environ.copy() @@ -222,12 +201,11 @@ class ComposeContract(unittest.TestCase): for case, env in (("missing", missing_env), ("empty", empty_env)): with self.subTest(case=case): rendered = run(self.render_command, env=env) - self.assertNotEqual( + self.assertEqual( rendered.returncode, 0, - f"Compose config accepted a {case} COOLIFY_RESOURCE_UUID", + f"Compose config rejected a {case} COOLIFY_RESOURCE_UUID:\n{rendered.stderr}", ) - self.assertIn("COOLIFY_RESOURCE_UUID", rendered.stderr) def test_no_secrets_bind_mounts_or_forbidden_source_keys(self) -> None: service = self.service()