Skip to content

Commit 005d54e

Browse files
committed
prep tests fixtures
1 parent a2ff5ce commit 005d54e

File tree

5 files changed

+480
-65
lines changed

5 files changed

+480
-65
lines changed

tests/conftest.py

Lines changed: 5 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,10 @@
1-
"""
2-
Configuration file for pytest.
3-
Contains fixtures and settings for the test suite.
4-
"""
5-
61
import sys
72
import os
8-
from typing import Optional, List
9-
10-
from fastapi import FastAPI, Query, Path, Body
11-
from pydantic import BaseModel
12-
import pytest
133

14-
# Add the parent directory to the path so that we can import the local package
4+
# Add the parent directory to the path
155
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
166

17-
18-
class Item(BaseModel):
19-
id: int
20-
name: str
21-
description: Optional[str] = None
22-
price: float
23-
tags: List[str] = []
24-
25-
26-
@pytest.fixture
27-
def fastapi_app():
28-
app = FastAPI(
29-
title="Test API",
30-
description="A test API app for unit testing",
31-
version="0.1.0",
32-
)
33-
34-
@app.get("/items/", response_model=List[Item], tags=["items"], operation_id="list_items")
35-
async def list_items(
36-
skip: int = Query(0, description="Number of items to skip"),
37-
limit: int = Query(10, description="Max number of items to return"),
38-
sort_by: Optional[str] = Query(None, description="Field to sort by"),
39-
):
40-
"""List all items with pagination and sorting options."""
41-
return []
42-
43-
@app.get("/items/{item_id}", response_model=Item, tags=["items"], operation_id="get_item")
44-
async def read_item(
45-
item_id: int = Path(..., description="The ID of the item to retrieve"),
46-
include_details: bool = Query(False, description="Include additional details"),
47-
):
48-
"""Get a specific item by its ID with optional details."""
49-
return {"id": item_id, "name": "Test Item", "price": 10.0}
50-
51-
@app.post("/items/", response_model=Item, tags=["items"], operation_id="create_item")
52-
async def create_item(item: Item = Body(..., description="The item to create")):
53-
"""Create a new item in the database."""
54-
return item
55-
56-
@app.put("/items/{item_id}", response_model=Item, tags=["items"], operation_id="update_item")
57-
async def update_item(
58-
item_id: int = Path(..., description="The ID of the item to update"),
59-
item: Item = Body(..., description="The updated item data"),
60-
):
61-
"""Update an existing item."""
62-
item.id = item_id
63-
return item
64-
65-
@app.delete("/items/{item_id}", tags=["items"], operation_id="delete_item")
66-
async def delete_item(item_id: int = Path(..., description="The ID of the item to delete")):
67-
"""Delete an item from the database."""
68-
return {"message": "Item deleted successfully"}
69-
70-
return app
7+
from .fixtures import types # noqa: F401
8+
from .fixtures import example_data # noqa: F401
9+
from .fixtures import simple_app # noqa: F401
10+
from .fixtures import complex_app # noqa: F401

tests/fixtures/complex_app.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
from typing import Optional, List, Dict, Any, Union
2+
from uuid import UUID
3+
4+
from fastapi import FastAPI, Query, Path, Body, Header, Cookie
5+
import pytest
6+
7+
from .types import (
8+
Product,
9+
Customer,
10+
OrderResponse,
11+
Address,
12+
PaginatedResponse,
13+
ProductCategory,
14+
OrderRequest,
15+
ErrorResponse,
16+
)
17+
18+
19+
@pytest.fixture
20+
def complex_fastapi_app(
21+
example_product: Product,
22+
example_customer: Customer,
23+
example_order_response: OrderResponse,
24+
example_address: Address,
25+
) -> FastAPI:
26+
app = FastAPI(
27+
title="Complex E-Commerce API",
28+
description="A more complex API with nested models and various schemas",
29+
version="1.0.0",
30+
)
31+
32+
@app.get(
33+
"/products",
34+
response_model=PaginatedResponse,
35+
tags=["products"],
36+
operation_id="list_products",
37+
response_model_exclude_none=True,
38+
)
39+
async def list_products(
40+
category: Optional[ProductCategory] = Query(None, description="Filter by product category"),
41+
min_price: Optional[float] = Query(None, description="Minimum price filter", gt=0),
42+
max_price: Optional[float] = Query(None, description="Maximum price filter", gt=0),
43+
tag: Optional[List[str]] = Query(None, description="Filter by tags"),
44+
sort_by: str = Query("created_at", description="Field to sort by"),
45+
sort_direction: str = Query("desc", description="Sort direction (asc or desc)"),
46+
in_stock_only: bool = Query(False, description="Show only in-stock products"),
47+
page: int = Query(1, description="Page number", ge=1),
48+
size: int = Query(20, description="Page size", ge=1, le=100),
49+
user_agent: Optional[str] = Header(None, description="User agent header"),
50+
):
51+
"""
52+
List products with various filtering, sorting and pagination options.
53+
Returns a paginated response of products.
54+
"""
55+
return PaginatedResponse(items=[example_product], total=1, page=page, size=size, pages=1)
56+
57+
@app.get(
58+
"/products/{product_id}",
59+
response_model=Product,
60+
tags=["products"],
61+
operation_id="get_product",
62+
responses={
63+
404: {"model": ErrorResponse, "description": "Product not found"},
64+
},
65+
)
66+
async def get_product(
67+
product_id: UUID = Path(..., description="The ID of the product to retrieve"),
68+
include_unavailable: bool = Query(False, description="Include product even if not available"),
69+
):
70+
"""
71+
Get detailed information about a specific product by its ID.
72+
Includes all variants, images, and metadata.
73+
"""
74+
# Just returning the example product with the requested ID
75+
product_copy = example_product.model_copy()
76+
product_copy.id = product_id
77+
return product_copy
78+
79+
@app.post(
80+
"/orders",
81+
response_model=OrderResponse,
82+
tags=["orders"],
83+
operation_id="create_order",
84+
status_code=201,
85+
responses={
86+
400: {"model": ErrorResponse, "description": "Invalid order data"},
87+
404: {"model": ErrorResponse, "description": "Customer or product not found"},
88+
422: {"model": ErrorResponse, "description": "Validation error"},
89+
},
90+
)
91+
async def create_order(
92+
order: OrderRequest = Body(..., description="Order details"),
93+
user_id: Optional[UUID] = Cookie(None, description="User ID from cookie"),
94+
authorization: Optional[str] = Header(None, description="Authorization header"),
95+
):
96+
"""
97+
Create a new order with multiple items, shipping details, and payment information.
98+
Returns the created order with full details including status and tracking information.
99+
"""
100+
# Return a copy of the example order response with the customer ID from the request
101+
order_copy = example_order_response.model_copy()
102+
order_copy.customer_id = order.customer_id
103+
order_copy.items = order.items
104+
return order_copy
105+
106+
@app.get(
107+
"/customers/{customer_id}",
108+
response_model=Union[Customer, Dict[str, Any]],
109+
tags=["customers"],
110+
operation_id="get_customer",
111+
responses={
112+
404: {"model": ErrorResponse, "description": "Customer not found"},
113+
403: {"model": ErrorResponse, "description": "Forbidden access"},
114+
},
115+
)
116+
async def get_customer(
117+
customer_id: UUID = Path(..., description="The ID of the customer to retrieve"),
118+
include_orders: bool = Query(False, description="Include customer's order history"),
119+
include_payment_methods: bool = Query(False, description="Include customer's saved payment methods"),
120+
fields: List[str] = Query(None, description="Specific fields to include in response"),
121+
):
122+
"""
123+
Get detailed information about a specific customer by ID.
124+
Can include additional related information like order history.
125+
"""
126+
# Return a copy of the example customer with the requested ID
127+
customer_copy = example_customer.model_copy()
128+
customer_copy.id = customer_id
129+
return customer_copy
130+
131+
return app

tests/fixtures/example_data.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
from datetime import datetime, date
2+
from uuid import UUID
3+
4+
import pytest
5+
6+
from .types import (
7+
Address,
8+
ProductVariant,
9+
Product,
10+
ProductCategory,
11+
Customer,
12+
CustomerTier,
13+
OrderItem,
14+
PaymentDetails,
15+
OrderRequest,
16+
OrderResponse,
17+
PaginatedResponse,
18+
OrderStatus,
19+
PaymentMethod,
20+
)
21+
22+
23+
@pytest.fixture
24+
def example_address() -> Address:
25+
return Address(street="123 Main St", city="Anytown", state="CA", postal_code="12345", country="US", is_primary=True)
26+
27+
28+
@pytest.fixture
29+
def example_product_variant() -> ProductVariant:
30+
return ProductVariant(
31+
sku="EP-001-BLK", color="Black", stock_count=10, size=None, weight=None, dimensions=None, in_stock=True
32+
)
33+
34+
35+
@pytest.fixture
36+
def example_product(example_product_variant) -> Product:
37+
return Product(
38+
id=UUID("550e8400-e29b-41d4-a716-446655440000"),
39+
name="Example Product",
40+
description="This is an example product",
41+
category=ProductCategory.ELECTRONICS,
42+
price=199.99,
43+
discount_percent=None,
44+
tax_rate=None,
45+
rating=None,
46+
review_count=0,
47+
tags=["example", "new"],
48+
image_urls=["https://example.com/image.jpg"],
49+
created_at=datetime.now(),
50+
variants=[example_product_variant],
51+
)
52+
53+
54+
@pytest.fixture
55+
def example_customer(example_address) -> Customer:
56+
return Customer(
57+
id=UUID("770f9511-f39c-42d5-a860-557654551222"),
58+
59+
full_name="John Doe",
60+
phone="1234567890",
61+
tier=CustomerTier.STANDARD,
62+
addresses=[example_address],
63+
created_at=datetime.now(),
64+
preferences={"theme": "dark", "notifications": True},
65+
consent={"marketing": True, "analytics": True},
66+
)
67+
68+
69+
@pytest.fixture
70+
def example_order_item() -> OrderItem:
71+
return OrderItem(
72+
product_id=UUID("550e8400-e29b-41d4-a716-446655440000"),
73+
variant_sku="EP-001-BLK",
74+
quantity=2,
75+
unit_price=199.99,
76+
discount_amount=10.00,
77+
total=389.98,
78+
)
79+
80+
81+
@pytest.fixture
82+
def example_payment_details() -> PaymentDetails:
83+
return PaymentDetails(
84+
method=PaymentMethod.CREDIT_CARD,
85+
transaction_id="txn_12345",
86+
status="completed",
87+
amount=389.98,
88+
currency="USD",
89+
paid_at=datetime.now(),
90+
)
91+
92+
93+
@pytest.fixture
94+
def example_order_request(example_order_item) -> OrderRequest:
95+
return OrderRequest(
96+
customer_id=UUID("770f9511-f39c-42d5-a860-557654551222"),
97+
items=[example_order_item],
98+
shipping_address_id=UUID("880f9511-f39c-42d5-a860-557654551333"),
99+
billing_address_id=None,
100+
payment_method=PaymentMethod.CREDIT_CARD,
101+
notes="Please deliver before 6pm",
102+
use_loyalty_points=False,
103+
)
104+
105+
106+
@pytest.fixture
107+
def example_order_response(example_order_item, example_address, example_payment_details) -> OrderResponse:
108+
return OrderResponse(
109+
id=UUID("660f9511-f39c-42d5-a860-557654551111"),
110+
customer_id=UUID("770f9511-f39c-42d5-a860-557654551222"),
111+
status=OrderStatus.PENDING,
112+
items=[example_order_item],
113+
shipping_address=example_address,
114+
billing_address=example_address,
115+
payment=example_payment_details,
116+
subtotal=389.98,
117+
shipping_cost=10.0,
118+
tax_amount=20.0,
119+
discount_amount=10.0,
120+
total_amount=409.98,
121+
tracking_number="TRK123456789",
122+
estimated_delivery=date.today(),
123+
created_at=datetime.now(),
124+
notes="Please deliver before 6pm",
125+
metadata={},
126+
)
127+
128+
129+
@pytest.fixture
130+
def example_paginated_products(example_product) -> PaginatedResponse:
131+
return PaginatedResponse(items=[example_product], total=1, page=1, size=20, pages=1)

tests/fixtures/simple_app.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from typing import Optional, List
2+
3+
from fastapi import FastAPI, Query, Path, Body
4+
import pytest
5+
6+
from .types import Item
7+
8+
9+
@pytest.fixture
10+
def simple_fastapi_app() -> FastAPI:
11+
app = FastAPI(
12+
title="Test API",
13+
description="A test API app for unit testing",
14+
version="0.1.0",
15+
)
16+
17+
@app.get("/items/", response_model=List[Item], tags=["items"], operation_id="list_items")
18+
async def list_items(
19+
skip: int = Query(0, description="Number of items to skip"),
20+
limit: int = Query(10, description="Max number of items to return"),
21+
sort_by: Optional[str] = Query(None, description="Field to sort by"),
22+
):
23+
"""List all items with pagination and sorting options."""
24+
return []
25+
26+
@app.get("/items/{item_id}", response_model=Item, tags=["items"], operation_id="get_item")
27+
async def read_item(
28+
item_id: int = Path(..., description="The ID of the item to retrieve"),
29+
include_details: bool = Query(False, description="Include additional details"),
30+
):
31+
"""Get a specific item by its ID with optional details."""
32+
return {"id": item_id, "name": "Test Item", "price": 10.0}
33+
34+
@app.post("/items/", response_model=Item, tags=["items"], operation_id="create_item")
35+
async def create_item(item: Item = Body(..., description="The item to create")):
36+
"""Create a new item in the database."""
37+
return item
38+
39+
@app.put("/items/{item_id}", response_model=Item, tags=["items"], operation_id="update_item")
40+
async def update_item(
41+
item_id: int = Path(..., description="The ID of the item to update"),
42+
item: Item = Body(..., description="The updated item data"),
43+
):
44+
"""Update an existing item."""
45+
item.id = item_id
46+
return item
47+
48+
@app.delete("/items/{item_id}", tags=["items"], operation_id="delete_item")
49+
async def delete_item(item_id: int = Path(..., description="The ID of the item to delete")):
50+
"""Delete an item from the database."""
51+
return {"message": "Item deleted successfully"}
52+
53+
return app

0 commit comments

Comments
 (0)