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

Commit fba52ed

Browse files
committed
get tests passing
1 parent 6a2f4d7 commit fba52ed

File tree

8 files changed

+147
-115
lines changed

8 files changed

+147
-115
lines changed

src/stapi_fastapi/responses.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from fastapi.responses import JSONResponse
2+
3+
from stapi_fastapi.constants import TYPE_GEOJSON
4+
5+
6+
class GeoJSONResponse(JSONResponse):
7+
def __init__(self, *args, **kwargs) -> None:
8+
kwargs["media_type"] = TYPE_GEOJSON
9+
super().__init__(*args, **kwargs)

src/stapi_fastapi/routers/product_router.py

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import TYPE_CHECKING, Self
44

5-
from fastapi import APIRouter, HTTPException, Request, status
5+
from fastapi import APIRouter, HTTPException, Request, Response, status
66

77
from stapi_fastapi.constants import TYPE_GEOJSON, TYPE_JSON
88
from stapi_fastapi.exceptions import ConstraintsException
@@ -13,6 +13,7 @@
1313
from stapi_fastapi.models.order import Order
1414
from stapi_fastapi.models.product import Product
1515
from stapi_fastapi.models.shared import Link
16+
from stapi_fastapi.responses import GeoJSONResponse
1617
from stapi_fastapi.types.json_schema_model import JsonSchemaModel
1718

1819
if TYPE_CHECKING:
@@ -44,13 +45,7 @@ def __init__(
4445
endpoint=self.search_opportunities,
4546
name=f"{self.root_router.name}:{self.product.id}:search-opportunities",
4647
methods=["POST"],
47-
responses={
48-
200: {
49-
"content": {
50-
"TYPE_GEOJSON": {},
51-
},
52-
}
53-
},
48+
response_class=GeoJSONResponse,
5449
summary="Search Opportunities for the product",
5550
)
5651

@@ -67,13 +62,8 @@ def __init__(
6762
endpoint=self.create_order,
6863
name=f"{self.root_router.name}:{self.product.id}:create-order",
6964
methods=["POST"],
70-
responses={
71-
201: {
72-
"content": {
73-
"TYPE_GEOJSON": {},
74-
},
75-
}
76-
},
65+
response_class=GeoJSONResponse,
66+
status_code=status.HTTP_201_CREATED,
7767
summary="Create an order for the product",
7868
)
7969

@@ -104,6 +94,7 @@ async def search_opportunities(
10494
)
10595
except ConstraintsException as exc:
10696
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, detail=exc.detail)
97+
10798
return OpportunityCollection(features=opportunities)
10899

109100
async def get_product_constraints(self: Self) -> JsonSchemaModel:
@@ -113,7 +104,7 @@ async def get_product_constraints(self: Self) -> JsonSchemaModel:
113104
return self.product.constraints
114105

115106
async def create_order(
116-
self, payload: OpportunityRequest, request: Request
107+
self, payload: OpportunityRequest, request: Request, response: Response
117108
) -> Order:
118109
"""
119110
Create a new order.
@@ -127,6 +118,7 @@ async def create_order(
127118
except ConstraintsException as exc:
128119
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, detail=exc.detail)
129120

130-
location = self.root_router.generate_order_href(request, order.id)
131-
order.links.append(Link(href=str(location), rel="self", type=TYPE_GEOJSON))
121+
location = str(self.root_router.generate_order_href(request, order.id))
122+
order.links.append(Link(href=location, rel="self", type=TYPE_GEOJSON))
123+
response.headers["Location"] = location
132124
return order

src/stapi_fastapi/routers/root_router.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from stapi_fastapi.models.product import Product, ProductsCollection
1111
from stapi_fastapi.models.root import RootResponse
1212
from stapi_fastapi.models.shared import Link
13+
from stapi_fastapi.responses import GeoJSONResponse
1314
from stapi_fastapi.routers.product_router import ProductRouter
1415

1516

@@ -56,13 +57,7 @@ def __init__(
5657
self.get_orders,
5758
methods=["GET"],
5859
name=f"{self.name}:list-orders",
59-
responses={
60-
200: {
61-
"content": {
62-
"TYPE_GEOJSON": {},
63-
},
64-
}
65-
},
60+
response_class=GeoJSONResponse,
6661
tags=["Order"],
6762
)
6863

@@ -71,13 +66,7 @@ def __init__(
7166
self.get_order,
7267
methods=["GET"],
7368
name=f"{self.name}:get-order",
74-
responses={
75-
200: {
76-
"content": {
77-
"TYPE_GEOJSON": {},
78-
},
79-
}
80-
},
69+
response_class=GeoJSONResponse,
8170
tags=["Orders"],
8271
)
8372

@@ -136,6 +125,7 @@ async def get_orders(self, request: Request) -> list[Order]:
136125
type=TYPE_JSON,
137126
)
138127
)
128+
139129
return orders
140130

141131
async def get_order(self: Self, order_id: str, request: Request) -> Order:

tests/backends.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from typing import Mapping
21
from uuid import uuid4
32

43
from fastapi import Request
@@ -9,8 +8,13 @@
98
from stapi_fastapi.models.product import Product
109

1110

12-
class TestRootBackend:
13-
_orders: Mapping[str, Order] = {}
11+
class MockOrderDB(dict[int | str, Order]):
12+
pass
13+
14+
15+
class MockRootBackend:
16+
def __init__(self, orders: MockOrderDB) -> None:
17+
self._orders = orders
1418

1519
async def get_orders(self, request: Request) -> list[Order]:
1620
"""
@@ -28,10 +32,11 @@ async def get_order(self, order_id: str, request: Request) -> Order:
2832
raise NotFoundException()
2933

3034

31-
class TestProductBackend(ProductBackend):
32-
_opportunities: list[Opportunity] = []
33-
_allowed_payloads: list[OpportunityRequest] = []
34-
_orders: Mapping[str, Order] = {}
35+
class MockProductBackend(ProductBackend):
36+
def __init__(self, orders: MockOrderDB) -> None:
37+
self._opportunities: list[Opportunity] = []
38+
self._allowed_payloads: list[OpportunityRequest] = []
39+
self._orders = orders
3540

3641
async def search_opportunities(
3742
self, product: Product, search: OpportunityRequest, request: Request

tests/conftest.py

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,48 +12,56 @@
1212
from stapi_fastapi.models.opportunity import (
1313
Opportunity,
1414
OpportunityPropertiesBase,
15+
OpportunityRequest,
1516
)
1617
from stapi_fastapi.models.product import Product, Provider, ProviderRole
1718
from stapi_fastapi.routers.root_router import RootRouter
1819

19-
from .backend import TestProductBackend, TestRootBackend
20+
from .backends import MockOrderDB, MockProductBackend, MockRootBackend
2021

2122

2223
class TestSpotlightProperties(OpportunityPropertiesBase):
2324
off_nadir: int
2425

2526

27+
@pytest.fixture(scope="session")
28+
def base_url() -> Iterator[str]:
29+
yield "http://stapiserver"
30+
31+
2632
@pytest.fixture
27-
def mock_product_test_spotlight(mock_provider_test: Provider) -> Product:
33+
def order_db() -> MockOrderDB:
34+
return MockOrderDB()
35+
36+
37+
@pytest.fixture
38+
def product_backend(order_db: MockOrderDB) -> MockProductBackend:
39+
return MockProductBackend(order_db)
40+
41+
42+
@pytest.fixture
43+
def root_backend(order_db) -> MockRootBackend:
44+
return MockRootBackend(order_db)
45+
46+
47+
@pytest.fixture
48+
def mock_product_test_spotlight(
49+
product_backend: MockProductBackend, mock_provider: Provider
50+
) -> Product:
2851
"""Fixture for a mock Test Spotlight product."""
2952
return Product(
3053
id="test-spotlight",
3154
title="Test Spotlight Product",
3255
description="Test product for test spotlight",
3356
license="CC-BY-4.0",
3457
keywords=["test", "satellite"],
35-
providers=[mock_provider_test],
58+
providers=[mock_provider],
3659
links=[],
3760
constraints=TestSpotlightProperties,
38-
backend=TestProductBackend(),
61+
backend=product_backend,
3962
)
4063

4164

42-
@pytest.fixture(scope="session")
43-
def base_url() -> Iterator[str]:
44-
yield "http://stapiserver"
45-
46-
47-
@pytest.fixture
48-
def product_backend() -> Iterator[TestProductBackend]:
49-
yield TestProductBackend()
50-
51-
52-
@pytest.fixture
53-
def root_backend() -> Iterator[TestRootBackend]:
54-
yield TestRootBackend()
55-
56-
5765
@pytest.fixture
5866
def stapi_client(
5967
root_backend, mock_product_test_spotlight, base_url: str
@@ -63,7 +71,8 @@ def stapi_client(
6371
app = FastAPI()
6472
app.include_router(root_router, prefix="")
6573

66-
yield TestClient(app, base_url=f"{base_url}")
74+
with TestClient(app, base_url=f"{base_url}") as client:
75+
yield client
6776

6877

6978
@pytest.fixture(scope="session")
@@ -83,8 +92,8 @@ def products(mock_product_test_spotlight) -> list[Product]:
8392

8493

8594
@pytest.fixture
86-
def opportunities(products: list[Product]) -> Iterator[list[Opportunity]]:
87-
yield [
95+
def opportunities(products: list[Product]) -> list[Opportunity]:
96+
return [
8897
Opportunity(
8998
geometry=Point(type="Point", coordinates=[13.4, 52.5]),
9099
properties={
@@ -97,7 +106,7 @@ def opportunities(products: list[Product]) -> Iterator[list[Opportunity]]:
97106

98107

99108
@pytest.fixture
100-
def mock_provider_test() -> Provider:
109+
def mock_provider() -> Provider:
101110
return Provider(
102111
name="Test Provider",
103112
description="A provider for Test data",
@@ -128,3 +137,16 @@ def mock_test_spotlight_opportunities() -> List[Opportunity]:
128137
),
129138
),
130139
]
140+
141+
142+
@pytest.fixture
143+
def allowed_payloads() -> list[OpportunityRequest]:
144+
return [
145+
OpportunityRequest(
146+
geometry=Point(
147+
type="Point", coordinates=Position2D(longitude=13.4, latitude=52.5)
148+
),
149+
datetime=(datetime.now(UTC), datetime.now(UTC)),
150+
filter={},
151+
),
152+
]

tests/opportunity_test.py

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
1-
import json
21
from datetime import UTC, datetime, timedelta
32
from typing import List
43

54
import pytest
65
from fastapi.testclient import TestClient
7-
from stapi_fastapi.models.opportunity import Opportunity
6+
from stapi_fastapi.models.opportunity import Opportunity, OpportunityCollection
87

9-
from .backend import TestProductBackend
8+
from .backends import MockProductBackend
109
from .datetime_interval_test import rfc3339_strftime
1110

1211

1312
@pytest.mark.parametrize("product_id", ["test-spotlight"])
1413
def test_search_opportunities_response(
1514
product_id: str,
1615
mock_test_spotlight_opportunities: List[Opportunity],
17-
product_backend: TestProductBackend,
16+
product_backend: MockProductBackend,
1817
stapi_client: TestClient,
19-
):
18+
) -> None:
2019
product_backend._opportunities = mock_test_spotlight_opportunities
2120

2221
now = datetime.now(UTC)
@@ -48,15 +47,11 @@ def test_search_opportunities_response(
4847
# Use POST method to send the payload
4948
response = stapi_client.post(url, json=request_payload)
5049

51-
print(json.dumps(response.json(), indent=4))
52-
5350
# Validate response status and structure
5451
assert response.status_code == 200, f"Failed for product: {product_id}"
55-
assert isinstance(
56-
response.json(), list
57-
), "Response should be a list of opportunities"
58-
for opportunity in response.json():
59-
assert "id" in opportunity, "Opportunity item should have an 'id' field"
60-
assert (
61-
"properties" in opportunity
62-
), "Opportunity item should have a 'properties' field"
52+
_json = response.json()
53+
54+
try:
55+
OpportunityCollection(**_json)
56+
except Exception as _:
57+
pytest.fail("response is not an opportunity collection")

0 commit comments

Comments
 (0)