Skip to content

Commit 5dcb15b

Browse files
author
alex-omophub
committed
Refactor tests for API key validation and enhance request handling tests. Updated synchronous and asynchronous client tests to use monkeypatching for API key checks. Added new tests for handling raw requests, including error parsing, rate limits, and JSON decoding issues.
1 parent 0a64db9 commit 5dcb15b

2 files changed

Lines changed: 168 additions & 18 deletions

File tree

tests/unit/test_client.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,12 @@
1313
class TestOMOPHubClient:
1414
"""Tests for the synchronous OMOPHub client."""
1515

16-
def test_client_requires_api_key(self) -> None:
16+
def test_client_requires_api_key(self, monkeypatch: pytest.MonkeyPatch) -> None:
1717
"""Test that client raises error without API key."""
18-
# Clear any module-level API key
19-
original_key = omophub.api_key
20-
omophub.api_key = None
18+
monkeypatch.setattr("omophub._client.default_api_key", None)
2119

22-
try:
23-
with pytest.raises(AuthenticationError):
24-
OMOPHub()
25-
finally:
26-
omophub.api_key = original_key
20+
with pytest.raises(AuthenticationError):
21+
OMOPHub()
2722

2823
def test_client_accepts_api_key(self, api_key: str) -> None:
2924
"""Test that client accepts API key parameter."""
@@ -93,16 +88,12 @@ def test_async_client_has_resources(
9388
assert hasattr(async_client, "vocabularies")
9489
assert hasattr(async_client, "domains")
9590

96-
def test_async_client_requires_api_key(self) -> None:
91+
def test_async_client_requires_api_key(self, monkeypatch: pytest.MonkeyPatch) -> None:
9792
"""Test that async client raises error without API key."""
98-
original_key = omophub.api_key
99-
omophub.api_key = None
100-
101-
try:
102-
with pytest.raises(AuthenticationError):
103-
omophub.AsyncOMOPHub()
104-
finally:
105-
omophub.api_key = original_key
93+
monkeypatch.setattr("omophub._client.default_api_key", None)
94+
95+
with pytest.raises(AuthenticationError):
96+
omophub.AsyncOMOPHub()
10697

10798

10899
class TestClientLazyPropertyCaching:

tests/unit/test_request.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,83 @@ def test_url_building(self) -> None:
260260
# Test without leading slash
261261
assert request._build_url("concepts") == "https://api.example.com/v1/concepts"
262262

263+
def test_get_raw_request(self, request_handler: Request) -> None:
264+
"""Test get_raw returns full response with data and meta."""
265+
with respx.mock:
266+
respx.get("https://api.example.com/v1/search").mock(
267+
return_value=Response(
268+
200,
269+
json={
270+
"success": True,
271+
"data": {"concepts": [{"concept_id": 1}]},
272+
"meta": {"pagination": {"page": 1, "total_pages": 5}},
273+
},
274+
)
275+
)
276+
result = request_handler.get_raw("/search")
277+
assert "data" in result
278+
assert "meta" in result
279+
assert result["meta"]["pagination"]["page"] == 1
280+
assert result["meta"]["pagination"]["total_pages"] == 5
281+
282+
def test_get_raw_with_params(self, request_handler: Request) -> None:
283+
"""Test get_raw passes query parameters correctly."""
284+
with respx.mock:
285+
route = respx.get("https://api.example.com/v1/search").mock(
286+
return_value=Response(
287+
200,
288+
json={
289+
"data": {"concepts": []},
290+
"meta": {"pagination": {"page": 2, "has_next": True}},
291+
},
292+
)
293+
)
294+
295+
result = request_handler.get_raw("/search", params={"query": "test", "page": 2})
296+
297+
url_str = str(route.calls[0].request.url)
298+
assert "query=test" in url_str
299+
assert "page=2" in url_str
300+
assert result["meta"]["pagination"]["page"] == 2
301+
302+
def test_get_raw_error_parsing(self, request_handler: Request) -> None:
303+
"""Test get_raw raises errors correctly."""
304+
with respx.mock:
305+
respx.get("https://api.example.com/v1/test").mock(
306+
return_value=Response(
307+
404,
308+
json={"error": {"message": "Not found"}},
309+
headers={"X-Request-Id": "req_123"},
310+
)
311+
)
312+
with pytest.raises(NotFoundError) as exc_info:
313+
request_handler.get_raw("/test")
314+
assert exc_info.value.request_id == "req_123"
315+
316+
def test_get_raw_rate_limit(self, request_handler: Request) -> None:
317+
"""Test get_raw handles rate limit with retry-after."""
318+
with respx.mock:
319+
respx.get("https://api.example.com/v1/test").mock(
320+
return_value=Response(
321+
429,
322+
json={"error": {"message": "Rate limited"}},
323+
headers={"Retry-After": "45"},
324+
)
325+
)
326+
with pytest.raises(RateLimitError) as exc_info:
327+
request_handler.get_raw("/test")
328+
assert exc_info.value.retry_after == 45
329+
330+
def test_get_raw_json_decode_error(self, request_handler: Request) -> None:
331+
"""Test get_raw handles invalid JSON."""
332+
with respx.mock:
333+
respx.get("https://api.example.com/v1/test").mock(
334+
return_value=Response(200, content=b"not json")
335+
)
336+
with pytest.raises(OMOPHubError) as exc_info:
337+
request_handler.get_raw("/test")
338+
assert "Invalid JSON" in str(exc_info.value)
339+
263340

264341
class TestAsyncRequest:
265342
"""Tests for asynchronous AsyncRequest class."""
@@ -407,3 +484,85 @@ async def test_async_rate_limit_error(self, request_handler: AsyncRequest) -> No
407484
await request_handler.get("/test")
408485

409486
assert exc_info.value.retry_after == 30
487+
488+
@pytest.mark.asyncio
489+
async def test_async_get_raw_request(self, request_handler: AsyncRequest) -> None:
490+
"""Test async get_raw returns full response with data and meta."""
491+
with respx.mock:
492+
respx.get("https://api.example.com/v1/search").mock(
493+
return_value=Response(
494+
200,
495+
json={
496+
"data": {"concepts": [{"concept_id": 42}]},
497+
"meta": {"pagination": {"page": 1, "has_next": True, "total_pages": 3}},
498+
},
499+
)
500+
)
501+
result = await request_handler.get_raw("/search")
502+
assert "data" in result
503+
assert "meta" in result
504+
assert result["meta"]["pagination"]["page"] == 1
505+
assert result["meta"]["pagination"]["has_next"] is True
506+
507+
@pytest.mark.asyncio
508+
async def test_async_get_raw_with_params(self, request_handler: AsyncRequest) -> None:
509+
"""Test async get_raw passes query parameters correctly."""
510+
with respx.mock:
511+
route = respx.get("https://api.example.com/v1/search").mock(
512+
return_value=Response(
513+
200,
514+
json={
515+
"data": {"concepts": []},
516+
"meta": {"pagination": {"page": 3}},
517+
},
518+
)
519+
)
520+
521+
result = await request_handler.get_raw("/search", params={"page": 3})
522+
523+
url_str = str(route.calls[0].request.url)
524+
assert "page=3" in url_str
525+
assert result["meta"]["pagination"]["page"] == 3
526+
527+
@pytest.mark.asyncio
528+
async def test_async_get_raw_error(self, request_handler: AsyncRequest) -> None:
529+
"""Test async get_raw raises errors correctly."""
530+
with respx.mock:
531+
respx.get("https://api.example.com/v1/test").mock(
532+
return_value=Response(
533+
404,
534+
json={"error": {"message": "Not found"}},
535+
headers={"X-Request-Id": "req_async_456"},
536+
)
537+
)
538+
with pytest.raises(NotFoundError) as exc_info:
539+
await request_handler.get_raw("/test")
540+
assert exc_info.value.request_id == "req_async_456"
541+
542+
@pytest.mark.asyncio
543+
async def test_async_get_raw_rate_limit(self, request_handler: AsyncRequest) -> None:
544+
"""Test async get_raw handles rate limit with retry-after."""
545+
with respx.mock:
546+
respx.get("https://api.example.com/v1/test").mock(
547+
return_value=Response(
548+
429,
549+
json={"error": {"message": "Rate limited"}},
550+
headers={"Retry-After": "60"},
551+
)
552+
)
553+
with pytest.raises(RateLimitError) as exc_info:
554+
await request_handler.get_raw("/test")
555+
assert exc_info.value.retry_after == 60
556+
557+
@pytest.mark.asyncio
558+
async def test_async_get_raw_json_decode_error(
559+
self, request_handler: AsyncRequest
560+
) -> None:
561+
"""Test async get_raw handles invalid JSON."""
562+
with respx.mock:
563+
respx.get("https://api.example.com/v1/test").mock(
564+
return_value=Response(200, content=b"invalid json response")
565+
)
566+
with pytest.raises(OMOPHubError) as exc_info:
567+
await request_handler.get_raw("/test")
568+
assert "Invalid JSON" in str(exc_info.value)

0 commit comments

Comments
 (0)