Skip to content

Commit

Permalink
get tests passing
Browse files Browse the repository at this point in the history
  • Loading branch information
jkeifer committed Nov 1, 2024
1 parent 6a2f4d7 commit fba52ed
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 115 deletions.
9 changes: 9 additions & 0 deletions src/stapi_fastapi/responses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from fastapi.responses import JSONResponse

from stapi_fastapi.constants import TYPE_GEOJSON


class GeoJSONResponse(JSONResponse):
def __init__(self, *args, **kwargs) -> None:
kwargs["media_type"] = TYPE_GEOJSON
super().__init__(*args, **kwargs)
28 changes: 10 additions & 18 deletions src/stapi_fastapi/routers/product_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING, Self

from fastapi import APIRouter, HTTPException, Request, status
from fastapi import APIRouter, HTTPException, Request, Response, status

from stapi_fastapi.constants import TYPE_GEOJSON, TYPE_JSON
from stapi_fastapi.exceptions import ConstraintsException
Expand All @@ -13,6 +13,7 @@
from stapi_fastapi.models.order import Order
from stapi_fastapi.models.product import Product
from stapi_fastapi.models.shared import Link
from stapi_fastapi.responses import GeoJSONResponse
from stapi_fastapi.types.json_schema_model import JsonSchemaModel

if TYPE_CHECKING:
Expand Down Expand Up @@ -44,13 +45,7 @@ def __init__(
endpoint=self.search_opportunities,
name=f"{self.root_router.name}:{self.product.id}:search-opportunities",
methods=["POST"],
responses={
200: {
"content": {
"TYPE_GEOJSON": {},
},
}
},
response_class=GeoJSONResponse,
summary="Search Opportunities for the product",
)

Expand All @@ -67,13 +62,8 @@ def __init__(
endpoint=self.create_order,
name=f"{self.root_router.name}:{self.product.id}:create-order",
methods=["POST"],
responses={
201: {
"content": {
"TYPE_GEOJSON": {},
},
}
},
response_class=GeoJSONResponse,
status_code=status.HTTP_201_CREATED,
summary="Create an order for the product",
)

Expand Down Expand Up @@ -104,6 +94,7 @@ async def search_opportunities(
)
except ConstraintsException as exc:
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, detail=exc.detail)

return OpportunityCollection(features=opportunities)

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

async def create_order(
self, payload: OpportunityRequest, request: Request
self, payload: OpportunityRequest, request: Request, response: Response
) -> Order:
"""
Create a new order.
Expand All @@ -127,6 +118,7 @@ async def create_order(
except ConstraintsException as exc:
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, detail=exc.detail)

location = self.root_router.generate_order_href(request, order.id)
order.links.append(Link(href=str(location), rel="self", type=TYPE_GEOJSON))
location = str(self.root_router.generate_order_href(request, order.id))
order.links.append(Link(href=location, rel="self", type=TYPE_GEOJSON))
response.headers["Location"] = location
return order
18 changes: 4 additions & 14 deletions src/stapi_fastapi/routers/root_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from stapi_fastapi.models.product import Product, ProductsCollection
from stapi_fastapi.models.root import RootResponse
from stapi_fastapi.models.shared import Link
from stapi_fastapi.responses import GeoJSONResponse
from stapi_fastapi.routers.product_router import ProductRouter


Expand Down Expand Up @@ -56,13 +57,7 @@ def __init__(
self.get_orders,
methods=["GET"],
name=f"{self.name}:list-orders",
responses={
200: {
"content": {
"TYPE_GEOJSON": {},
},
}
},
response_class=GeoJSONResponse,
tags=["Order"],
)

Expand All @@ -71,13 +66,7 @@ def __init__(
self.get_order,
methods=["GET"],
name=f"{self.name}:get-order",
responses={
200: {
"content": {
"TYPE_GEOJSON": {},
},
}
},
response_class=GeoJSONResponse,
tags=["Orders"],
)

Expand Down Expand Up @@ -136,6 +125,7 @@ async def get_orders(self, request: Request) -> list[Order]:
type=TYPE_JSON,
)
)

return orders

async def get_order(self: Self, order_id: str, request: Request) -> Order:
Expand Down
19 changes: 12 additions & 7 deletions tests/backends.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from typing import Mapping
from uuid import uuid4

from fastapi import Request
Expand All @@ -9,8 +8,13 @@
from stapi_fastapi.models.product import Product


class TestRootBackend:
_orders: Mapping[str, Order] = {}
class MockOrderDB(dict[int | str, Order]):
pass


class MockRootBackend:
def __init__(self, orders: MockOrderDB) -> None:
self._orders = orders

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


class TestProductBackend(ProductBackend):
_opportunities: list[Opportunity] = []
_allowed_payloads: list[OpportunityRequest] = []
_orders: Mapping[str, Order] = {}
class MockProductBackend(ProductBackend):
def __init__(self, orders: MockOrderDB) -> None:
self._opportunities: list[Opportunity] = []
self._allowed_payloads: list[OpportunityRequest] = []
self._orders = orders

async def search_opportunities(
self, product: Product, search: OpportunityRequest, request: Request
Expand Down
68 changes: 45 additions & 23 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,48 +12,56 @@
from stapi_fastapi.models.opportunity import (
Opportunity,
OpportunityPropertiesBase,
OpportunityRequest,
)
from stapi_fastapi.models.product import Product, Provider, ProviderRole
from stapi_fastapi.routers.root_router import RootRouter

from .backend import TestProductBackend, TestRootBackend
from .backends import MockOrderDB, MockProductBackend, MockRootBackend


class TestSpotlightProperties(OpportunityPropertiesBase):
off_nadir: int


@pytest.fixture(scope="session")
def base_url() -> Iterator[str]:
yield "http://stapiserver"


@pytest.fixture
def mock_product_test_spotlight(mock_provider_test: Provider) -> Product:
def order_db() -> MockOrderDB:
return MockOrderDB()


@pytest.fixture
def product_backend(order_db: MockOrderDB) -> MockProductBackend:
return MockProductBackend(order_db)


@pytest.fixture
def root_backend(order_db) -> MockRootBackend:
return MockRootBackend(order_db)


@pytest.fixture
def mock_product_test_spotlight(
product_backend: MockProductBackend, mock_provider: Provider
) -> Product:
"""Fixture for a mock Test Spotlight product."""
return Product(
id="test-spotlight",
title="Test Spotlight Product",
description="Test product for test spotlight",
license="CC-BY-4.0",
keywords=["test", "satellite"],
providers=[mock_provider_test],
providers=[mock_provider],
links=[],
constraints=TestSpotlightProperties,
backend=TestProductBackend(),
backend=product_backend,
)


@pytest.fixture(scope="session")
def base_url() -> Iterator[str]:
yield "http://stapiserver"


@pytest.fixture
def product_backend() -> Iterator[TestProductBackend]:
yield TestProductBackend()


@pytest.fixture
def root_backend() -> Iterator[TestRootBackend]:
yield TestRootBackend()


@pytest.fixture
def stapi_client(
root_backend, mock_product_test_spotlight, base_url: str
Expand All @@ -63,7 +71,8 @@ def stapi_client(
app = FastAPI()
app.include_router(root_router, prefix="")

yield TestClient(app, base_url=f"{base_url}")
with TestClient(app, base_url=f"{base_url}") as client:
yield client


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


@pytest.fixture
def opportunities(products: list[Product]) -> Iterator[list[Opportunity]]:
yield [
def opportunities(products: list[Product]) -> list[Opportunity]:
return [
Opportunity(
geometry=Point(type="Point", coordinates=[13.4, 52.5]),
properties={
Expand All @@ -97,7 +106,7 @@ def opportunities(products: list[Product]) -> Iterator[list[Opportunity]]:


@pytest.fixture
def mock_provider_test() -> Provider:
def mock_provider() -> Provider:
return Provider(
name="Test Provider",
description="A provider for Test data",
Expand Down Expand Up @@ -128,3 +137,16 @@ def mock_test_spotlight_opportunities() -> List[Opportunity]:
),
),
]


@pytest.fixture
def allowed_payloads() -> list[OpportunityRequest]:
return [
OpportunityRequest(
geometry=Point(
type="Point", coordinates=Position2D(longitude=13.4, latitude=52.5)
),
datetime=(datetime.now(UTC), datetime.now(UTC)),
filter={},
),
]
25 changes: 10 additions & 15 deletions tests/opportunity_test.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import json
from datetime import UTC, datetime, timedelta
from typing import List

import pytest
from fastapi.testclient import TestClient
from stapi_fastapi.models.opportunity import Opportunity
from stapi_fastapi.models.opportunity import Opportunity, OpportunityCollection

from .backend import TestProductBackend
from .backends import MockProductBackend
from .datetime_interval_test import rfc3339_strftime


@pytest.mark.parametrize("product_id", ["test-spotlight"])
def test_search_opportunities_response(
product_id: str,
mock_test_spotlight_opportunities: List[Opportunity],
product_backend: TestProductBackend,
product_backend: MockProductBackend,
stapi_client: TestClient,
):
) -> None:
product_backend._opportunities = mock_test_spotlight_opportunities

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

print(json.dumps(response.json(), indent=4))

# Validate response status and structure
assert response.status_code == 200, f"Failed for product: {product_id}"
assert isinstance(
response.json(), list
), "Response should be a list of opportunities"
for opportunity in response.json():
assert "id" in opportunity, "Opportunity item should have an 'id' field"
assert (
"properties" in opportunity
), "Opportunity item should have a 'properties' field"
_json = response.json()

try:
OpportunityCollection(**_json)
except Exception as _:
pytest.fail("response is not an opportunity collection")
Loading

0 comments on commit fba52ed

Please sign in to comment.