Skip to content

Commit ce7c91b

Browse files
authored
Merge pull request #120 from ipinfo/fix-tests
Fix failing resproxy tests caused by data change
2 parents 7aefc4b + 9947628 commit ce7c91b

File tree

2 files changed

+162
-49
lines changed

2 files changed

+162
-49
lines changed

tests/handler_async_test.py

Lines changed: 116 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
import os
33
import sys
44

5+
import aiohttp
6+
import ipinfo
7+
import pytest
8+
from ipinfo import handler_utils
59
from ipinfo.cache.default import DefaultCache
610
from ipinfo.details import Details
7-
from ipinfo.handler_async import AsyncHandler
8-
from ipinfo import handler_utils
911
from ipinfo.error import APIError
1012
from ipinfo.exceptions import RequestQuotaExceededError
11-
import ipinfo
12-
import pytest
13-
import aiohttp
13+
from ipinfo.handler_async import AsyncHandler
1414

1515
skip_if_python_3_11_or_later = sys.version_info >= (3, 11)
1616

@@ -78,8 +78,7 @@ async def test_get_details():
7878
assert country_flag["unicode"] == "U+1F1FA U+1F1F8"
7979
country_flag_url = details.country_flag_url
8080
assert (
81-
country_flag_url
82-
== "https://cdn.ipinfo.io/static/images/countries-flags/US.svg"
81+
country_flag_url == "https://cdn.ipinfo.io/static/images/countries-flags/US.svg"
8382
)
8483
country_currency = details.country_currency
8584
assert country_currency["code"] == "USD"
@@ -132,40 +131,84 @@ async def test_get_details():
132131

133132
await handler.deinit()
134133

134+
135135
@pytest.mark.parametrize(
136-
("mock_resp_status_code", "mock_resp_headers", "mock_resp_error_msg", "expected_error_json"),
136+
(
137+
"mock_resp_status_code",
138+
"mock_resp_headers",
139+
"mock_resp_error_msg",
140+
"expected_error_json",
141+
),
137142
[
138-
pytest.param(503, {"Content-Type": "text/plain"}, "Service Unavailable", {"error": "Service Unavailable"}, id="5xx_not_json"),
139-
pytest.param(403, {"Content-Type": "application/json"}, '{"message": "missing token"}', {"message": "missing token"}, id="4xx_json"),
140-
pytest.param(400, {"Content-Type": "application/json"}, '{"message": "missing field"}', {"message": "missing field"}, id="400"),
141-
]
143+
pytest.param(
144+
503,
145+
{"Content-Type": "text/plain"},
146+
"Service Unavailable",
147+
{"error": "Service Unavailable"},
148+
id="5xx_not_json",
149+
),
150+
pytest.param(
151+
403,
152+
{"Content-Type": "application/json"},
153+
'{"message": "missing token"}',
154+
{"message": "missing token"},
155+
id="4xx_json",
156+
),
157+
pytest.param(
158+
400,
159+
{"Content-Type": "application/json"},
160+
'{"message": "missing field"}',
161+
{"message": "missing field"},
162+
id="400",
163+
),
164+
],
142165
)
143166
@pytest.mark.asyncio
144-
async def test_get_details_error(monkeypatch, mock_resp_status_code, mock_resp_headers, mock_resp_error_msg, expected_error_json):
167+
async def test_get_details_error(
168+
monkeypatch,
169+
mock_resp_status_code,
170+
mock_resp_headers,
171+
mock_resp_error_msg,
172+
expected_error_json,
173+
):
145174
async def mock_get(*args, **kwargs):
146-
response = MockResponse(status=mock_resp_status_code, text=mock_resp_error_msg, headers=mock_resp_headers)
175+
response = MockResponse(
176+
status=mock_resp_status_code,
177+
text=mock_resp_error_msg,
178+
headers=mock_resp_headers,
179+
)
147180
return response
148181

149-
monkeypatch.setattr(aiohttp.ClientSession, 'get', lambda *args, **kwargs: aiohttp.client._RequestContextManager(mock_get()))
182+
monkeypatch.setattr(
183+
aiohttp.ClientSession,
184+
"get",
185+
lambda *args, **kwargs: aiohttp.client._RequestContextManager(mock_get()),
186+
)
150187
token = os.environ.get("IPINFO_TOKEN", "")
151188
handler = AsyncHandler(token)
152189
with pytest.raises(APIError) as exc_info:
153190
await handler.getDetails("8.8.8.8")
154191
assert exc_info.value.error_code == mock_resp_status_code
155192
assert exc_info.value.error_json == expected_error_json
156193

194+
157195
@pytest.mark.asyncio
158196
async def test_get_details_quota_error(monkeypatch):
159197
async def mock_get(*args, **kwargs):
160198
response = MockResponse(status=429, text="Quota exceeded", headers={})
161199
return response
162200

163-
monkeypatch.setattr(aiohttp.ClientSession, 'get', lambda *args, **kwargs: aiohttp.client._RequestContextManager(mock_get()))
201+
monkeypatch.setattr(
202+
aiohttp.ClientSession,
203+
"get",
204+
lambda *args, **kwargs: aiohttp.client._RequestContextManager(mock_get()),
205+
)
164206
token = os.environ.get("IPINFO_TOKEN", "")
165207
handler = AsyncHandler(token)
166208
with pytest.raises(RequestQuotaExceededError):
167209
await handler.getDetails("8.8.8.8")
168210

211+
169212
#############
170213
# BATCH TESTS
171214
#############
@@ -198,7 +241,9 @@ def _check_batch_details(ips, details, token):
198241
assert "domains" in d
199242

200243

201-
@pytest.mark.skipif(skip_if_python_3_11_or_later, reason="Requires Python 3.10 or earlier")
244+
@pytest.mark.skipif(
245+
skip_if_python_3_11_or_later, reason="Requires Python 3.10 or earlier"
246+
)
202247
@pytest.mark.parametrize("batch_size", [None, 1, 2, 3])
203248
@pytest.mark.asyncio
204249
async def test_get_batch_details(batch_size):
@@ -229,15 +274,15 @@ async def test_get_iterative_batch_details(batch_size):
229274
_check_iterative_batch_details(ips, details, token)
230275

231276

232-
@pytest.mark.skipif(skip_if_python_3_11_or_later, reason="Requires Python 3.10 or earlier")
277+
@pytest.mark.skipif(
278+
skip_if_python_3_11_or_later, reason="Requires Python 3.10 or earlier"
279+
)
233280
@pytest.mark.parametrize("batch_size", [None, 1, 2, 3])
234281
@pytest.mark.asyncio
235282
async def test_get_batch_details_total_timeout(batch_size):
236283
handler, token, ips = _prepare_batch_test()
237284
with pytest.raises(ipinfo.exceptions.TimeoutExceededError):
238-
await handler.getBatchDetails(
239-
ips, batch_size=batch_size, timeout_total=0.001
240-
)
285+
await handler.getBatchDetails(ips, batch_size=batch_size, timeout_total=0.001)
241286
await handler.deinit()
242287

243288

@@ -260,30 +305,65 @@ async def test_bogon_details():
260305

261306

262307
@pytest.mark.asyncio
263-
async def test_get_resproxy():
264-
token = os.environ.get("IPINFO_TOKEN", "")
265-
if not token:
266-
pytest.skip("token required for resproxy tests")
267-
handler = AsyncHandler(token)
268-
# Use an IP known to be a residential proxy (from API documentation)
308+
async def test_get_resproxy(monkeypatch):
309+
mock_response = MockResponse(
310+
json.dumps(
311+
{
312+
"ip": "175.107.211.204",
313+
"last_seen": "2025-01-20",
314+
"percent_days_seen": 0.85,
315+
"service": "example_service",
316+
}
317+
),
318+
200,
319+
{"Content-Type": "application/json"},
320+
)
321+
322+
def mock_get(*args, **kwargs):
323+
return mock_response
324+
325+
handler = AsyncHandler("test_token")
326+
handler._ensure_aiohttp_ready()
327+
monkeypatch.setattr(handler.httpsess, "get", mock_get)
328+
269329
details = await handler.getResproxy("175.107.211.204")
270330
assert isinstance(details, Details)
271331
assert details.ip == "175.107.211.204"
272-
assert details.last_seen is not None
273-
assert details.percent_days_seen is not None
274-
assert details.service is not None
332+
assert details.last_seen == "2025-01-20"
333+
assert details.percent_days_seen == 0.85
334+
assert details.service == "example_service"
275335
await handler.deinit()
276336

277337

278338
@pytest.mark.asyncio
279-
async def test_get_resproxy_caching():
280-
token = os.environ.get("IPINFO_TOKEN", "")
281-
if not token:
282-
pytest.skip("token required for resproxy tests")
283-
handler = AsyncHandler(token)
339+
async def test_get_resproxy_caching(monkeypatch):
340+
call_count = 0
341+
342+
def mock_get(*args, **kwargs):
343+
nonlocal call_count
344+
call_count += 1
345+
return MockResponse(
346+
json.dumps(
347+
{
348+
"ip": "175.107.211.204",
349+
"last_seen": "2025-01-20",
350+
"percent_days_seen": 0.85,
351+
"service": "example_service",
352+
}
353+
),
354+
200,
355+
{"Content-Type": "application/json"},
356+
)
357+
358+
handler = AsyncHandler("test_token")
359+
handler._ensure_aiohttp_ready()
360+
monkeypatch.setattr(handler.httpsess, "get", mock_get)
361+
284362
# First call should hit the API
285363
details1 = await handler.getResproxy("175.107.211.204")
286364
# Second call should hit the cache
287365
details2 = await handler.getResproxy("175.107.211.204")
288366
assert details1.ip == details2.ip
289-
await handler.deinit()
367+
# Verify only one API call was made (second was cached)
368+
assert call_count == 1
369+
await handler.deinit()

tests/handler_test.py

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -243,27 +243,60 @@ def test_iterative_bogon_details():
243243
#################
244244

245245

246-
def test_get_resproxy():
247-
token = os.environ.get("IPINFO_TOKEN", "")
248-
if not token:
249-
pytest.skip("token required for resproxy tests")
246+
def test_get_resproxy(monkeypatch):
247+
def mock_get(*args, **kwargs):
248+
response = requests.Response()
249+
response.status_code = 200
250+
response.headers = {"Content-Type": "application/json"}
251+
response._content = b'{"ip": "175.107.211.204", "last_seen": "2025-01-20", "percent_days_seen": 0.85, "service": "example_service"}'
252+
return response
253+
254+
monkeypatch.setattr(requests, "get", mock_get)
255+
token = "test_token"
250256
handler = Handler(token)
251-
# Use an IP known to be a residential proxy (from API documentation)
252257
details = handler.getResproxy("175.107.211.204")
253258
assert isinstance(details, Details)
254259
assert details.ip == "175.107.211.204"
255-
assert details.last_seen is not None
256-
assert details.percent_days_seen is not None
257-
assert details.service is not None
260+
assert details.last_seen == "2025-01-20"
261+
assert details.percent_days_seen == 0.85
262+
assert details.service == "example_service"
258263

259264

260-
def test_get_resproxy_caching():
261-
token = os.environ.get("IPINFO_TOKEN", "")
262-
if not token:
263-
pytest.skip("token required for resproxy tests")
265+
def test_get_resproxy_caching(monkeypatch):
266+
call_count = 0
267+
268+
def mock_get(*args, **kwargs):
269+
nonlocal call_count
270+
call_count += 1
271+
response = requests.Response()
272+
response.status_code = 200
273+
response.headers = {"Content-Type": "application/json"}
274+
response._content = b'{"ip": "175.107.211.204", "last_seen": "2025-01-20", "percent_days_seen": 0.85, "service": "example_service"}'
275+
return response
276+
277+
monkeypatch.setattr(requests, "get", mock_get)
278+
token = "test_token"
264279
handler = Handler(token)
265280
# First call should hit the API
266281
details1 = handler.getResproxy("175.107.211.204")
267282
# Second call should hit the cache
268283
details2 = handler.getResproxy("175.107.211.204")
269-
assert details1.ip == details2.ip
284+
assert details1.ip == details2.ip
285+
# Verify only one API call was made (second was cached)
286+
assert call_count == 1
287+
288+
289+
def test_get_resproxy_empty(monkeypatch):
290+
def mock_get(*args, **kwargs):
291+
response = requests.Response()
292+
response.status_code = 200
293+
response.headers = {"Content-Type": "application/json"}
294+
response._content = b"{}"
295+
return response
296+
297+
monkeypatch.setattr(requests, "get", mock_get)
298+
token = "test_token"
299+
handler = Handler(token)
300+
details = handler.getResproxy("8.8.8.8")
301+
assert isinstance(details, Details)
302+
assert details.all == {}

0 commit comments

Comments
 (0)