mirror of https://github.com/tiangolo/fastapi.git
Update tests for passing empty str to Form
This commit is contained in:
parent
cf0d31bd69
commit
2a2aafa01e
|
|
@ -1,5 +1,5 @@
|
||||||
from typing import Annotated, Any, Union
|
from typing import Annotated, Any, Union
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, call, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from dirty_equals import IsList, IsOneOf, IsPartialDict
|
from dirty_equals import IsList, IsOneOf, IsPartialDict
|
||||||
|
|
@ -150,18 +150,55 @@ def test_nullable_required_missing(path: str):
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/nullable-required",
|
"/nullable-required",
|
||||||
marks=pytest.mark.xfail(
|
marks=pytest.mark.xfail(
|
||||||
reason="Empty str is replaced with None, but then None gets dropped"
|
reason="Empty str is replaced with None even for required parameters"
|
||||||
),
|
|
||||||
),
|
|
||||||
pytest.param(
|
|
||||||
"/model-nullable-required",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
reason="Empty strings are not replaced with None for models"
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
"/model-nullable-required",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_nullable_required_pass_empty_str(path: str):
|
def test_nullable_required_pass_empty_str_to_str_val(path: str):
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
with patch(f"{__name__}.convert", Mock(wraps=convert)) as mock_convert:
|
||||||
|
response = client.post(
|
||||||
|
path,
|
||||||
|
data={
|
||||||
|
"int_val": "0", # Empty string would cause validation error (see below)
|
||||||
|
"str_val": "",
|
||||||
|
"list_val": "0", # Empty string would cause validation error (see below)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert mock_convert.call_count == 3, "Validator should be called for each field"
|
||||||
|
assert mock_convert.call_args_list == [
|
||||||
|
call("0"), # int_val
|
||||||
|
call(""), # str_val
|
||||||
|
call(["0"]), # list_val
|
||||||
|
]
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
assert response.json() == {
|
||||||
|
"int_val": 0,
|
||||||
|
"str_val": "",
|
||||||
|
"list_val": [0],
|
||||||
|
"fields_set": IsOneOf(
|
||||||
|
None, IsList("int_val", "str_val", "list_val", check_order=False)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path",
|
||||||
|
[
|
||||||
|
pytest.param(
|
||||||
|
"/nullable-required",
|
||||||
|
marks=pytest.mark.xfail(
|
||||||
|
reason="Empty str is replaced with None even for required parameters"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"/model-nullable-required",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_nullable_required_pass_empty_str_to_int_val_and_list_val(path: str):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
|
|
||||||
with patch(f"{__name__}.convert", Mock(wraps=convert)) as mock_convert:
|
with patch(f"{__name__}.convert", Mock(wraps=convert)) as mock_convert:
|
||||||
|
|
@ -170,26 +207,33 @@ def test_nullable_required_pass_empty_str(path: str):
|
||||||
data={
|
data={
|
||||||
"int_val": "",
|
"int_val": "",
|
||||||
"str_val": "",
|
"str_val": "",
|
||||||
"list_val": "0", # Empty strings are not treated as null for lists. It's Ok
|
"list_val": "",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert mock_convert.call_count == 3, "Validator should be called for each field"
|
assert mock_convert.call_count == 3, "Validator should be called for each field"
|
||||||
assert mock_convert.call_args_list == [
|
assert mock_convert.call_args_list == [
|
||||||
(""), # int_val
|
call(""), # int_val
|
||||||
(""), # str_val
|
call(""), # str_val
|
||||||
(["0"]), # list_val
|
call([""]), # list_val
|
||||||
]
|
]
|
||||||
assert response.status_code == 200, response.text # pragma: no cover
|
assert response.status_code == 422, response.text
|
||||||
assert response.json() == { # pragma: no cover
|
assert response.json() == {
|
||||||
"int_val": None,
|
"detail": [
|
||||||
"str_val": None,
|
{
|
||||||
"list_val": [0],
|
"input": "",
|
||||||
"fields_set": IsOneOf(
|
"loc": ["body", "int_val"],
|
||||||
None, IsList("int_val", "str_val", "list_val", check_order=False)
|
"msg": "Input should be a valid integer, unable to parse string as an integer",
|
||||||
),
|
"type": "int_parsing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"loc": ["body", "list_val", 0],
|
||||||
|
"msg": "Input should be a valid integer, unable to parse string as an integer",
|
||||||
|
"type": "int_parsing",
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
# TODO: Remove 'no cover' when the issue is fixed
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
@ -336,21 +380,16 @@ def test_nullable_non_required_missing(path: str):
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
pytest.param(
|
"/nullable-non-required",
|
||||||
"/nullable-non-required",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
reason="Empty str is replaced with None, but then None gets dropped"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/model-nullable-non-required",
|
"/model-nullable-non-required",
|
||||||
marks=pytest.mark.xfail(
|
marks=pytest.mark.xfail(
|
||||||
reason="Empty strings are not replaced with None for models"
|
reason="Empty strings are not replaced with None for parameters declared as model"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_nullable_non_required_pass_empty_str(path: str):
|
def test_nullable_non_required_pass_empty_str_to_str_val_and_int_val(path: str):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
|
|
||||||
with patch(f"{__name__}.convert", Mock(wraps=convert)) as mock_convert:
|
with patch(f"{__name__}.convert", Mock(wraps=convert)) as mock_convert:
|
||||||
|
|
@ -359,26 +398,63 @@ def test_nullable_non_required_pass_empty_str(path: str):
|
||||||
data={
|
data={
|
||||||
"int_val": "",
|
"int_val": "",
|
||||||
"str_val": "",
|
"str_val": "",
|
||||||
"list_val": "0", # Empty strings are not treated as null for lists. It's Ok
|
"list_val": "0", # Empty string would cause validation error (see below)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert mock_convert.call_count == 3, "Validator should be called for each field"
|
assert mock_convert.call_count == 1, "Validator should be called for list_val only"
|
||||||
assert mock_convert.call_args_list == [
|
assert mock_convert.call_args_list == [
|
||||||
(""), # int_val
|
call(["0"]), # list_val
|
||||||
(""), # str_val
|
|
||||||
(["0"]), # list_val
|
|
||||||
]
|
]
|
||||||
assert response.status_code == 200, response.text # pragma: no cover
|
assert response.status_code == 200, response.text
|
||||||
assert response.json() == { # pragma: no cover
|
assert response.json() == {
|
||||||
"int_val": None,
|
"int_val": None,
|
||||||
"str_val": None,
|
"str_val": None,
|
||||||
"list_val": [0],
|
"list_val": [0],
|
||||||
"fields_set": IsOneOf(
|
"fields_set": IsOneOf(None, IsList("list_val", check_order=False)),
|
||||||
None, IsList("int_val", "str_val", "list_val", check_order=False)
|
}
|
||||||
),
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path",
|
||||||
|
[
|
||||||
|
"/nullable-non-required",
|
||||||
|
pytest.param(
|
||||||
|
"/model-nullable-non-required",
|
||||||
|
marks=pytest.mark.xfail(
|
||||||
|
reason="Empty strings are not replaced with None for parameters declared as model"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_nullable_non_required_pass_empty_str_to_all(path: str):
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
with patch(f"{__name__}.convert", Mock(wraps=convert)) as mock_convert:
|
||||||
|
response = client.post(
|
||||||
|
path,
|
||||||
|
data={
|
||||||
|
"int_val": "",
|
||||||
|
"str_val": "",
|
||||||
|
"list_val": "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert mock_convert.call_count == 1, "Validator should be called for list_val only"
|
||||||
|
assert mock_convert.call_args_list == [
|
||||||
|
call([""]), # list_val
|
||||||
|
]
|
||||||
|
assert response.status_code == 422, response.text
|
||||||
|
assert response.json() == {
|
||||||
|
"detail": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"loc": ["body", "list_val", 0],
|
||||||
|
"msg": "Input should be a valid integer, unable to parse string as an integer",
|
||||||
|
"type": "int_parsing",
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
# TODO: Remove 'no cover' when the issue is fixed
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
@ -540,18 +616,20 @@ def test_nullable_with_non_null_default_missing(path: str):
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/nullable-with-non-null-default",
|
"/nullable-with-non-null-default",
|
||||||
marks=pytest.mark.xfail(
|
marks=pytest.mark.xfail(
|
||||||
reason="Empty str is replaced with default value, not with None" # Is this correct ???
|
reason="Empty strings are replaced with default values before validation"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/model-nullable-with-non-null-default",
|
"/model-nullable-with-non-null-default",
|
||||||
marks=pytest.mark.xfail(
|
marks=pytest.mark.xfail(
|
||||||
reason="Empty strings are not replaced with None for models"
|
reason="Empty strings are not replaced with None for parameters declared as model"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_nullable_with_non_null_default_pass_empty_str(path: str):
|
def test_nullable_with_non_null_default_pass_empty_str_to_str_val_and_int_val(
|
||||||
|
path: str,
|
||||||
|
):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
|
|
||||||
with patch(f"{__name__}.convert", Mock(wraps=convert)) as mock_convert:
|
with patch(f"{__name__}.convert", Mock(wraps=convert)) as mock_convert:
|
||||||
|
|
@ -560,24 +638,68 @@ def test_nullable_with_non_null_default_pass_empty_str(path: str):
|
||||||
data={
|
data={
|
||||||
"int_val": "",
|
"int_val": "",
|
||||||
"str_val": "",
|
"str_val": "",
|
||||||
"list_val": "0", # Empty strings are not treated as null for lists. It's Ok
|
"list_val": "0", # Empty string would cause validation error (see below)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert mock_convert.call_count == 3, "Validator should be called for each field"
|
assert mock_convert.call_count == 1, "Validator should be called for list_val only"
|
||||||
assert mock_convert.call_args_list == [
|
assert mock_convert.call_args_list == [ # pragma: no cover
|
||||||
(""), # int_val
|
call(["0"]), # list_val
|
||||||
(""), # str_val
|
|
||||||
(["0"]), # list_val
|
|
||||||
]
|
]
|
||||||
assert response.status_code == 200, response.text # pragma: no cover
|
assert response.status_code == 200, response.text # pragma: no cover
|
||||||
assert response.json() == { # pragma: no cover
|
assert response.json() == { # pragma: no cover
|
||||||
"int_val": None,
|
"int_val": -1,
|
||||||
"str_val": None,
|
"str_val": "default",
|
||||||
"list_val": [0],
|
"list_val": [0],
|
||||||
"fields_set": IsOneOf(
|
"fields_set": IsOneOf(None, IsList("list_val", check_order=False)),
|
||||||
None, IsList("int_val", "str_val", "list_val", check_order=False)
|
}
|
||||||
|
# TODO: Remove 'no cover' when the issue is fixed
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path",
|
||||||
|
[
|
||||||
|
pytest.param(
|
||||||
|
"/nullable-with-non-null-default",
|
||||||
|
marks=pytest.mark.xfail(
|
||||||
|
reason="Empty strings are replaced with default values before validation"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
pytest.param(
|
||||||
|
"/model-nullable-with-non-null-default",
|
||||||
|
marks=pytest.mark.xfail(
|
||||||
|
reason="Empty strings are not replaced with None for parameters declared as model"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_nullable_with_non_null_default_pass_empty_str_to_all(path: str):
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
with patch(f"{__name__}.convert", Mock(wraps=convert)) as mock_convert:
|
||||||
|
response = client.post(
|
||||||
|
path,
|
||||||
|
data={
|
||||||
|
"int_val": "",
|
||||||
|
"str_val": "",
|
||||||
|
"list_val": "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert mock_convert.call_count == 1, "Validator should be called for list_val only"
|
||||||
|
assert mock_convert.call_args_list == [ # pragma: no cover
|
||||||
|
call([""]), # list_val
|
||||||
|
]
|
||||||
|
assert response.status_code == 422, response.text # pragma: no cover
|
||||||
|
assert response.json() == { # pragma: no cover
|
||||||
|
"detail": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"loc": ["body", "list_val", 0],
|
||||||
|
"msg": "Input should be a valid integer, unable to parse string as an integer",
|
||||||
|
"type": "int_parsing",
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
# TODO: Remove 'no cover' when the issue is fixed
|
# TODO: Remove 'no cover' when the issue is fixed
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue