From 8000f76457a902357d56bc75a86f06fbdebc0b3e Mon Sep 17 00:00:00 2001 From: RubenRWU Date: Thu, 15 Oct 2026 16:19:01 +0200 Subject: [PATCH] Mittwald-/Login-API: Weiterleitungen folgen (wie requests); nicht gefolgte 3xx-Antwort ist ein Fehler statt leerer Erfolg Co-Authored-By: Claude Sonnet 5.5 --- app/services/mittwald.py | 4 ++-- app/services/onyva_login.py | 2 +- tests/test_core_contract.py | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/services/mittwald.py b/app/services/mittwald.py index 831cc92..ec1d73e 100644 --- a/app/services/mittwald.py +++ b/app/services/mittwald.py @@ -33,7 +33,7 @@ class Mittwald: @staticmethod def _real_http(method, url, headers, body): - r = httpx.request(method, url, headers=headers, json=body, timeout=60) + r = httpx.request(method, url, headers=headers, json=body, timeout=60, follow_redirects=True) # wie `requests` im alten deploy.py try: data = r.json() if r.content else None except ValueError: @@ -56,7 +56,7 @@ class Mittwald: raise DryRunSkip() raise DeployError("MITTWALD_API_TOKEN nicht gesetzt.") status, data, text = self._http(method, f"{self.base}{path}", {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json"}, body) - if status >= 400: + if status >= 300: # 3xx (nicht gefolgte Weiterleitung) darf nie als Erfolg mit leerem Body durchgehen msg = f"{method} {path} → HTTP {status}: {text}" if fatal: raise DeployError(msg) diff --git a/app/services/onyva_login.py b/app/services/onyva_login.py index ce7ea66..267c879 100644 --- a/app/services/onyva_login.py +++ b/app/services/onyva_login.py @@ -62,7 +62,7 @@ class OnyvaLogin: @staticmethod def _real(method, url, headers, body): - r = httpx.request(method, url, headers=headers, json=body, timeout=30) + r = httpx.request(method, url, headers=headers, json=body, timeout=30, follow_redirects=True) try: data = r.json() if r.content else None except ValueError: diff --git a/tests/test_core_contract.py b/tests/test_core_contract.py index 328a02b..22d2de8 100644 --- a/tests/test_core_contract.py +++ b/tests/test_core_contract.py @@ -492,3 +492,22 @@ def test_cortecs_key_from_admin_env_counts_as_set_and_reaches_the_container(clie assert deploy.build_env(db, t, reveal=True)["CORTECS_API_KEY"] == "ck-aus-env" assert "ck-aus-env" not in str(deploy.build_env(db, t, reveal=False)) assert "aus Admin-Env" in deploy.build_env(db, t, reveal=False)["CORTECS_API_KEY"] + + +def test_http_clients_follow_redirects_and_3xx_is_never_success(monkeypatch): + import httpx + from app.services import mittwald as mwmod, onyva_login + from app.services.mittwald import DeployError, Mittwald + seen = [] + + class R: + status_code, content, text = 200, b"[]", "[]" + def json(self): return [] + + monkeypatch.setattr(httpx, "request", lambda *a, **k: (seen.append(k), R())[1]) + Mittwald._real_http("GET", "https://x/v2/stacks/", {}, None) + onyva_login.OnyvaLogin._real("GET", "https://x/api/v1/apps", {}, None) + assert all(k.get("follow_redirects") is True for k in seen) and len(seen) == 2 + # Eine nicht gefolgte 308-Weiterleitung (leerer Body) ist ein Fehler, kein „null“ + with pytest.raises(DeployError, match="308"): + Mittwald("t", "https://api.test/v2", False, lambda m: None, lambda *a: (308, None, "")).api("GET", "/projects/p/stacks/", write=False)