committed by
GitHub
3 changed files with 179 additions and 2 deletions
@ -0,0 +1,157 @@ |
|||
import pytest |
|||
from dirty_equals import IsDict |
|||
from fastapi import FastAPI |
|||
from fastapi.testclient import TestClient |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
INVALID_JSON = """ |
|||
{ |
|||
"name": "Test", |
|||
"price": 'invalid' |
|||
}""" |
|||
|
|||
|
|||
class Item(BaseModel): |
|||
name: str |
|||
price: float |
|||
description: str = None |
|||
|
|||
|
|||
@app.post("/items/") |
|||
async def create_item(item: Item): |
|||
return item # pragma: no cover |
|||
|
|||
|
|||
client = TestClient(app) |
|||
|
|||
|
|||
def test_json_decode_error_single_line(): |
|||
response = client.post( |
|||
"/items/", |
|||
content='{"name": "Test", "price": None}', |
|||
headers={"Content-Type": "application/json"}, |
|||
) |
|||
|
|||
assert response.status_code == 422 |
|||
error = response.json()["detail"][0] |
|||
|
|||
assert error["loc"] == ["body", 26] |
|||
assert error["msg"] == "JSON decode error" |
|||
assert error["input"] == {} |
|||
assert error["ctx"]["error"] == "Expecting value" |
|||
assert error["ctx"]["position"] == 26 |
|||
assert error["ctx"]["line"] == 0 |
|||
assert error["ctx"]["column"] == 26 |
|||
assert "None" in error["ctx"]["snippet"] |
|||
|
|||
|
|||
def test_json_decode_error_multiline(): |
|||
response = client.post( |
|||
"/items/", content=INVALID_JSON, headers={"Content-Type": "application/json"} |
|||
) |
|||
|
|||
assert response.status_code == 422 |
|||
error = response.json()["detail"][0] |
|||
|
|||
assert error["loc"][0] == "body" |
|||
assert isinstance(error["loc"][1], int) |
|||
assert error["msg"] == "JSON decode error" |
|||
assert error["input"] == {} |
|||
assert error["ctx"]["line"] == 3 |
|||
assert error["ctx"]["column"] == 11 |
|||
assert "invalid" in error["ctx"]["snippet"] |
|||
|
|||
|
|||
def test_json_decode_error_empty_body(): |
|||
response = client.post( |
|||
"/items/", content="", headers={"Content-Type": "application/json"} |
|||
) |
|||
|
|||
assert response.status_code == 422 |
|||
# Handle both Pydantic v1 and v2 - empty body is handled differently |
|||
assert response.json() == IsDict( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": ["body"], |
|||
"msg": "Field required", |
|||
"type": "missing", |
|||
"input": None, |
|||
} |
|||
] |
|||
} |
|||
) | IsDict( |
|||
# Pydantic v1 |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": ["body"], |
|||
"msg": "field required", |
|||
"type": "value_error.missing", |
|||
} |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_json_decode_error_unclosed_brace(): |
|||
response = client.post( |
|||
"/items/", |
|||
content='{"name": "Test"', |
|||
headers={"Content-Type": "application/json"}, |
|||
) |
|||
|
|||
assert response.status_code == 422 |
|||
error = response.json()["detail"][0] |
|||
|
|||
assert error["msg"] == "JSON decode error" |
|||
assert error["type"] == "json_invalid" |
|||
assert error["input"] == {} |
|||
assert "position" in error["ctx"] |
|||
assert "line" in error["ctx"] |
|||
assert "column" in error["ctx"] |
|||
assert "snippet" in error["ctx"] |
|||
|
|||
|
|||
@pytest.mark.parametrize( |
|||
"json_content,expected_starts_with_ellipsis,expected_ends_with_ellipsis", |
|||
[ |
|||
( |
|||
'{"field": invalid, "padding_field": "this needs to be long enough that we have more than forty characters after the error position"}', |
|||
False, |
|||
True, |
|||
), |
|||
( |
|||
'{"very_long_field_name_here": "some value that is long enough to push us past the forty character mark", "another_field": invalid}', |
|||
True, |
|||
False, |
|||
), |
|||
( |
|||
'{"very_long_field_name_here": "some value", "field": invalid, "padding_field": "this needs to be long enough that we have more than forty characters after the error position"}', |
|||
True, |
|||
True, |
|||
), |
|||
('{"field": invalid}', False, False), |
|||
], |
|||
) |
|||
def test_json_decode_error_snippet_ellipsis( |
|||
json_content, expected_starts_with_ellipsis, expected_ends_with_ellipsis |
|||
): |
|||
response = client.post( |
|||
"/items/", content=json_content, headers={"Content-Type": "application/json"} |
|||
) |
|||
|
|||
assert response.status_code == 422 |
|||
error = response.json()["detail"][0] |
|||
|
|||
assert error["msg"] == "JSON decode error" |
|||
assert error["input"] == {} |
|||
assert "invalid" in error["ctx"]["snippet"] |
|||
assert error["type"] == "json_invalid" |
|||
|
|||
snippet = error["ctx"]["snippet"] |
|||
assert snippet.startswith("...") == expected_starts_with_ellipsis |
|||
assert snippet.endswith("...") == expected_ends_with_ellipsis |
|||
Loading…
Reference in new issue