Files
fastapi/tests/test_request_params/test_default_validation.py
6a5069998a ✅ Add tests for parameter defaults, nullability, and browser form behavior (#14829)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2026-10-19 09:42:19 +02:00

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