Skip to content

Commit 57ccba7

Browse files
committed
feat: enforce 100% test coverage in CI
1 parent 0210469 commit 57ccba7

File tree

4 files changed

+142
-1
lines changed

4 files changed

+142
-1
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ jobs:
3131
run: ruff format --check .
3232

3333
- name: Run tests
34-
run: pytest -v
34+
run: pytest -v --cov=rdapapi --cov-fail-under=100

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ dependencies = [
3232
dev = [
3333
"pytest>=8.0",
3434
"pytest-asyncio>=0.24",
35+
"pytest-cov>=6.0",
3536
"respx>=0.22",
3637
"ruff>=0.8",
3738
]

tests/test_async.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,78 @@
4747
}
4848

4949

50+
IP_RESPONSE = {
51+
"handle": "NET-8-8-8-0-2",
52+
"name": "GOGL",
53+
"type": "DIRECT ALLOCATION",
54+
"start_address": "8.8.8.0",
55+
"end_address": "8.8.8.255",
56+
"ip_version": "v4",
57+
"parent_handle": "NET-8-0-0-0-0",
58+
"country": None,
59+
"status": ["active"],
60+
"dates": {"registered": "2023-12-28T17:24:33-05:00", "expires": None, "updated": None},
61+
"entities": {},
62+
"cidr": ["8.8.8.0/24"],
63+
"remarks": [],
64+
"port43": "whois.arin.net",
65+
"meta": {
66+
"rdap_server": "https://rdap.arin.net/registry/",
67+
"raw_rdap_url": "https://rdap.arin.net/registry/ip/8.8.8.8",
68+
"cached": False,
69+
"cache_expires": "2026-02-24T15:30:00Z",
70+
},
71+
}
72+
73+
NS_RESPONSE = {
74+
"ldh_name": "ns1.google.com",
75+
"unicode_name": None,
76+
"handle": None,
77+
"ip_addresses": {"v4": ["216.239.32.10"], "v6": []},
78+
"status": [],
79+
"dates": {"registered": None, "expires": None, "updated": None},
80+
"entities": {},
81+
"meta": {
82+
"rdap_server": "https://rdap.verisign.com/com/v1/",
83+
"raw_rdap_url": "https://rdap.verisign.com/com/v1/nameserver/ns1.google.com",
84+
"cached": False,
85+
"cache_expires": "2026-02-24T15:30:00Z",
86+
},
87+
}
88+
89+
ENTITY_RESPONSE = {
90+
"handle": "GOGL",
91+
"name": "Google LLC",
92+
"organization": None,
93+
"email": None,
94+
"phone": None,
95+
"address": None,
96+
"contact_url": None,
97+
"country_code": None,
98+
"roles": [],
99+
"status": [],
100+
"dates": {"registered": "2000-03-30T00:00:00-04:00", "expires": None, "updated": None},
101+
"remarks": [],
102+
"port43": "whois.arin.net",
103+
"public_ids": [],
104+
"entities": {},
105+
"autnums": [],
106+
"networks": [],
107+
"meta": {
108+
"rdap_server": "https://rdap.arin.net/registry/",
109+
"raw_rdap_url": "https://rdap.arin.net/registry/entity/GOGL",
110+
"cached": False,
111+
"cache_expires": "2026-02-24T15:30:00Z",
112+
},
113+
}
114+
115+
116+
@pytest.mark.asyncio
117+
async def test_async_empty_api_key_raises():
118+
with pytest.raises(ValueError, match="non-empty"):
119+
AsyncRdapApi("")
120+
121+
50122
@pytest.mark.asyncio
51123
@respx.mock
52124
async def test_async_domain_lookup():
@@ -190,3 +262,52 @@ async def test_async_bulk_domains_plan_upgrade_required():
190262
await api.bulk_domains(["google.com"])
191263

192264
assert exc_info.value.error == "plan_upgrade_required"
265+
266+
267+
@pytest.mark.asyncio
268+
@respx.mock
269+
async def test_async_ip_lookup():
270+
respx.get(f"{BASE_URL}/ip/8.8.8.8").mock(return_value=httpx.Response(200, json=IP_RESPONSE))
271+
272+
async with AsyncRdapApi("test-key", base_url=BASE_URL) as api:
273+
result = await api.ip("8.8.8.8")
274+
275+
assert result.handle == "NET-8-8-8-0-2"
276+
assert result.ip_version == "v4"
277+
278+
279+
@pytest.mark.asyncio
280+
@respx.mock
281+
async def test_async_nameserver_lookup():
282+
respx.get(f"{BASE_URL}/nameserver/ns1.google.com").mock(return_value=httpx.Response(200, json=NS_RESPONSE))
283+
284+
async with AsyncRdapApi("test-key", base_url=BASE_URL) as api:
285+
result = await api.nameserver("ns1.google.com")
286+
287+
assert result.ldh_name == "ns1.google.com"
288+
289+
290+
@pytest.mark.asyncio
291+
@respx.mock
292+
async def test_async_entity_lookup():
293+
respx.get(f"{BASE_URL}/entity/GOGL").mock(return_value=httpx.Response(200, json=ENTITY_RESPONSE))
294+
295+
async with AsyncRdapApi("test-key", base_url=BASE_URL) as api:
296+
result = await api.entity("GOGL")
297+
298+
assert result.handle == "GOGL"
299+
assert result.name == "Google LLC"
300+
301+
302+
@pytest.mark.asyncio
303+
@respx.mock
304+
async def test_async_bulk_domains_with_follow():
305+
import json
306+
307+
route = respx.post(f"{BASE_URL}/domains/bulk").mock(return_value=httpx.Response(200, json=BULK_RESPONSE))
308+
309+
async with AsyncRdapApi("test-key", base_url=BASE_URL) as api:
310+
await api.bulk_domains(["google.com"], follow=True)
311+
312+
body = json.loads(route.calls[0].request.content)
313+
assert body["follow"] is True

tests/test_client.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
NotFoundError,
1010
RateLimitError,
1111
RdapApi,
12+
RdapApiError,
1213
SubscriptionRequiredError,
1314
UpstreamError,
1415
ValidationError,
@@ -362,6 +363,24 @@ def test_upstream_error():
362363
api.close()
363364

364365

366+
def test_empty_api_key_raises():
367+
with pytest.raises(ValueError, match="non-empty"):
368+
RdapApi("")
369+
370+
371+
@respx.mock
372+
def test_non_json_error_body():
373+
respx.get(f"{BASE_URL}/domain/test.com").mock(return_value=httpx.Response(500, text="Internal Server Error"))
374+
375+
api = RdapApi("test-key", base_url=BASE_URL)
376+
with pytest.raises(RdapApiError) as exc_info:
377+
api.domain("test.com")
378+
379+
assert exc_info.value.status_code == 500
380+
assert exc_info.value.error == "unknown_error"
381+
api.close()
382+
383+
365384
@respx.mock
366385
def test_auth_header_sent():
367386
route = respx.get(f"{BASE_URL}/domain/google.com").mock(return_value=httpx.Response(200, json=DOMAIN_RESPONSE))

0 commit comments

Comments
 (0)