1 changed files with 320 additions and 0 deletions
@ -0,0 +1,320 @@ |
|||||
|
from typing import Annotated, Optional |
||||
|
|
||||
|
from fastapi import FastAPI |
||||
|
from fastapi._compat._params_v1 import Body, Cookie, Header, Path, Query |
||||
|
from fastapi._compat.v1 import BaseModel |
||||
|
from fastapi.testclient import TestClient |
||||
|
from inline_snapshot import snapshot |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
price: float |
||||
|
description: Optional[str] = None |
||||
|
|
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
@app.get("/items/{item_id}") |
||||
|
def get_item_with_path( |
||||
|
item_id: Annotated[int, Path(title="The ID of the item", ge=1, le=1000)], |
||||
|
): |
||||
|
return {"item_id": item_id} |
||||
|
|
||||
|
|
||||
|
@app.get("/items/") |
||||
|
def get_items_with_query( |
||||
|
q: Annotated[ |
||||
|
Optional[str], Query(min_length=3, max_length=50, pattern="^[a-zA-Z0-9 ]+$") |
||||
|
] = None, |
||||
|
skip: Annotated[int, Query(ge=0)] = 0, |
||||
|
limit: Annotated[int, Query(ge=1, le=100)] = 10, |
||||
|
): |
||||
|
return {"q": q, "skip": skip, "limit": limit} |
||||
|
|
||||
|
|
||||
|
@app.get("/users/") |
||||
|
def get_user_with_header( |
||||
|
x_custom: Annotated[Optional[str], Header()] = None, |
||||
|
x_token: Annotated[Optional[str], Header(convert_underscores=True)] = None, |
||||
|
): |
||||
|
return {"x_custom": x_custom, "x_token": x_token} |
||||
|
|
||||
|
|
||||
|
@app.get("/cookies/") |
||||
|
def get_cookies( |
||||
|
session_id: Annotated[Optional[str], Cookie()] = None, |
||||
|
tracking_id: Annotated[Optional[str], Cookie(min_length=10)] = None, |
||||
|
): |
||||
|
return {"session_id": session_id, "tracking_id": tracking_id} |
||||
|
|
||||
|
|
||||
|
@app.post("/items/") |
||||
|
def create_item( |
||||
|
item: Annotated[ |
||||
|
Item, |
||||
|
Body(examples=[{"name": "Foo", "price": 35.4, "description": "The Foo item"}]), |
||||
|
], |
||||
|
): |
||||
|
return {"item": item} |
||||
|
|
||||
|
|
||||
|
@app.post("/items-embed/") |
||||
|
def create_item_embed( |
||||
|
item: Annotated[Item, Body(embed=True)], |
||||
|
): |
||||
|
return {"item": item} |
||||
|
|
||||
|
|
||||
|
@app.put("/items/{item_id}") |
||||
|
def update_item( |
||||
|
item_id: Annotated[int, Path(ge=1)], |
||||
|
item: Annotated[Item, Body()], |
||||
|
importance: Annotated[int, Body(gt=0, le=10)], |
||||
|
): |
||||
|
return {"item": item, "importance": importance} |
||||
|
|
||||
|
|
||||
|
client = TestClient(app) |
||||
|
|
||||
|
|
||||
|
# Path parameter tests |
||||
|
def test_path_param_valid(): |
||||
|
response = client.get("/items/50") |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == {"item_id": 50} |
||||
|
|
||||
|
|
||||
|
def test_path_param_too_large(): |
||||
|
response = client.get("/items/1001") |
||||
|
assert response.status_code == 422 |
||||
|
error = response.json()["detail"][0] |
||||
|
assert error["loc"] == ["path", "item_id"] |
||||
|
|
||||
|
|
||||
|
def test_path_param_too_small(): |
||||
|
response = client.get("/items/0") |
||||
|
assert response.status_code == 422 |
||||
|
error = response.json()["detail"][0] |
||||
|
assert error["loc"] == ["path", "item_id"] |
||||
|
|
||||
|
|
||||
|
# Query parameter tests |
||||
|
def test_query_params_valid(): |
||||
|
response = client.get("/items/?q=test search&skip=5&limit=20") |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == {"q": "test search", "skip": 5, "limit": 20} |
||||
|
|
||||
|
|
||||
|
def test_query_params_defaults(): |
||||
|
response = client.get("/items/") |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == {"q": None, "skip": 0, "limit": 10} |
||||
|
|
||||
|
|
||||
|
def test_query_param_too_short(): |
||||
|
response = client.get("/items/?q=ab") |
||||
|
assert response.status_code == 422 |
||||
|
error = response.json()["detail"][0] |
||||
|
assert error["loc"] == ["query", "q"] |
||||
|
|
||||
|
|
||||
|
def test_query_param_invalid_pattern(): |
||||
|
response = client.get("/items/?q=test@#$") |
||||
|
assert response.status_code == 422 |
||||
|
error = response.json()["detail"][0] |
||||
|
assert error["loc"] == ["query", "q"] |
||||
|
|
||||
|
|
||||
|
def test_query_param_limit_too_large(): |
||||
|
response = client.get("/items/?limit=101") |
||||
|
assert response.status_code == 422 |
||||
|
error = response.json()["detail"][0] |
||||
|
assert error["loc"] == ["query", "limit"] |
||||
|
|
||||
|
|
||||
|
# Header parameter tests |
||||
|
def test_header_params(): |
||||
|
response = client.get( |
||||
|
"/users/", |
||||
|
headers={"X-Custom": "Plumbus", "X-Token": "secret-token"}, |
||||
|
) |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == { |
||||
|
"x_custom": "Plumbus", |
||||
|
"x_token": "secret-token", |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def test_header_underscore_conversion(): |
||||
|
response = client.get( |
||||
|
"/users/", |
||||
|
headers={"x-token": "secret-token-with-dash"}, |
||||
|
) |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json()["x_token"] == "secret-token-with-dash" |
||||
|
|
||||
|
|
||||
|
def test_header_params_none(): |
||||
|
response = client.get("/users/") |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == {"x_custom": None, "x_token": None} |
||||
|
|
||||
|
|
||||
|
# Cookie parameter tests |
||||
|
def test_cookie_params(): |
||||
|
with TestClient(app) as client: |
||||
|
client.cookies.set("session_id", "abc123") |
||||
|
client.cookies.set("tracking_id", "1234567890abcdef") |
||||
|
response = client.get("/cookies/") |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == { |
||||
|
"session_id": "abc123", |
||||
|
"tracking_id": "1234567890abcdef", |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def test_cookie_tracking_id_too_short(): |
||||
|
with TestClient(app) as client: |
||||
|
client.cookies.set("tracking_id", "short") |
||||
|
response = client.get("/cookies/") |
||||
|
assert response.status_code == 422 |
||||
|
assert response.json() == snapshot( |
||||
|
{ |
||||
|
"detail": [ |
||||
|
{ |
||||
|
"loc": ["cookie", "tracking_id"], |
||||
|
"msg": "ensure this value has at least 10 characters", |
||||
|
"type": "value_error.any_str.min_length", |
||||
|
"ctx": {"limit_value": 10}, |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def test_cookie_params_none(): |
||||
|
response = client.get("/cookies/") |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == {"session_id": None, "tracking_id": None} |
||||
|
|
||||
|
|
||||
|
# Body parameter tests |
||||
|
def test_body_param(): |
||||
|
response = client.post( |
||||
|
"/items/", |
||||
|
json={"name": "Test Item", "price": 29.99, "description": "A test item"}, |
||||
|
) |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == { |
||||
|
"item": { |
||||
|
"name": "Test Item", |
||||
|
"price": 29.99, |
||||
|
"description": "A test item", |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def test_body_param_minimal(): |
||||
|
response = client.post( |
||||
|
"/items/", |
||||
|
json={"name": "Minimal", "price": 9.99}, |
||||
|
) |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == { |
||||
|
"item": {"name": "Minimal", "price": 9.99, "description": None} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def test_body_param_missing_required(): |
||||
|
response = client.post( |
||||
|
"/items/", |
||||
|
json={"name": "Incomplete"}, |
||||
|
) |
||||
|
assert response.status_code == 422 |
||||
|
error = response.json()["detail"][0] |
||||
|
assert error["loc"] == ["body", "price"] |
||||
|
|
||||
|
|
||||
|
def test_body_embed(): |
||||
|
response = client.post( |
||||
|
"/items-embed/", |
||||
|
json={"item": {"name": "Embedded", "price": 15.0}}, |
||||
|
) |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == { |
||||
|
"item": {"name": "Embedded", "price": 15.0, "description": None} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def test_body_embed_wrong_structure(): |
||||
|
response = client.post( |
||||
|
"/items-embed/", |
||||
|
json={"name": "Not Embedded", "price": 15.0}, |
||||
|
) |
||||
|
assert response.status_code == 422 |
||||
|
|
||||
|
|
||||
|
# Multiple body parameters test |
||||
|
def test_multiple_body_params(): |
||||
|
response = client.put( |
||||
|
"/items/5", |
||||
|
json={ |
||||
|
"item": {"name": "Updated Item", "price": 49.99}, |
||||
|
"importance": 8, |
||||
|
}, |
||||
|
) |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == snapshot( |
||||
|
{ |
||||
|
"item": {"name": "Updated Item", "price": 49.99, "description": None}, |
||||
|
"importance": 8, |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def test_multiple_body_params_importance_too_large(): |
||||
|
response = client.put( |
||||
|
"/items/5", |
||||
|
json={ |
||||
|
"item": {"name": "Item", "price": 10.0}, |
||||
|
"importance": 11, |
||||
|
}, |
||||
|
) |
||||
|
assert response.status_code == 422 |
||||
|
assert response.json() == snapshot( |
||||
|
{ |
||||
|
"detail": [ |
||||
|
{ |
||||
|
"loc": ["body", "importance"], |
||||
|
"msg": "ensure this value is less than or equal to 10", |
||||
|
"type": "value_error.number.not_le", |
||||
|
"ctx": {"limit_value": 10}, |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def test_multiple_body_params_importance_too_small(): |
||||
|
response = client.put( |
||||
|
"/items/5", |
||||
|
json={ |
||||
|
"item": {"name": "Item", "price": 10.0}, |
||||
|
"importance": 0, |
||||
|
}, |
||||
|
) |
||||
|
assert response.status_code == 422 |
||||
|
assert response.json() == snapshot( |
||||
|
{ |
||||
|
"detail": [ |
||||
|
{ |
||||
|
"loc": ["body", "importance"], |
||||
|
"msg": "ensure this value is greater than 0", |
||||
|
"type": "value_error.number.not_gt", |
||||
|
"ctx": {"limit_value": 0}, |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
) |
||||
Loading…
Reference in new issue