"""The real thing: a real helm, against a real cluster, installing a real chart. Everything else in this suite runs against a FakeProvisioner, which is what makes the worker tests take milliseconds. That trade has one cost, and this file is the payment: the fake proves the *worker* is correct, and cannot prove the *adapter* is. Argv order, the `--wait` flag, chart resolution, RBAC, whether `upgrade --install` is genuinely idempotent against a live release — none of it is exercised by a fake that returns None. So one test does it for real, exactly once. It is marked `e2e` and excluded from `make test` and from every pre-commit run; CI runs `-m e2e` in a job that has a cluster. kind create cluster --name svcforge uv run pytest -m e2e -q It skips (does not fail) with no cluster, because a laptop without kubectl is not a regression. """ from __future__ import annotations import asyncio import shutil import subprocess import uuid import pytest from svcforge_core.adapters.helm import HelmProvisioner from svcforge_core.domain.models import CatalogEntry, SizeSpec pytestmark = [pytest.mark.e2e, pytest.mark.slow] NAMESPACE = "svcforge-e2e" # A chart with no dependencies, no PVCs and no image pulls worth waiting on. The point is # to exercise the adapter, not to wait four minutes for Elasticsearch. ENTRY = CatalogEntry( service_type="podinfo", chart="oci://ghcr.io/stefanprodan/charts/podinfo", chart_version="6.7.1", sizes={"small": SizeSpec(replicas=1, resources={})}, ) def _cluster_reachable() -> bool: if not shutil.which("helm") or not shutil.which("kubectl"): return False out = subprocess.run( ["kubectl", "cluster-info"], capture_output=True, timeout=15, ) return out.returncode == 0 requires_cluster = pytest.mark.skipif( not _cluster_reachable(), reason="no reachable cluster: `kind create cluster --name svcforge`", ) @pytest.fixture(scope="module") def namespace() -> str: subprocess.run( ["kubectl", "create", "namespace", NAMESPACE], capture_output=True, check=False, # already exists is fine — the whole system is idempotent or it is broken ) return NAMESPACE @requires_cluster async def test_install_is_idempotent_against_a_real_cluster(namespace: str) -> None: """Install twice. Get one release. This is the claim the fake cannot make for us.""" release = f"e2e-podinfo-{uuid.uuid4().hex[:8]}" prov = HelmProvisioner(timeout_s=300) try: await prov.install(release=release, ns=namespace, entry=ENTRY, values={"replicaCount": 1}) releases = [r for r in await prov.list_releases() if r.name == release] assert len(releases) == 1, f"expected exactly one release, got {releases}" # The redelivery, for real: same task, same deterministic release name, run again. # `helm upgrade --install` must converge, not duplicate and not error. await prov.install(release=release, ns=namespace, entry=ENTRY, values={"replicaCount": 1}) releases = [r for r in await prov.list_releases() if r.name == release] assert len(releases) == 1, "second install created a second release: not idempotent" # --wait means the DB writing `ready` is telling the truth. out = subprocess.run( [ "kubectl", "get", "deploy", "-n", namespace, "-l", f"app.kubernetes.io/instance={release}", "-o", "jsonpath={.items[*].status.readyReplicas}", ], capture_output=True, text=True, timeout=30, ) assert out.stdout.strip() == "1", f"--wait returned before the pod was ready: {out.stdout!r}" finally: await prov.uninstall(release=release, ns=namespace) @requires_cluster async def test_uninstall_of_a_missing_release_is_not_an_error(namespace: str) -> None: """The desired state — no release — is already true. That is success, not failure.""" prov = HelmProvisioner(timeout_s=120) await prov.uninstall(release=f"never-existed-{uuid.uuid4().hex[:8]}", ns=namespace) @requires_cluster async def test_real_helm_timeout_leaves_no_helm_behind(namespace: str) -> None: """A deadline that does not kill helm is a deadline that lets helm keep mutating.""" prov = HelmProvisioner(timeout_s=1) # cannot possibly finish release = f"e2e-timeout-{uuid.uuid4().hex[:8]}" with pytest.raises((TimeoutError, Exception)): await prov.install(release=release, ns=namespace, entry=ENTRY, values={}) await asyncio.sleep(0.5) out = subprocess.run(["pgrep", "-f", f"helm.*{release}"], capture_output=True, text=True) assert out.stdout.strip() == "", "helm survived its own timeout and is still touching the cluster" await prov.uninstall(release=release, ns=namespace)