Skip to content

Commit 6faef91

Browse files
authored
- Fix pagination bug (#305)
- Add unit test for pagination
1 parent 370601f commit 6faef91

File tree

2 files changed

+70
-1
lines changed

2 files changed

+70
-1
lines changed

Diff for: easypost/services/base_service.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def _get_next_page_resources(
104104
response = Requestor(self._client).request(method=RequestMethod.GET, url=url, params=params)
105105

106106
response_array: List[Any] = response.get(url[1:]) # type: ignore
107-
if response is None or len(response_array) == 0 or not response.get("has_more"):
107+
if response is None or len(response_array) == 0:
108108
raise EndOfPaginationError(NO_MORE_PAGES_ERROR)
109109

110110
return convert_to_easypost_object(response=response)

Diff for: tests/test_base_service.py

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from unittest.mock import patch
2+
3+
import pytest
4+
from easypost.constant import NO_MORE_PAGES_ERROR
5+
from easypost.errors import EndOfPaginationError
6+
7+
8+
@pytest.mark.vcr() # Cassette not needed due to mocking, but used to avoid making real bogus API calls
9+
def test_get_next_page_collects_all_pages(test_client):
10+
page_size = 1 # Doesn't matter what this is, we're mocking the response
11+
all_results = []
12+
13+
# Using scanforms as an example, but this should work for any service since it's a base class method
14+
first_page_response = {
15+
"scan_forms": [
16+
{
17+
"id": "sf_123",
18+
}
19+
],
20+
"has_more": True,
21+
}
22+
23+
# Mock the initial "get all scanforms" call
24+
with patch("easypost.requestor.Requestor.request", return_value=first_page_response):
25+
first_page = test_client.scan_form.all(page_size=page_size)
26+
all_results += first_page["scan_forms"]
27+
previous_page = first_page
28+
29+
second_page_response = {
30+
"scan_forms": [
31+
{
32+
"id": "sf_456",
33+
}
34+
],
35+
"has_more": True,
36+
}
37+
38+
# Mock the first "get next page" call with more to collect after
39+
# (current page "has_more" = True, next page "has_more" = True)
40+
with patch("easypost.requestor.Requestor.request", return_value=second_page_response):
41+
next_page = test_client.scan_form.get_next_page(scan_forms=previous_page, page_size=page_size) # type: ignore
42+
all_results += next_page["scan_forms"]
43+
previous_page = next_page
44+
45+
third_page_response = {
46+
"scan_forms": [
47+
{
48+
"id": "sf_789",
49+
}
50+
],
51+
"has_more": False,
52+
}
53+
54+
# Mock the second "get next page" call with no more to collect
55+
# (current page "has_more" = True, next page "has_more" = False)
56+
with patch("easypost.requestor.Requestor.request", return_value=third_page_response):
57+
next_page = test_client.scan_form.get_next_page(scan_forms=previous_page, page_size=page_size) # type: ignore
58+
all_results += next_page["scan_forms"]
59+
previous_page = next_page
60+
61+
# Verify we have all scan_forms (from the initial "get all scanforms" and two subsequent "get next page" calls)
62+
# Ensures that no guard clauses inside the "get next page" method are preventing us from collecting all scanforms
63+
assert len(all_results) == 3
64+
65+
# Now that the previous page has "has_more" = False, it should throw an error before even making the API call
66+
with pytest.raises(EndOfPaginationError) as error:
67+
_ = test_client.scan_form.get_next_page(scan_forms=previous_page, page_size=page_size) # type: ignore
68+
69+
assert error.value.message == NO_MORE_PAGES_ERROR

0 commit comments

Comments
 (0)