Skip to content
This repository was archived by the owner on Apr 2, 2025. It is now read-only.

Commit 2759ec2

Browse files
feat: adding pagination for POST search_opportunities
1 parent af2e2ad commit 2759ec2

File tree

5 files changed

+93
-31
lines changed

5 files changed

+93
-31
lines changed

src/stapi_fastapi/backends/product_backend.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ async def search_opportunities(
1616
product_router: ProductRouter,
1717
search: OpportunityRequest,
1818
request: Request,
19-
) -> ResultE[list[Opportunity]]:
19+
next: str | None,
20+
limit: int,
21+
) -> ResultE[tuple[list[Opportunity], str]]:
2022
"""
2123
Search for ordering opportunities for the given search parameters.
2224

src/stapi_fastapi/routers/product_router.py

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -165,27 +165,45 @@ def get_product(self, request: Request) -> Product:
165165
)
166166

167167
async def search_opportunities(
168-
self, search: OpportunityRequest, request: Request
168+
self,
169+
search: OpportunityRequest,
170+
request: Request,
171+
next: str | None = None,
172+
limit: int = 10,
169173
) -> OpportunityCollection:
170174
"""
171175
Explore the opportunities available for a particular set of constraints
172176
"""
173-
match await self.product.backend.search_opportunities(self, search, request):
174-
case Success(features):
175-
return OpportunityCollection(
176-
features=features,
177-
links=[
178-
Link( # current bug is missing method set and setting body for
177+
match await self.product.backend.search_opportunities(
178+
self, search, request, next, limit
179+
):
180+
case Success((features, pagination_token)):
181+
links = [
182+
Link( # current bug is missing method set and setting body for
183+
href=str(
184+
request.url_for(
185+
f"{self.root_router.name}:{self.product.id}:create-order",
186+
),
187+
),
188+
rel="create-order",
189+
type=TYPE_JSON,
190+
method="POST",
191+
),
192+
]
193+
if pagination_token:
194+
links.append(
195+
Link(
179196
href=str(
180-
request.url_for(
181-
f"{self.root_router.name}:{self.product.id}:create-order",
182-
),
197+
request.url.include_query_params(next=pagination_token)
183198
),
184-
rel="create-order",
199+
rel="next",
185200
type=TYPE_JSON,
186-
),
187-
],
188-
)
201+
method="POST",
202+
body=search,
203+
)
204+
)
205+
return OpportunityCollection(features=features, links=links)
206+
return OpportunityCollection(features=features, links=links)
189207
case Failure(e) if isinstance(e, ConstraintsException):
190208
raise e
191209
case Failure(e):

tests/application.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,19 +113,15 @@ async def search_opportunities(
113113
if limit > 100:
114114
limit = 100
115115
if next:
116-
start = self._opportunities.index(next)
116+
start = int(next)
117117
end = min(start + limit, len(self._opportunities))
118-
print(end)
119-
# opportunities = self._opportunities.k
120-
return Success(
121-
(
122-
[
123-
o.model_copy(update=search.model_dump())
124-
for o in self._opportunities
125-
],
126-
"",
127-
)
128-
)
118+
opportunities = [
119+
o.model_copy(update=search.model_dump())
120+
for o in self._opportunities[start:end]
121+
]
122+
if end < len(self._opportunities):
123+
return Success((opportunities, str(end)))
124+
return Success((opportunities, ""))
129125
except Exception as e:
130126
return Failure(e)
131127

tests/conftest.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,10 @@ def mock_test_spotlight_opportunities() -> list[Opportunity]:
186186
),
187187
),
188188
]
189+
190+
191+
@pytest.fixture
192+
def mock_test_pagination_opportunities(
193+
mock_test_spotlight_opportunities,
194+
) -> list[Opportunity]:
195+
return [opp for opp in mock_test_spotlight_opportunities for __ in range(0, 3)]

tests/test_opportunity.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import List
33

44
import pytest
5+
from fastapi import status
56
from fastapi.testclient import TestClient
67

78
from stapi_fastapi.models.opportunity import Opportunity, OpportunityCollection
@@ -27,7 +28,6 @@ def test_search_opportunities_response(
2728
start_string = rfc3339_strftime(start, format)
2829
end_string = rfc3339_strftime(end, format)
2930

30-
# Prepare the request payload
3131
request_payload = {
3232
"geometry": {
3333
"type": "Point",
@@ -43,13 +43,10 @@ def test_search_opportunities_response(
4343
},
4444
}
4545

46-
# Construct the endpoint URL using the `product_name` parameter
4746
url = f"/products/{product_id}/opportunities"
4847

49-
# Use POST method to send the payload
5048
response = stapi_client.post(url, json=request_payload)
5149

52-
# Validate response status and structure
5350
assert response.status_code == 200, f"Failed for product: {product_id}"
5451
body = response.json()
5552

@@ -59,3 +56,45 @@ def test_search_opportunities_response(
5956
pytest.fail("response is not an opportunity collection")
6057

6158
assert_link(f"POST {url}", body, "create-order", f"/products/{product_id}/orders")
59+
60+
61+
@pytest.mark.parametrize("product_id", ["test-spotlight"])
62+
def test_search_opportunities_pagination(
63+
product_id: str,
64+
stapi_client: TestClient,
65+
product_backend: MockProductBackend,
66+
mock_test_pagination_opportunities: List[Opportunity],
67+
) -> None:
68+
product_backend._opportunities = mock_test_pagination_opportunities
69+
70+
now = datetime.now(UTC)
71+
start = now
72+
end = start + timedelta(days=5)
73+
format = "%Y-%m-%dT%H:%M:%S.%f%z"
74+
start_string = rfc3339_strftime(start, format)
75+
end_string = rfc3339_strftime(end, format)
76+
77+
request_payload = {
78+
"geometry": {
79+
"type": "Point",
80+
"coordinates": [0, 0],
81+
},
82+
"datetime": f"{start_string}/{end_string}",
83+
"filter": {
84+
"op": "and",
85+
"args": [
86+
{"op": ">", "args": [{"property": "off_nadir"}, 0]},
87+
{"op": "<", "args": [{"property": "off_nadir"}, 45]},
88+
],
89+
},
90+
}
91+
92+
res = stapi_client.post(
93+
f"/products/{product_id}/opportunities",
94+
json=request_payload,
95+
params={"next": None, "limit": 2},
96+
)
97+
body = res.json() # noqa: F841
98+
99+
assert res.status_code == status.HTTP_200_OK
100+
assert 1 == 2

0 commit comments

Comments
 (0)