Coverage for tests/test_multi_body_errors.py: 100%
30 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-12-04 08:29 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-12-04 08:29 +0000
1from decimal import Decimal 1abcdefg
2from typing import List 1abcdefg
4from dirty_equals import IsDict, IsOneOf 1abcdefg
5from fastapi import FastAPI 1abcdefg
6from fastapi.testclient import TestClient 1abcdefg
7from pydantic import BaseModel, condecimal 1abcdefg
9app = FastAPI() 1abcdefg
12class Item(BaseModel): 1abcdefg
13 name: str 1abcdefg
14 age: condecimal(gt=Decimal(0.0)) # type: ignore 1abcdefg
17@app.post("/items/") 1abcdefg
18def save_item_no_body(item: List[Item]): 1abcdefg
19 return {"item": item} 1hijklmn
22client = TestClient(app) 1abcdefg
25def test_put_correct_body(): 1abcdefg
26 response = client.post("/items/", json=[{"name": "Foo", "age": 5}]) 1hijklmn
27 assert response.status_code == 200, response.text 1hijklmn
28 assert response.json() == { 1hijklmn
29 "item": [
30 {
31 "name": "Foo",
32 "age": IsOneOf(
33 5,
34 # TODO: remove when deprecating Pydantic v1
35 "5",
36 ),
37 }
38 ]
39 }
42def test_jsonable_encoder_requiring_error(): 1abcdefg
43 response = client.post("/items/", json=[{"name": "Foo", "age": -1.0}]) 1opqrstu
44 assert response.status_code == 422, response.text 1opqrstu
45 assert response.json() == IsDict( 1opqrstu
46 {
47 "detail": [
48 {
49 "type": "greater_than",
50 "loc": ["body", 0, "age"],
51 "msg": "Input should be greater than 0",
52 "input": -1.0,
53 "ctx": {"gt": 0},
54 }
55 ]
56 }
57 ) | IsDict(
58 # TODO: remove when deprecating Pydantic v1
59 {
60 "detail": [
61 {
62 "ctx": {"limit_value": 0.0},
63 "loc": ["body", 0, "age"],
64 "msg": "ensure this value is greater than 0",
65 "type": "value_error.number.not_gt",
66 }
67 ]
68 }
69 )
72def test_put_incorrect_body_multiple(): 1abcdefg
73 response = client.post("/items/", json=[{"age": "five"}, {"age": "six"}]) 1vwxyzAB
74 assert response.status_code == 422, response.text 1vwxyzAB
75 assert response.json() == IsDict( 1vwxyzAB
76 {
77 "detail": [
78 {
79 "type": "missing",
80 "loc": ["body", 0, "name"],
81 "msg": "Field required",
82 "input": {"age": "five"},
83 },
84 {
85 "type": "decimal_parsing",
86 "loc": ["body", 0, "age"],
87 "msg": "Input should be a valid decimal",
88 "input": "five",
89 },
90 {
91 "type": "missing",
92 "loc": ["body", 1, "name"],
93 "msg": "Field required",
94 "input": {"age": "six"},
95 },
96 {
97 "type": "decimal_parsing",
98 "loc": ["body", 1, "age"],
99 "msg": "Input should be a valid decimal",
100 "input": "six",
101 },
102 ]
103 }
104 ) | IsDict(
105 # TODO: remove when deprecating Pydantic v1
106 {
107 "detail": [
108 {
109 "loc": ["body", 0, "name"],
110 "msg": "field required",
111 "type": "value_error.missing",
112 },
113 {
114 "loc": ["body", 0, "age"],
115 "msg": "value is not a valid decimal",
116 "type": "type_error.decimal",
117 },
118 {
119 "loc": ["body", 1, "name"],
120 "msg": "field required",
121 "type": "value_error.missing",
122 },
123 {
124 "loc": ["body", 1, "age"],
125 "msg": "value is not a valid decimal",
126 "type": "type_error.decimal",
127 },
128 ]
129 }
130 )
133def test_openapi_schema(): 1abcdefg
134 response = client.get("/openapi.json") 1CDEFGHI
135 assert response.status_code == 200, response.text 1CDEFGHI
136 assert response.json() == { 1CDEFGHI
137 "openapi": "3.1.0",
138 "info": {"title": "FastAPI", "version": "0.1.0"},
139 "paths": {
140 "/items/": {
141 "post": {
142 "responses": {
143 "200": {
144 "description": "Successful Response",
145 "content": {"application/json": {"schema": {}}},
146 },
147 "422": {
148 "description": "Validation Error",
149 "content": {
150 "application/json": {
151 "schema": {
152 "$ref": "#/components/schemas/HTTPValidationError"
153 }
154 }
155 },
156 },
157 },
158 "summary": "Save Item No Body",
159 "operationId": "save_item_no_body_items__post",
160 "requestBody": {
161 "content": {
162 "application/json": {
163 "schema": {
164 "title": "Item",
165 "type": "array",
166 "items": {"$ref": "#/components/schemas/Item"},
167 }
168 }
169 },
170 "required": True,
171 },
172 }
173 }
174 },
175 "components": {
176 "schemas": {
177 "Item": {
178 "title": "Item",
179 "required": ["name", "age"],
180 "type": "object",
181 "properties": {
182 "name": {"title": "Name", "type": "string"},
183 "age": IsDict(
184 {
185 "title": "Age",
186 "anyOf": [
187 {"exclusiveMinimum": 0.0, "type": "number"},
188 IsOneOf(
189 # pydantic < 2.12.0
190 {"type": "string"},
191 # pydantic >= 2.12.0
192 {
193 "type": "string",
194 "pattern": r"^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$",
195 },
196 ),
197 ],
198 }
199 )
200 | IsDict(
201 # TODO: remove when deprecating Pydantic v1
202 {
203 "title": "Age",
204 "exclusiveMinimum": 0.0,
205 "type": "number",
206 }
207 ),
208 },
209 },
210 "ValidationError": {
211 "title": "ValidationError",
212 "required": ["loc", "msg", "type"],
213 "type": "object",
214 "properties": {
215 "loc": {
216 "title": "Location",
217 "type": "array",
218 "items": {
219 "anyOf": [{"type": "string"}, {"type": "integer"}]
220 },
221 },
222 "msg": {"title": "Message", "type": "string"},
223 "type": {"title": "Error Type", "type": "string"},
224 },
225 },
226 "HTTPValidationError": {
227 "title": "HTTPValidationError",
228 "type": "object",
229 "properties": {
230 "detail": {
231 "title": "Detail",
232 "type": "array",
233 "items": {"$ref": "#/components/schemas/ValidationError"},
234 }
235 },
236 },
237 }
238 },
239 }