network/cnmaestro/tests/test_client.py
2026-05-09 16:54:28 -05:00

87 lines
2.7 KiB
Python

"""Tests for HTTP client (mocked)."""
import httpx
import pytest
import respx
from cnmaestro.client import CnMaestroClient, CnMaestroError
BASE = "https://example.test"
@pytest.fixture
def client(monkeypatch):
monkeypatch.setenv("CNMAESTRO_BASE_URL", BASE)
monkeypatch.setenv("CNMAESTRO_CLIENT_ID", "id")
monkeypatch.setenv("CNMAESTRO_CLIENT_SECRET", "secret")
c = CnMaestroClient()
yield c
c.close()
@respx.mock
def test_token_fetched_on_first_call(client):
respx.post(f"{BASE}/api/v2/access/token").mock(
return_value=httpx.Response(200, json={"access_token": "T1"})
)
respx.get(f"{BASE}/api/v2/devices").mock(
return_value=httpx.Response(200, json={"data": [], "paging": {"total": 0}})
)
list(client.paginated("/api/v2/devices"))
assert client._token == "T1"
@respx.mock
def test_401_triggers_refresh(client):
token_route = respx.post(f"{BASE}/api/v2/access/token").mock(
side_effect=[
httpx.Response(200, json={"access_token": "T1"}),
httpx.Response(200, json={"access_token": "T2"}),
]
)
respx.get(f"{BASE}/api/v2/devices").mock(
side_effect=[
httpx.Response(401),
httpx.Response(200, json={"data": [], "paging": {"total": 0}}),
]
)
list(client.paginated("/api/v2/devices"))
assert token_route.call_count == 2
assert client._token == "T2"
@respx.mock
def test_paginated_walks_offsets(client):
respx.post(f"{BASE}/api/v2/access/token").mock(
return_value=httpx.Response(200, json={"access_token": "T"})
)
page1 = [{"mac": f"M{i}"} for i in range(100)]
page2 = [{"mac": f"M{i}"} for i in range(100, 150)]
respx.get(f"{BASE}/api/v2/devices", params={"limit": 100, "offset": 0}).mock(
return_value=httpx.Response(200, json={"data": page1, "paging": {"total": 150}})
)
respx.get(f"{BASE}/api/v2/devices", params={"limit": 100, "offset": 100}).mock(
return_value=httpx.Response(200, json={"data": page2, "paging": {"total": 150}})
)
items = list(client.paginated("/api/v2/devices"))
assert len(items) == 150
@respx.mock
def test_delete_propagates_error(client):
respx.post(f"{BASE}/api/v2/access/token").mock(
return_value=httpx.Response(200, json={"access_token": "T"})
)
respx.delete(f"{BASE}/api/v2/devices/AA:BB").mock(
return_value=httpx.Response(404, text="not found")
)
with pytest.raises(CnMaestroError):
client.delete("/api/v2/devices/AA:BB")
def test_missing_credentials_raises(monkeypatch):
monkeypatch.delenv("CNMAESTRO_CLIENT_ID", raising=False)
monkeypatch.delenv("CNMAESTRO_CLIENT_SECRET", raising=False)
with pytest.raises(CnMaestroError):
CnMaestroClient()