Updated SDK to match v0.1.1
This commit is contained in:
@ -7,6 +7,7 @@ import pytest
|
||||
import pytest_asyncio
|
||||
from typing_extensions import AsyncGenerator
|
||||
|
||||
from wrenn.capsule import Capsule
|
||||
from wrenn.client import AsyncWrennClient, WrennClient
|
||||
|
||||
WRENN_API_KEY = os.environ.get("WRENN_API_KEY")
|
||||
@ -61,7 +62,9 @@ def bearer_client() -> Generator[WrennClient, None, None]:
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def async_minimal_capsule(async_client: AsyncWrennClient):
|
||||
async def async_minimal_capsule(
|
||||
async_client: AsyncWrennClient,
|
||||
) -> AsyncGenerator[Capsule, None]:
|
||||
"""Provides a ready-to-use minimal capsule and cleans it up afterward."""
|
||||
cap = await async_client.capsules.create(template="minimal", timeout_sec=120)
|
||||
await cap.async_wait_ready(timeout=60, interval=1)
|
||||
@ -70,7 +73,9 @@ async def async_minimal_capsule(async_client: AsyncWrennClient):
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def async_python_capsule(async_client: AsyncWrennClient):
|
||||
async def async_python_capsule(
|
||||
async_client: AsyncWrennClient,
|
||||
) -> AsyncGenerator[Capsule, None]:
|
||||
"""Provides a ready-to-use Python interpreter capsule."""
|
||||
cap = await async_client.capsules.create(
|
||||
template="python-interpreter-v0-beta", timeout_sec=120
|
||||
@ -83,7 +88,7 @@ async def async_python_capsule(async_client: AsyncWrennClient):
|
||||
@pytest.fixture
|
||||
def minimal_capsule(
|
||||
client: WrennClient,
|
||||
) -> Generator[Any, None, None]: # Replace Any with your Capsule type
|
||||
) -> Generator[Capsule, None, None]:
|
||||
"""Provides a ready-to-use minimal capsule and cleans it up afterward."""
|
||||
with client.capsules.create(template="minimal", timeout_sec=120) as cap:
|
||||
cap.wait_ready(timeout=60, interval=1)
|
||||
|
||||
@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from wrenn.capsule import Capsule
|
||||
from wrenn.capsule import Capsule, ExecResult
|
||||
|
||||
from .conftest import requires_auth
|
||||
|
||||
@ -14,6 +14,7 @@ class TestAsyncCapsuleLifecycle:
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_create_exec_destroy(self, async_minimal_capsule: Capsule):
|
||||
result = await async_minimal_capsule.async_exec("echo", args=["async_hello"])
|
||||
assert isinstance(result, ExecResult)
|
||||
assert result.exit_code == 0
|
||||
assert "async_hello" in result.stdout
|
||||
|
||||
|
||||
@ -9,7 +9,9 @@ from wrenn.client import WrennClient
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
with WrennClient(api_key="wrn_test1234567890abcdef12345678") as c:
|
||||
with WrennClient(
|
||||
api_key="wrn_test1234567890abcdef12345678", token="jwt-test-token-abc123"
|
||||
) as c:
|
||||
yield c
|
||||
|
||||
|
||||
@ -81,14 +83,20 @@ class TestCapsuleHttpClient:
|
||||
def test_jwt_only_get_url_works(self):
|
||||
with WrennClient(token="jwt-abc") as c:
|
||||
cap = Capsule(id="cl-abc")
|
||||
cap._bind(c._http, str(c._http.base_url), api_key=None, token="jwt-abc")
|
||||
assert c._mgmt_http is not None
|
||||
cap._bind(
|
||||
c._mgmt_http, str(c._mgmt_http.base_url), api_key=None, token="jwt-abc"
|
||||
)
|
||||
url = cap.get_url(8888)
|
||||
assert "8888-cl-abc" in url
|
||||
|
||||
def test_jwt_only_http_client_has_bearer_header(self):
|
||||
with WrennClient(token="jwt-abc") as c:
|
||||
cap = Capsule(id="cl-abc")
|
||||
cap._bind(c._http, str(c._http.base_url), api_key=None, token="jwt-abc")
|
||||
assert c._mgmt_http is not None
|
||||
cap._bind(
|
||||
c._mgmt_http, str(c._mgmt_http.base_url), api_key=None, token="jwt-abc"
|
||||
)
|
||||
hc = cap.http_client
|
||||
assert hc.headers["Authorization"] == "Bearer jwt-abc"
|
||||
|
||||
@ -136,6 +144,7 @@ class TestCodeResult:
|
||||
error=None,
|
||||
)
|
||||
assert r.text == "84"
|
||||
assert r.data is not None
|
||||
assert r.data["text/plain"] == "84"
|
||||
|
||||
def test_error_result(self):
|
||||
@ -164,7 +173,6 @@ class TestJupyterMessageFormat:
|
||||
|
||||
class TestDeprecationWarnings:
|
||||
def test_import_sandbox_from_capsule_warns(self):
|
||||
import importlib
|
||||
import warnings
|
||||
|
||||
import wrenn.capsule as capsule_mod
|
||||
|
||||
@ -16,24 +16,29 @@ from wrenn.exceptions import (
|
||||
)
|
||||
from wrenn.models import (
|
||||
APIKeyResponse,
|
||||
AuthResponse,
|
||||
Capsule,
|
||||
CreateHostResponse,
|
||||
Host,
|
||||
SignupResponse,
|
||||
Status,
|
||||
Template,
|
||||
UsageResponse,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
with WrennClient(api_key="wrn_test1234567890abcdef12345678") as c:
|
||||
with WrennClient(
|
||||
api_key="wrn_test1234567890abcdef12345678", token="jwt-test-token-abc123"
|
||||
) as c:
|
||||
yield c
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def async_client():
|
||||
return AsyncWrennClient(api_key="wrn_test1234567890abcdef12345678")
|
||||
return AsyncWrennClient(
|
||||
api_key="wrn_test1234567890abcdef12345678", token="jwt-test-token-abc123"
|
||||
)
|
||||
|
||||
|
||||
class TestAuth:
|
||||
@ -41,17 +46,21 @@ class TestAuth:
|
||||
def test_signup(self, client):
|
||||
respx.post("https://api.wrenn.dev/v1/auth/signup").respond(
|
||||
201,
|
||||
json={
|
||||
"token": "jwt-token",
|
||||
"user_id": "u-1",
|
||||
"team_id": "t-1",
|
||||
"email": "a@b.com",
|
||||
},
|
||||
json={"message": "Account created. Check your email to activate."},
|
||||
)
|
||||
resp = client.auth.signup("a@b.com", "password123")
|
||||
assert isinstance(resp, AuthResponse)
|
||||
assert resp.token == "jwt-token"
|
||||
assert resp.user_id == "u-1"
|
||||
resp = client.auth.signup("a@b.com", "password123", "Test User")
|
||||
assert isinstance(resp, SignupResponse)
|
||||
assert resp.message is not None
|
||||
|
||||
@respx.mock
|
||||
def test_signup_no_creds(self):
|
||||
respx.post("https://api.wrenn.dev/v1/auth/signup").respond(
|
||||
201,
|
||||
json={"message": "Account created."},
|
||||
)
|
||||
with WrennClient() as c:
|
||||
resp = c.auth.signup("a@b.com", "password123", "Test User")
|
||||
assert isinstance(resp, SignupResponse)
|
||||
|
||||
@respx.mock
|
||||
def test_login(self, client):
|
||||
@ -146,6 +155,40 @@ class TestCapsules:
|
||||
client.capsules.destroy("sb-1")
|
||||
assert route.called
|
||||
|
||||
@respx.mock
|
||||
def test_usage(self, client):
|
||||
respx.get("https://api.wrenn.dev/v1/capsules/usage").respond(
|
||||
200,
|
||||
json={
|
||||
"from": "2026-03-21",
|
||||
"to": "2026-04-20",
|
||||
"points": [
|
||||
{
|
||||
"date": "2026-04-19",
|
||||
"cpu_minutes": 12.5,
|
||||
"ram_mb_minutes": 640.0,
|
||||
},
|
||||
{"date": "2026-04-20", "cpu_minutes": 8.0, "ram_mb_minutes": 512.0},
|
||||
],
|
||||
},
|
||||
)
|
||||
resp = client.capsules.usage()
|
||||
assert isinstance(resp, UsageResponse)
|
||||
assert resp.points is not None
|
||||
assert len(resp.points) == 2
|
||||
assert resp.points[0].cpu_minutes == 12.5
|
||||
|
||||
@respx.mock
|
||||
def test_usage_with_dates(self, client):
|
||||
route = respx.get("https://api.wrenn.dev/v1/capsules/usage").respond(
|
||||
200,
|
||||
json={"from": "2026-04-01", "to": "2026-04-15", "points": []},
|
||||
)
|
||||
client.capsules.usage(from_date="2026-04-01", to_date="2026-04-15")
|
||||
req = route.calls[0].request
|
||||
assert "from=2026-04-01" in str(req.url)
|
||||
assert "to=2026-04-15" in str(req.url)
|
||||
|
||||
|
||||
class TestSnapshots:
|
||||
@respx.mock
|
||||
@ -355,25 +398,92 @@ class TestErrorHandling:
|
||||
|
||||
|
||||
class TestAuthModes:
|
||||
def test_api_key_header(self):
|
||||
def test_api_key_only_creates_data_client(self):
|
||||
with WrennClient(api_key="wrn_test1234567890abcdef12345678") as c:
|
||||
assert c._http.headers["X-API-Key"] == "wrn_test1234567890abcdef12345678"
|
||||
assert c._data_http is not None
|
||||
assert (
|
||||
c._data_http.headers["X-API-Key"] == "wrn_test1234567890abcdef12345678"
|
||||
)
|
||||
assert c._mgmt_http is None
|
||||
|
||||
def test_token_header(self):
|
||||
def test_token_only_creates_mgmt_client(self):
|
||||
with WrennClient(token="jwt-token-abc") as c:
|
||||
assert c._http.headers["Authorization"] == "Bearer jwt-token-abc"
|
||||
assert c._mgmt_http is not None
|
||||
assert c._mgmt_http.headers["Authorization"] == "Bearer jwt-token-abc"
|
||||
assert c._data_http is None
|
||||
|
||||
def test_no_auth_raises(self):
|
||||
with pytest.raises(ValueError, match="Either api_key or token"):
|
||||
WrennClient()
|
||||
def test_no_auth_allowed(self):
|
||||
with WrennClient() as c:
|
||||
assert c._data_http is None
|
||||
assert c._mgmt_http is None
|
||||
assert c._public_http is not None
|
||||
|
||||
def test_both_creds_creates_both_clients(self):
|
||||
with WrennClient(
|
||||
api_key="wrn_test1234567890abcdef12345678", token="jwt-abc"
|
||||
) as c:
|
||||
assert c._data_http is not None
|
||||
assert c._mgmt_http is not None
|
||||
|
||||
def test_capsule_ops_require_api_key(self):
|
||||
with WrennClient(token="jwt-abc") as c:
|
||||
with pytest.raises(ValueError, match="API key"):
|
||||
c.capsules.list()
|
||||
|
||||
def test_snapshot_ops_require_api_key(self):
|
||||
with WrennClient(token="jwt-abc") as c:
|
||||
with pytest.raises(ValueError, match="API key"):
|
||||
c.snapshots.list()
|
||||
|
||||
def test_mgmt_ops_require_token(self):
|
||||
with WrennClient(api_key="wrn_test1234567890abcdef12345678") as c:
|
||||
with pytest.raises(ValueError, match="JWT token"):
|
||||
c.api_keys.list()
|
||||
with pytest.raises(ValueError, match="JWT token"):
|
||||
c.teams.list()
|
||||
with pytest.raises(ValueError, match="JWT token"):
|
||||
c.hosts.list()
|
||||
with pytest.raises(ValueError, match="JWT token"):
|
||||
c.channels.list()
|
||||
with pytest.raises(ValueError, match="JWT token"):
|
||||
c.users.search("a@b.com")
|
||||
with pytest.raises(ValueError, match="JWT token"):
|
||||
c.account.get()
|
||||
with pytest.raises(ValueError, match="JWT token"):
|
||||
c.auth.switch_team("team-1")
|
||||
|
||||
@respx.mock
|
||||
def test_jwt_auth_on_api_keys(self):
|
||||
def test_mgmt_sends_bearer_only(self):
|
||||
route = respx.get("https://api.wrenn.dev/v1/api-keys").respond(200, json=[])
|
||||
with WrennClient(token="jwt-abc") as c:
|
||||
with WrennClient(
|
||||
api_key="wrn_test1234567890abcdef12345678", token="jwt-abc"
|
||||
) as c:
|
||||
c.api_keys.list()
|
||||
req = route.calls[0].request
|
||||
assert req.headers["Authorization"] == "Bearer jwt-abc"
|
||||
assert "X-API-Key" not in req.headers
|
||||
|
||||
@respx.mock
|
||||
def test_data_sends_api_key_only(self):
|
||||
route = respx.get("https://api.wrenn.dev/v1/capsules").respond(200, json=[])
|
||||
with WrennClient(
|
||||
api_key="wrn_test1234567890abcdef12345678", token="jwt-abc"
|
||||
) as c:
|
||||
c.capsules.list()
|
||||
req = route.calls[0].request
|
||||
assert req.headers["X-API-Key"] == "wrn_test1234567890abcdef12345678"
|
||||
assert "Authorization" not in req.headers
|
||||
|
||||
@respx.mock
|
||||
def test_public_sends_no_auth(self):
|
||||
route = respx.post("https://api.wrenn.dev/v1/auth/signup").respond(
|
||||
201, json={"message": "ok"}
|
||||
)
|
||||
with WrennClient() as c:
|
||||
c.auth.signup("a@b.com", "password123", "Test")
|
||||
req = route.calls[0].request
|
||||
assert "X-API-Key" not in req.headers
|
||||
assert "Authorization" not in req.headers
|
||||
|
||||
|
||||
class TestAsyncClient:
|
||||
|
||||
Reference in New Issue
Block a user