You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

120 lines
3.8 KiB

"""
Test that Form-based dependency models preserve model_fields_set correctly.
This test addresses issue https://github.com/fastapi/fastapi/issues/13399
where Form models were pre-filling default values, making it impossible
to distinguish between explicitly set fields and fields using defaults.
"""
from typing import Annotated
import pytest
from fastapi import FastAPI, Form
from fastapi.testclient import TestClient
from pydantic import BaseModel
class ModelWithDefaults(BaseModel):
field_bool: bool = True
field_str: str = "default_value"
field_int: int = 42
app = FastAPI()
@app.post("/json")
async def json_endpoint(model: ModelWithDefaults):
return {
"fields_set": list(model.model_fields_set),
"field_bool": model.field_bool,
"field_str": model.field_str,
"field_int": model.field_int,
}
@app.post("/form")
async def form_endpoint(model: Annotated[ModelWithDefaults, Form()]):
return {
"fields_set": list(model.model_fields_set),
"field_bool": model.field_bool,
"field_str": model.field_str,
"field_int": model.field_int,
}
client = TestClient(app)
def test_json_empty_preserves_fields_set():
"""JSON with empty dict should have empty fields_set"""
response = client.post("/json", json={})
assert response.status_code == 200
data = response.json()
assert data["fields_set"] == []
assert data["field_bool"] is True
assert data["field_str"] == "default_value"
assert data["field_int"] == 42
def test_json_partial_preserves_fields_set():
"""JSON with partial data should only set provided fields"""
response = client.post("/json", json={"field_str": "custom"})
assert response.status_code == 200
data = response.json()
assert data["fields_set"] == ["field_str"]
assert data["field_bool"] is True
assert data["field_str"] == "custom"
assert data["field_int"] == 42
def test_json_all_fields_preserves_fields_set():
"""JSON with all fields should mark all as set"""
response = client.post(
"/json",
json={"field_bool": False, "field_str": "custom", "field_int": 100},
)
assert response.status_code == 200
data = response.json()
assert set(data["fields_set"]) == {"field_bool", "field_str", "field_int"}
assert data["field_bool"] is False
assert data["field_str"] == "custom"
assert data["field_int"] == 100
def test_form_empty_preserves_fields_set():
"""Form with empty data should have empty fields_set (matching JSON behavior)"""
response = client.post("/form", data={})
assert response.status_code == 200
data = response.json()
# This is the key fix: Form should behave like JSON
assert data["fields_set"] == []
assert data["field_bool"] is True
assert data["field_str"] == "default_value"
assert data["field_int"] == 42
def test_form_partial_preserves_fields_set():
"""Form with partial data should only set provided fields (matching JSON behavior)"""
response = client.post("/form", data={"field_str": "custom"})
assert response.status_code == 200
data = response.json()
# This is the key fix: only field_str should be in fields_set
assert data["fields_set"] == ["field_str"]
assert data["field_bool"] is True
assert data["field_str"] == "custom"
assert data["field_int"] == 42
def test_form_all_fields_preserves_fields_set():
"""Form with all fields should mark all as set"""
response = client.post(
"/form",
data={"field_bool": "false", "field_str": "custom", "field_int": "100"},
)
assert response.status_code == 200
data = response.json()
assert set(data["fields_set"]) == {"field_bool", "field_str", "field_int"}
assert data["field_bool"] is False
assert data["field_str"] == "custom"
assert data["field_int"] == 100