mirror of
https://github.com/Katakate/k7.git
synced 2026-09-22 10:03:18 +00:00
76a8139774
HTTPS-by-default for k7-api, cluster-wide Cilium isolation, first-class --docker on Kata and k7d, and RuntimeClass k7-fc. Playbook pins k7d 0.6.0.
210 lines
7.8 KiB
Python
210 lines
7.8 KiB
Python
"""Unit tests for the k7_sdk client (sync + async).
|
|
|
|
These tests verify URL/body construction and response unwrapping for the
|
|
pause/resume/fork SDK additions. The HTTP layer is mocked so
|
|
the tests run without a live API.
|
|
"""
|
|
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from k7_sdk.client import AsyncClient, Client, SandboxProxy
|
|
|
|
|
|
def test_sync_client_accepts_ca_path(tmp_path):
|
|
ca = tmp_path / "ca.crt"
|
|
ca.write_text("dummy")
|
|
c = Client(endpoint="https://api.test", api_key="k", verify_ssl=str(ca))
|
|
assert c.session.verify == str(ca)
|
|
|
|
|
|
def test_sync_client_refuses_verify_false_on_https():
|
|
with pytest.raises(ValueError, match="not allowed for https"):
|
|
Client(endpoint="https://api.test", api_key="k", verify_ssl=False)
|
|
|
|
|
|
def test_sync_client_allows_verify_false_on_http():
|
|
c = Client(endpoint="http://api.test", api_key="k", verify_ssl=False)
|
|
assert c.session.verify is False
|
|
|
|
|
|
def _mock_response(payload: dict, status_code: int = 200) -> MagicMock:
|
|
r = MagicMock()
|
|
r.status_code = status_code
|
|
r.json.return_value = payload
|
|
r.raise_for_status.return_value = None
|
|
return r
|
|
|
|
|
|
# --- Client (sync) -----------------------------------------------------------
|
|
|
|
|
|
class TestSyncClientPause:
|
|
def test_pause_default_args(self):
|
|
c = Client(endpoint="http://api.test", api_key="k")
|
|
with patch.object(
|
|
c.session,
|
|
"post",
|
|
return_value=_mock_response({"data": {"message": "ok"}}),
|
|
) as post:
|
|
out = c.pause("demo")
|
|
assert out == {"message": "ok"}
|
|
post.assert_called_once()
|
|
url, kwargs = post.call_args[0][0], post.call_args[1]
|
|
assert url == "http://api.test/api/v1/sandboxes/demo/pause"
|
|
assert kwargs["json"] == {"namespace": "default"}
|
|
|
|
def test_pause_with_snapshot(self):
|
|
c = Client(endpoint="http://api.test", api_key="k")
|
|
with patch.object(
|
|
c.session,
|
|
"post",
|
|
return_value=_mock_response({"data": {"message": "ok"}}),
|
|
) as post:
|
|
c.pause("demo", namespace="ns", snapshot="snap1")
|
|
body = post.call_args[1]["json"]
|
|
assert body == {"namespace": "ns", "snapshot": "snap1"}
|
|
|
|
def test_pause_does_not_accept_legacy_kwargs(self):
|
|
"""pvc and snapshot_class were removed from the SDK surface."""
|
|
import inspect
|
|
|
|
sig = inspect.signature(Client.pause)
|
|
assert "pvc" not in sig.parameters
|
|
assert "snapshot_class" not in sig.parameters
|
|
|
|
|
|
class TestSyncClientResume:
|
|
def test_resume_posts_namespace(self):
|
|
c = Client(endpoint="http://api.test", api_key="k")
|
|
with patch.object(
|
|
c.session,
|
|
"post",
|
|
return_value=_mock_response({"data": {"message": "ok"}}),
|
|
) as post:
|
|
out = c.resume("demo", namespace="ns")
|
|
assert out == {"message": "ok"}
|
|
assert post.call_args[0][0] == "http://api.test/api/v1/sandboxes/demo/resume"
|
|
assert post.call_args[1]["json"] == {"namespace": "ns"}
|
|
|
|
|
|
class TestSyncClientFork:
|
|
def test_fork_returns_proxy(self):
|
|
c = Client(endpoint="http://api.test", api_key="k")
|
|
with patch.object(
|
|
c.session,
|
|
"post",
|
|
return_value=_mock_response({"data": {"name": "child", "source": "demo"}}),
|
|
) as post:
|
|
proxy = c.fork("demo", "child", namespace="ns", snapshot="snap1")
|
|
assert isinstance(proxy, SandboxProxy)
|
|
assert proxy.name == "child"
|
|
assert proxy.namespace == "ns"
|
|
assert proxy._client is c
|
|
assert post.call_args[0][0] == "http://api.test/api/v1/sandboxes/demo/fork"
|
|
assert post.call_args[1]["json"] == {
|
|
"new_name": "child",
|
|
"namespace": "ns",
|
|
"snapshot": "snap1",
|
|
}
|
|
|
|
def test_fork_without_snapshot(self):
|
|
c = Client(endpoint="http://api.test", api_key="k")
|
|
with patch.object(
|
|
c.session,
|
|
"post",
|
|
return_value=_mock_response({"data": {"name": "child"}}),
|
|
) as post:
|
|
c.fork("demo", "child")
|
|
body = post.call_args[1]["json"]
|
|
assert body == {"new_name": "child", "namespace": "default"}
|
|
assert "snapshot" not in body
|
|
|
|
|
|
# --- SandboxProxy ergonomics ------------------------------------------------
|
|
|
|
|
|
class TestSandboxProxyOps:
|
|
def test_pause_delegates_to_client(self):
|
|
c = Client(endpoint="http://api.test", api_key="k")
|
|
proxy = SandboxProxy("demo", "ns", c)
|
|
with patch.object(c, "pause", return_value={"message": "ok"}) as m:
|
|
proxy.pause(snapshot="snap1")
|
|
m.assert_called_once_with("demo", namespace="ns", snapshot="snap1")
|
|
|
|
def test_resume_delegates_to_client(self):
|
|
c = Client(endpoint="http://api.test", api_key="k")
|
|
proxy = SandboxProxy("demo", "ns", c)
|
|
with patch.object(c, "resume", return_value={"message": "ok"}) as m:
|
|
proxy.resume()
|
|
m.assert_called_once_with("demo", namespace="ns")
|
|
|
|
def test_fork_delegates_and_returns_proxy(self):
|
|
c = Client(endpoint="http://api.test", api_key="k")
|
|
proxy = SandboxProxy("demo", "ns", c)
|
|
returned_proxy = SandboxProxy("child", "ns", c)
|
|
with patch.object(c, "fork", return_value=returned_proxy) as m:
|
|
out = proxy.fork("child")
|
|
assert out is returned_proxy
|
|
m.assert_called_once_with("demo", "child", namespace="ns", snapshot=None)
|
|
|
|
|
|
# --- AsyncClient -------------------------------------------------------------
|
|
|
|
|
|
class TestAsyncClient:
|
|
async def test_pause_builds_body(self):
|
|
ac = AsyncClient(endpoint="http://api.test", api_key="k")
|
|
try:
|
|
post = AsyncMock(return_value=_mock_response({"data": {"message": "ok"}}))
|
|
with patch.object(ac._client, "post", post):
|
|
out = await ac.pause("demo", namespace="ns", snapshot="snap")
|
|
assert out == {"message": "ok"}
|
|
assert post.call_args[0][0] == "/api/v1/sandboxes/demo/pause"
|
|
assert post.call_args[1]["json"] == {"namespace": "ns", "snapshot": "snap"}
|
|
finally:
|
|
await ac.aclose()
|
|
|
|
async def test_resume(self):
|
|
ac = AsyncClient(endpoint="http://api.test", api_key="k")
|
|
try:
|
|
post = AsyncMock(return_value=_mock_response({"data": {"message": "ok"}}))
|
|
with patch.object(ac._client, "post", post):
|
|
out = await ac.resume("demo")
|
|
assert out == {"message": "ok"}
|
|
assert post.call_args[0][0] == "/api/v1/sandboxes/demo/resume"
|
|
assert post.call_args[1]["json"] == {"namespace": "default"}
|
|
finally:
|
|
await ac.aclose()
|
|
|
|
async def test_fork_returns_data(self):
|
|
ac = AsyncClient(endpoint="http://api.test", api_key="k")
|
|
try:
|
|
post = AsyncMock(return_value=_mock_response({"data": {"name": "child", "source": "demo"}}, 201))
|
|
with patch.object(ac._client, "post", post):
|
|
out = await ac.fork("demo", "child")
|
|
assert out["name"] == "child"
|
|
assert out["source"] == "demo"
|
|
body = post.call_args[1]["json"]
|
|
assert body == {"new_name": "child", "namespace": "default"}
|
|
finally:
|
|
await ac.aclose()
|
|
|
|
|
|
# --- Smoke: AsyncClient construction requires httpx --------------------------
|
|
|
|
|
|
def test_async_client_refuses_verify_false_on_https():
|
|
with pytest.raises(ValueError, match="not allowed for https"):
|
|
AsyncClient(endpoint="https://api.test", api_key="k", verify_ssl=False)
|
|
|
|
|
|
def test_async_client_requires_httpx(monkeypatch):
|
|
"""When httpx is unavailable, AsyncClient raises RuntimeError loudly."""
|
|
import k7_sdk.client as mod
|
|
|
|
monkeypatch.setattr(mod, "httpx", None)
|
|
with pytest.raises(RuntimeError, match="httpx is required"):
|
|
AsyncClient(endpoint="http://api.test", api_key="k")
|