"""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()