mirror of
https://github.com/fastapi/fastapi.git
synced 2026-10-29 11:14:41 +08:00
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
186 lines
5.4 KiB
Python
186 lines
5.4 KiB
Python
from typing import Annotated
|
|
|
|
import pytest
|
|
from fastapi import Body, Cookie, FastAPI, Form, Header, Query
|
|
from fastapi.testclient import TestClient
|
|
from pydantic import BaseModel, BeforeValidator, Field
|
|
|
|
|
|
def double(value):
|
|
return 0 if value is None else int(value) * 2
|
|
|
|
|
|
ValidatedInt = Annotated[int | None, BeforeValidator(double)]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"parameter",
|
|
[
|
|
pytest.param(
|
|
parameter,
|
|
marks=pytest.mark.xfail(
|
|
reason="Missing values are validated as explicitly supplied defaults",
|
|
raises=AssertionError,
|
|
),
|
|
)
|
|
for parameter in (Query, Header, Cookie, Form)
|
|
]
|
|
+ [Body],
|
|
)
|
|
@pytest.mark.parametrize("path", ["/parameter", "/model"])
|
|
def test_missing_value_uses_default_without_validation(parameter, path):
|
|
app = FastAPI()
|
|
field = Body(embed=True) if parameter is Body else parameter()
|
|
|
|
@app.post("/parameter")
|
|
def read_parameter(value: Annotated[ValidatedInt, field] = 10):
|
|
return value
|
|
|
|
class Model(BaseModel):
|
|
value: ValidatedInt = 10
|
|
|
|
@app.post("/model")
|
|
def read_model(value: Annotated[Model, parameter()]):
|
|
return value.value
|
|
|
|
client = TestClient(app)
|
|
if parameter is Body:
|
|
response = client.post(path, json={})
|
|
else:
|
|
response = client.post(path)
|
|
assert response.status_code == 200
|
|
assert response.json() == 10
|
|
|
|
|
|
@pytest.mark.parametrize("parameter", [Query, Header, Cookie, Form, Body])
|
|
@pytest.mark.parametrize("path", ["/parameter", "/model"])
|
|
def test_missing_value_uses_none_default_without_validation(parameter, path):
|
|
app = FastAPI()
|
|
field = Body(embed=True) if parameter is Body else parameter()
|
|
|
|
@app.post("/parameter")
|
|
def read_parameter(value: Annotated[ValidatedInt, field] = None):
|
|
return value
|
|
|
|
class Model(BaseModel):
|
|
value: ValidatedInt = None
|
|
|
|
@app.post("/model")
|
|
def read_model(value: Annotated[Model, parameter()]):
|
|
return value.value
|
|
|
|
client = TestClient(app)
|
|
if parameter is Body:
|
|
response = client.post(path, json={})
|
|
else:
|
|
response = client.post(path)
|
|
assert response.status_code == 200
|
|
assert response.json() is None
|
|
|
|
|
|
@pytest.mark.parametrize("parameter", [Query, Header, Cookie, Form, Body])
|
|
@pytest.mark.parametrize("path", ["/parameter", "/model"])
|
|
def test_explicit_value_equal_to_default_is_validated(parameter, path):
|
|
app = FastAPI()
|
|
field = Body(embed=True) if parameter is Body else parameter()
|
|
|
|
@app.post("/parameter")
|
|
def read_parameter(value: Annotated[ValidatedInt, field] = 10):
|
|
return value
|
|
|
|
class Model(BaseModel):
|
|
value: ValidatedInt = 10
|
|
|
|
@app.post("/model")
|
|
def read_model(value: Annotated[Model, parameter()]):
|
|
return value.value
|
|
|
|
client = TestClient(app)
|
|
if parameter is Query:
|
|
response = client.post(path, params={"value": "10"})
|
|
elif parameter is Header:
|
|
response = client.post(path, headers={"value": "10"})
|
|
elif parameter is Cookie:
|
|
response = client.post(path, headers={"cookie": "value=10"})
|
|
elif parameter is Form:
|
|
response = client.post(path, data={"value": "10"})
|
|
else:
|
|
response = client.post(path, json={"value": 10})
|
|
assert response.status_code == 200
|
|
assert response.json() == 20
|
|
|
|
|
|
@pytest.mark.parametrize("default", [None, 10])
|
|
@pytest.mark.parametrize(
|
|
"path",
|
|
[
|
|
pytest.param(
|
|
"/parameter",
|
|
marks=pytest.mark.xfail(
|
|
reason="Explicit null is treated as missing for individual Body parameters",
|
|
raises=AssertionError,
|
|
),
|
|
),
|
|
"/model",
|
|
],
|
|
)
|
|
def test_explicit_null_is_validated(default, path):
|
|
app = FastAPI()
|
|
field = Body(embed=True)
|
|
|
|
@app.post("/parameter")
|
|
def read_parameter(value: Annotated[ValidatedInt, field] = default):
|
|
return value
|
|
|
|
class Model(BaseModel):
|
|
value: ValidatedInt = default
|
|
|
|
@app.post("/model")
|
|
def read_model(value: Annotated[Model, Body()]):
|
|
return value.value
|
|
|
|
client = TestClient(app)
|
|
response = client.post(path, json={"value": None})
|
|
assert response.status_code == 200
|
|
assert response.json() == 0
|
|
|
|
|
|
@pytest.mark.parametrize("default", [None, 10])
|
|
@pytest.mark.xfail(
|
|
reason="Explicit null-body is treated as missing", raises=AssertionError
|
|
)
|
|
def test_explicit_null_body_is_validated(default):
|
|
app = FastAPI()
|
|
|
|
@app.post("/")
|
|
def read_body(value: Annotated[ValidatedInt, Body()] = default):
|
|
return value
|
|
|
|
client = TestClient(app)
|
|
response = client.post(
|
|
"/", content="null", headers={"content-type": "application/json"}
|
|
)
|
|
assert response.status_code == 200
|
|
assert response.json() == 0
|
|
|
|
|
|
@pytest.mark.parametrize("parameter", [Query, Header, Cookie, Form, Body])
|
|
@pytest.mark.parametrize("default,expected", [(None, 0), (10, 20)])
|
|
def test_model_can_validate_default(parameter, default, expected):
|
|
app = FastAPI()
|
|
|
|
class Model(BaseModel):
|
|
value: Annotated[ValidatedInt, Field(validate_default=True)] = default
|
|
|
|
@app.post("/")
|
|
def read_model(value: Annotated[Model, parameter()]):
|
|
return value.value
|
|
|
|
client = TestClient(app)
|
|
if parameter is Body:
|
|
response = client.post("/", json={})
|
|
else:
|
|
response = client.post("/")
|
|
assert response.status_code == 200
|
|
assert response.json() == expected
|