From 4fec10d1ee624d68e8a3fcd5df31d157ff6c33c3 Mon Sep 17 00:00:00 2001 From: codex Date: Tue, 21 Apr 2026 03:34:35 -0300 Subject: [PATCH] test(ariadne): cover Kubernetes exec edges --- ariadne/k8s/exec.py | 13 ++------- tests/test_k8s_exec.py | 64 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/ariadne/k8s/exec.py b/ariadne/k8s/exec.py index 8627210..2b5d2ba 100644 --- a/ariadne/k8s/exec.py +++ b/ariadne/k8s/exec.py @@ -9,10 +9,7 @@ try: from kubernetes import client, config from kubernetes.stream import stream except Exception as exc: # pragma: no cover - import checked at runtime - client = None - config = None - stream = None - _IMPORT_ERROR = exc + client, config, stream, _IMPORT_ERROR = None, None, None, exc else: _IMPORT_ERROR = None @@ -72,13 +69,7 @@ class PodExecutor: self._label_selector = label_selector self._container = container - def exec( - self, - command: list[str] | str, - env: dict[str, str] | None = None, - timeout_sec: float | None = None, - check: bool = True, - ) -> ExecResult: + def exec(self, command: list[str] | str, env: dict[str, str] | None = None, timeout_sec: float | None = None, check: bool = True) -> ExecResult: pod = select_pod(self._namespace, self._label_selector) cmd = _build_command(command, env) api = _ensure_client() diff --git a/tests/test_k8s_exec.py b/tests/test_k8s_exec.py index dc678be..1f5a6bd 100644 --- a/tests/test_k8s_exec.py +++ b/tests/test_k8s_exec.py @@ -1,5 +1,8 @@ from __future__ import annotations +import builtins +import importlib.util +import sys import types import pytest @@ -57,11 +60,25 @@ class HangingStream(DummyStream): return False +class ReturnCodeStream(DummyStream): + def __init__(self): + super().__init__(stdout="fallback", stderr="", exit_code=0) + self.returncode = 7 + + def is_open(self) -> bool: + return False + + def peek_exit_code(self): + raise AssertionError("closed streams should not read exit code") + + def test_build_command_wraps_env() -> None: cmd = _build_command(["echo", "hello"], {"FOO": "bar"}) assert cmd[0] == "/bin/sh" assert "export FOO=bar" in cmd[2] + assert _build_command("echo hello", None) == ["/bin/sh", "-c", "echo hello"] + def test_exec_returns_output(monkeypatch) -> None: monkeypatch.setattr(exec_module, "select_pod", lambda *_args, **_kwargs: PodRef("pod", "ns")) @@ -94,6 +111,17 @@ def test_exec_times_out(monkeypatch) -> None: executor.exec(["sleep", "10"], timeout_sec=0.0, check=False) +def test_exec_uses_returncode_when_stream_has_no_exit_code(monkeypatch) -> None: + monkeypatch.setattr(exec_module, "select_pod", lambda *_args, **_kwargs: PodRef("pod", "ns")) + monkeypatch.setattr(exec_module, "_ensure_client", lambda: types.SimpleNamespace(connect_get_namespaced_pod_exec=None)) + monkeypatch.setattr(exec_module, "stream", lambda *args, **kwargs: ReturnCodeStream()) + + result = PodExecutor("ns", "app=test", None).exec("echo ok", check=False) + + assert result.exit_code == 7 + assert result.ok is False + + def test_ensure_client_fallback(monkeypatch) -> None: dummy_api = object() monkeypatch.setattr(exec_module, "_CORE_API", None) @@ -115,3 +143,39 @@ def test_ensure_client_fallback(monkeypatch) -> None: monkeypatch.setattr(exec_module, "client", types.SimpleNamespace(CoreV1Api=lambda: dummy_api)) assert exec_module._ensure_client() is dummy_api + + +def test_ensure_client_cached_and_import_error(monkeypatch) -> None: + cached = object() + monkeypatch.setattr(exec_module, "_IMPORT_ERROR", None) + monkeypatch.setattr(exec_module, "_CORE_API", cached) + assert exec_module._ensure_client() is cached + + error = RuntimeError("missing kubernetes") + monkeypatch.setattr(exec_module, "_IMPORT_ERROR", error) + monkeypatch.setattr(exec_module, "_CORE_API", None) + with pytest.raises(RuntimeError, match="kubernetes client missing"): + exec_module._ensure_client() + + +def test_exec_module_import_error_fallback(monkeypatch) -> None: + real_import = builtins.__import__ + + def fake_import(name, globals=None, locals=None, fromlist=(), level=0): + if name == "kubernetes" or name.startswith("kubernetes."): + raise RuntimeError("kubernetes unavailable") + return real_import(name, globals, locals, fromlist, level) + + module_name = "ariadne.k8s.exec_import_failure_probe" + spec = importlib.util.spec_from_file_location(module_name, exec_module.__file__) + assert spec and spec.loader + module = importlib.util.module_from_spec(spec) + monkeypatch.setattr(builtins, "__import__", fake_import) + monkeypatch.setitem(sys.modules, module_name, module) + + spec.loader.exec_module(module) + + assert module.client is None + assert module.config is None + assert module.stream is None + assert isinstance(module._IMPORT_ERROR, RuntimeError)