From 47a57deb5acfc6bd865d702b46f1aca542ed15af Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Tue, 4 Nov 2025 20:48:42 +0100 Subject: [PATCH] Update `test_form_field_aliases.py` --- tests/test_form_field_aliases.py | 318 +++++++++++++++++++++++++------ 1 file changed, 258 insertions(+), 60 deletions(-) diff --git a/tests/test_form_field_aliases.py b/tests/test_form_field_aliases.py index 267c7f8b8..39920d7f6 100644 --- a/tests/test_form_field_aliases.py +++ b/tests/test_form_field_aliases.py @@ -1,20 +1,21 @@ from typing import Optional import pytest +from dirty_equals import IsOneOf from fastapi import FastAPI, Form from fastapi.testclient import TestClient app = FastAPI() # ===================================================================================== -# alias +# Form(alias=...) # Current situation: Works, but schema is wrong # ------------------------------ # required field -@app.post("/required-field-alias") +@app.post("/required-field-alias", operation_id="required_field_alias") async def required_field_alias(param: str = Form(alias="param_alias")): return {"param": param} @@ -23,6 +24,12 @@ def test_required_field_alias_by_name(): client = TestClient(app) resp = client.post("/required-field-alias", data={"param": "123"}) assert resp.status_code == 422 + detail = resp.json()["detail"] + assert detail[0]["msg"] == IsOneOf( + "Field required", + "field required", # TODO: remove when deprecating Pydantic v1 + ) + assert "param_alias" in detail[0]["loc"] def test_required_field_alias_by_alias(): @@ -32,20 +39,26 @@ def test_required_field_alias_by_alias(): assert resp.json() == {"param": "123"} -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_required_field_alias_schema(): openapi = app.openapi() body_schema = openapi["components"]["schemas"]["Body_required_field_alias"] - assert len(body_schema["properties"]) == 1 - assert "param_alias" in body_schema["properties"] + assert body_schema["properties"] == { + "param_alias": {"title": "Param Alias", "type": "string"} + } + # Currently fails due to issue with aliases: + # AssertionError: assert {'param': {'type': 'string', 'title': 'Param'}} == + # {'param_alias': {'title': 'Param Alias', 'type': 'string'}} # ------------------------------ # optional field -@app.post("/optional-field-alias") -async def optional_field_alias(param: Optional[str] = Form(None, alias="param_alias")): +@app.post("/optional-field-alias", operation_id="optional_field_alias") +async def optional_field_alias( + param: Optional[str] = Form(None, alias="param_alias"), +): return {"param": param} @@ -63,19 +76,27 @@ def test_optional_field_alias_by_alias(): assert resp.json() == {"param": "123"} -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_field_alias_schema(): openapi = app.openapi() body_schema = openapi["components"]["schemas"]["Body_optional_field_alias"] - assert len(body_schema["properties"]) == 1 - assert "param_alias" in body_schema["properties"] + assert body_schema["properties"] == { + "param_alias": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Param Alias", + } + } + # Currently fails due to issue with aliases: + # AssertionError: assert + # {'param': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Param'}} == + # {'param_alias': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Param Alias'}} # ------------------------------ # list field -@app.post("/list-field-alias") +@app.post("/list-field-alias", operation_id="list_field_alias") async def list_field_alias(param: list[str] = Form(alias="param_alias")): return {"param": param} @@ -84,6 +105,12 @@ def test_list_field_alias_by_name(): client = TestClient(app) resp = client.post("/list-field-alias", data={"param": ["123", "456"]}) assert resp.status_code == 422 + detail = resp.json()["detail"] + assert detail[0]["msg"] == IsOneOf( + "Field required", + "field required", # TODO: remove when deprecating Pydantic v1 + ) + assert "param_alias" in detail[0]["loc"] def test_list_field_alias_by_alias(): @@ -93,19 +120,28 @@ def test_list_field_alias_by_alias(): assert resp.json() == {"param": ["123", "456"]} -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_list_field_alias_schema(): openapi = app.openapi() body_schema = openapi["components"]["schemas"]["Body_list_field_alias"] - assert len(body_schema["properties"]) == 1 - assert "param_alias" in body_schema["properties"] + assert body_schema["properties"] == { + "param_alias": { + "title": "Param Alias", + "type": "array", + "items": {"type": "string"}, + } + } + # Currently fails due to issue with aliases: + # AssertionError: assert + # {'param': {'items': {'type': 'string'}, 'type': 'array', 'title': 'Param'}} == + # {'param_alias': {'title': 'Param Alias', 'type': 'array', 'items': {'type': 'string'}}} # ------------------------------ # optional list field -@app.post("/optional-list-field-alias") +@app.post("/optional-list-field-alias", operation_id="optional_list_field_alias") async def optional_list_field_alias( param: Optional[list[str]] = Form(None, alias="param_alias"), ): @@ -128,17 +164,25 @@ def test_optional_list_field_alias_by_alias(): assert resp.json() == {"param": ["123", "456"]} -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_list_field_alias_schema(): openapi = app.openapi() body_schema = openapi["components"]["schemas"]["Body_optional_list_field_alias"] - assert len(body_schema["properties"]) == 1 - assert "param_alias" in body_schema["properties"] + assert body_schema["properties"] == { + "param_alias": { + "anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], + "title": "Param Alias", + }, + } + # Currently fails due to issue with aliases: + # AssertionError: assert + # {'param': {'anyOf': [{'items': {'type': 'string'}, 'type': 'array'}, {'type': 'null'}], 'title': 'Param'}} == + # {'param_alias': {'anyOf': [{'items': {'type': 'string'}, 'type': 'array'}, {'type': 'null'}], 'title': 'Param Alias'}} # ===================================================================================== -# validation alias -# Current situation: schema is correct, but doesn't work (name is used) +# Form(validation_alias=...) +# Current situation: schema is correct, but doesn't work (parameter name is used) # ------------------------------ @@ -154,20 +198,32 @@ async def required_field_validation_alias( return {"param": param} -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_required_field_validation_alias_by_name(): client = TestClient(app) resp = client.post("/required-field-validation-alias", data={"param": "123"}) assert resp.status_code == 422 + # Currently fails due to issue with aliases: + # AssertionError: assert 200 == 422 + detail = resp.json()["detail"] + assert detail[0]["msg"] == IsOneOf( + "Field required", + "field required", # TODO: remove when deprecating Pydantic v1 + ) + assert "param_val_alias" in detail[0]["loc"] -@pytest.mark.xfail + +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_required_field_validation_alias_by_alias(): client = TestClient(app) resp = client.post( "/required-field-validation-alias", data={"param_val_alias": "123"} ) assert resp.status_code == 200 + # Currently fails due to issue with aliases: + # AssertionError: assert 422 == 200 + assert resp.json() == {"param": "123"} @@ -176,9 +232,9 @@ def test_required_field_validation_alias_schema(): body_schema = openapi["components"]["schemas"][ "Body_required_field_validation_alias" ] - print(body_schema) - assert len(body_schema["properties"]) == 1 - assert "param_val_alias" in body_schema["properties"] + assert body_schema["properties"] == { + "param_val_alias": {"title": "Param Val Alias", "type": "string"}, + } # ------------------------------ @@ -194,14 +250,16 @@ async def optional_field_validation_alias( return {"param": param} -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_field_validation_alias_by_name(): client = TestClient(app) resp = client.post("/optional-field-validation-alias", data={"param": "123"}) - assert resp.status_code == 422 + assert resp.json() == {"param": None} + # Currently fails due to issue with aliases: + # AssertionError: assert {'param': '123'} == {'param': None} -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_field_validation_alias_by_alias(): client = TestClient(app) resp = client.post( @@ -209,6 +267,8 @@ def test_optional_field_validation_alias_by_alias(): ) assert resp.status_code == 200 assert resp.json() == {"param": "123"} + # Currently fails due to issue with aliases: + # AssertionError: assert {'param': None} == {'param': '123'} def test_optional_field_validation_alias_schema(): @@ -216,8 +276,12 @@ def test_optional_field_validation_alias_schema(): body_schema = openapi["components"]["schemas"][ "Body_optional_field_validation_alias" ] - assert len(body_schema["properties"]) == 1 - assert "param_val_alias" in body_schema["properties"] + assert body_schema["properties"] == { + "param_val_alias": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Param Val Alias", + }, + } # ------------------------------ @@ -231,28 +295,45 @@ async def list_field_validation_alias( return {"param": param} -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_list_field_validation_alias_by_name(): client = TestClient(app) resp = client.post("/list-field-validation-alias", data={"param": ["123", "456"]}) assert resp.status_code == 422 + # Currently fails due to issue with aliases: + # AssertionError: assert 200 == 422 + + detail = resp.json()["detail"] + assert detail[0]["msg"] == IsOneOf( + "Field required", + "field required", # TODO: remove when deprecating Pydantic v1 + ) + assert "param_val_alias" in detail[0]["loc"] -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_list_field_validation_alias_by_alias(): client = TestClient(app) resp = client.post( "/list-field-validation-alias", data={"param_val_alias": ["123", "456"]} ) assert resp.status_code == 200 + # Currently fails due to issue with aliases: + # AssertionError: assert 422 == 200 + assert resp.json() == {"param": ["123", "456"]} def test_list_field_validation_alias_schema(): openapi = app.openapi() body_schema = openapi["components"]["schemas"]["Body_list_field_validation_alias"] - assert len(body_schema["properties"]) == 1 - assert "param_val_alias" in body_schema["properties"] + assert body_schema["properties"] == { + "param_val_alias": { + "items": {"type": "string"}, + "title": "Param Val Alias", + "type": "array", + }, + } # ------------------------------ @@ -269,16 +350,19 @@ async def optional_list_field_validation_alias( return {"param": param} -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_list_field_validation_alias_by_name(): client = TestClient(app) resp = client.post( "/optional-list-field-validation-alias", data={"param": ["123", "456"]} ) - assert resp.status_code == 422 + assert resp.status_code == 200 + assert resp.json() == {"param": None} + # Currently fails due to issue with aliases: + # AssertionError: assert {'param': ['123', '456']} == {'param': None} -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_list_field_validation_alias_by_alias(): client = TestClient(app) resp = client.post( @@ -287,6 +371,8 @@ def test_optional_list_field_validation_alias_by_alias(): ) assert resp.status_code == 200 assert resp.json() == {"param": ["123", "456"]} + # Currently fails due to issue with aliases: + # AssertionError: assert {'param': None} == {'param': ['123', '456']} def test_optional_list_field_validation_alias_schema(): @@ -294,13 +380,17 @@ def test_optional_list_field_validation_alias_schema(): body_schema = openapi["components"]["schemas"][ "Body_optional_list_field_validation_alias" ] - assert len(body_schema["properties"]) == 1 - assert "param_val_alias" in body_schema["properties"] + assert body_schema["properties"] == { + "param_val_alias": { + "anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], + "title": "Param Val Alias", + }, + } # ===================================================================================== -# alias and validation alias -# Current situation: Schema is correct (val_alias), but doesn't work (alias is used) +# Form(alias=..., validation_alias=...) +# Current situation: Schema is correct (validation_alias), but doesn't work (alias is used) # ------------------------------ # required field @@ -316,30 +406,51 @@ async def required_field_alias_and_validation_alias( return {"param": param} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_required_field_alias_and_validation_alias_by_name(): client = TestClient(app) resp = client.post( "/required-field-alias-and-validation-alias", data={"param": "123"} ) assert resp.status_code == 422 + detail = resp.json()["detail"] + assert detail[0]["msg"] == IsOneOf( + "Field required", + "field required", # TODO: remove when deprecating Pydantic v1 + ) + assert "param_val_alias" in detail[0]["loc"] + # Currently fails due to issue with aliases: + # AssertionError: assert 'param_val_alias' in ['body', 'param_alias'] -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_required_field_alias_and_validation_alias_by_alias(): client = TestClient(app) resp = client.post( "/required-field-alias-and-validation-alias", data={"param_alias": "123"} ) assert resp.status_code == 422 + # Currently fails due to issue with aliases: + # AssertionError: assert 200 == 422 + + detail = resp.json()["detail"] + assert detail[0]["msg"] == IsOneOf( + "Field required", + "field required", # TODO: remove when deprecating Pydantic v1 + ) + assert "param_val_alias" in detail[0]["loc"] -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_required_field_alias_and_validation_alias_by_validation_alias(): client = TestClient(app) resp = client.post( "/required-field-alias-and-validation-alias", data={"param_val_alias": "123"} ) assert resp.status_code == 200 + # Currently fails due to issue with aliases: + # AssertionError: assert 422 == 200 + assert resp.json() == {"param": "123"} @@ -348,8 +459,9 @@ def test_required_field_alias_and_validation_alias_schema(): body_schema = openapi["components"]["schemas"][ "Body_required_field_alias_and_validation_alias" ] - assert len(body_schema["properties"]) == 1 - assert "param_val_alias" in body_schema["properties"] + assert body_schema["properties"] == { + "param_val_alias": {"title": "Param Val Alias", "type": "string"} + } # ------------------------------ @@ -361,7 +473,9 @@ def test_required_field_alias_and_validation_alias_schema(): operation_id="optional_field_alias_and_validation_alias", ) async def optional_field_alias_and_validation_alias( - param: str = Form(None, alias="param_alias", validation_alias="param_val_alias"), + param: Optional[str] = Form( + None, alias="param_alias", validation_alias="param_val_alias" + ), ): return {"param": param} @@ -375,16 +489,19 @@ def test_optional_field_alias_and_validation_alias_by_name(): assert resp.json() == {"param": None} -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_field_alias_and_validation_alias_by_alias(): client = TestClient(app) resp = client.post( "/optional-field-alias-and-validation-alias", data={"param_alias": "123"} ) - assert resp.status_code == 422 + assert resp.status_code == 200 + assert resp.json() == {"param": None} + # Currently fails due to issue with aliases: + # AssertionError: assert {'param': '123'} == {'param': None} -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_field_alias_and_validation_alias_by_validation_alias(): client = TestClient(app) resp = client.post( @@ -392,6 +509,8 @@ def test_optional_field_alias_and_validation_alias_by_validation_alias(): ) assert resp.status_code == 200 assert resp.json() == {"param": "123"} + # Currently fails due to issue with aliases: + # AssertionError: assert {'param': None} == {'param': '123'} def test_optional_field_alias_and_validation_alias_schema(): @@ -399,8 +518,12 @@ def test_optional_field_alias_and_validation_alias_schema(): body_schema = openapi["components"]["schemas"][ "Body_optional_field_alias_and_validation_alias" ] - assert len(body_schema["properties"]) == 1 - assert "param_val_alias" in body_schema["properties"] + assert body_schema["properties"] == { + "param_val_alias": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Param Val Alias", + } + } # ------------------------------ @@ -417,24 +540,42 @@ async def list_field_alias_and_validation_alias( return {"param": param} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_list_field_alias_and_validation_alias_by_name(): client = TestClient(app) resp = client.post( "/list-field-alias-and-validation-alias", data={"param": ["123", "456"]} ) assert resp.status_code == 422 + detail = resp.json()["detail"] + assert detail[0]["msg"] == IsOneOf( + "Field required", + "field required", # TODO: remove when deprecating Pydantic v1 + ) + assert "param_val_alias" in detail[0]["loc"] + # Currently fails due to issue with aliases: + # AssertionError: assert 'param_val_alias' in ['body', 'param_alias'] -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_list_field_alias_and_validation_alias_by_alias(): client = TestClient(app) resp = client.post( "/list-field-alias-and-validation-alias", data={"param_alias": ["123", "456"]} ) assert resp.status_code == 422 + # Currently fails due to issue with aliases: + # AssertionError: assert 200 == 422 + + detail = resp.json()["detail"] + assert detail[0]["msg"] == IsOneOf( + "Field required", + "field required", # TODO: remove when deprecating Pydantic v1 + ) + assert "param_val_alias" in detail[0]["loc"] -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_list_field_alias_and_validation_alias_by_validation_alias(): client = TestClient(app) resp = client.post( @@ -442,6 +583,9 @@ def test_list_field_alias_and_validation_alias_by_validation_alias(): data={"param_val_alias": ["123", "456"]}, ) assert resp.status_code == 200 + # Currently fails due to issue with aliases: + # AssertionError: assert 422 == 200 + assert resp.json() == {"param": ["123", "456"]} @@ -450,8 +594,13 @@ def test_list_field_alias_and_validation_alias_schema(): body_schema = openapi["components"]["schemas"][ "Body_list_field_alias_and_validation_alias" ] - assert len(body_schema["properties"]) == 1 - assert "param_val_alias" in body_schema["properties"] + assert body_schema["properties"] == { + "param_val_alias": { + "items": {"type": "string"}, + "title": "Param Val Alias", + "type": "array", + }, + } # ------------------------------ @@ -480,17 +629,20 @@ def test_optional_list_field_alias_and_validation_alias_by_name(): assert resp.json() == {"param": None} -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_list_field_alias_and_validation_alias_by_alias(): client = TestClient(app) resp = client.post( "/optional-list-field-alias-and-validation-alias", data={"param_alias": ["123", "456"]}, ) - assert resp.status_code == 422 + assert resp.status_code == 200 + assert resp.json() == {"param": None} + # Currently fails due to issue with aliases: + # AssertionError: assert {'param': ['123', '456']} == {'param': None} -@pytest.mark.xfail +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_list_field_alias_and_validation_alias_by_validation_alias(): client = TestClient(app) resp = client.post( @@ -499,6 +651,8 @@ def test_optional_list_field_alias_and_validation_alias_by_validation_alias(): ) assert resp.status_code == 200 assert resp.json() == {"param": ["123", "456"]} + # Currently fails due to issue with aliases: + # AssertionError: assert {'param': None} == {'param': ['123', '456']} def test_optional_list_field_alias_and_validation_alias_schema(): @@ -506,5 +660,49 @@ def test_optional_list_field_alias_and_validation_alias_schema(): body_schema = openapi["components"]["schemas"][ "Body_optional_list_field_alias_and_validation_alias" ] - assert len(body_schema["properties"]) == 1 - assert "param_val_alias" in body_schema["properties"] + assert body_schema["properties"] == { + "param_val_alias": { + "anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], + "title": "Param Val Alias", + }, + } + + +# ===================================================================================== +# Form(alias=..., validation_alias=...) # alias == validation_alias +# The only working solution (current workaround) +# TODO: remove when issue with Form field aliases is fixed + + +@app.post("/workaround", operation_id="workaround") +async def workaround( + param: str = Form(alias="param_alias", validation_alias="param_alias"), +): + return {"param": param} + + +def test_workaround_by_name(): + client = TestClient(app) + resp = client.post("/workaround", data={"param": "123"}) + assert resp.status_code == 422 + detail = resp.json()["detail"] + assert detail[0]["msg"] == IsOneOf( + "Field required", + "field required", # TODO: remove when deprecating Pydantic v1 + ) + assert "param_alias" in detail[0]["loc"] + + +def test_workaround_by_alias(): + client = TestClient(app) + resp = client.post("/workaround", data={"param_alias": "123"}) + assert resp.status_code == 200 + assert resp.json() == {"param": "123"} + + +def test_workaround_schema(): + openapi = app.openapi() + body_schema = openapi["components"]["schemas"]["Body_workaround"] + assert body_schema["properties"] == { + "param_alias": {"title": "Param Alias", "type": "string"} + }