Coverage for tests / test_openapi_separate_input_output_schemas.py: 100%

83 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-04-06 01:24 +0000

1from fastapi import FastAPI 1adbc

2from fastapi.testclient import TestClient 1adbc

3from inline_snapshot import snapshot 1adbc

4from pydantic import BaseModel, computed_field 1adbc

5 

6 

7class SubItem(BaseModel): 1adbc

8 subname: str 1abc

9 sub_description: str | None = None 1adbc

10 tags: list[str] = [] 1adbc

11 model_config = {"json_schema_serialization_defaults_required": True} 1adbc

12 

13 

14class Item(BaseModel): 1adbc

15 name: str 1abc

16 description: str | None = None 1adbc

17 sub: SubItem | None = None 1adbc

18 model_config = {"json_schema_serialization_defaults_required": True} 1adbc

19 

20 

21class WithComputedField(BaseModel): 1adbc

22 name: str 1abc

23 

24 @computed_field 1adbc

25 @property 1adbc

26 def computed_field(self) -> str: 1adbc

27 return f"computed {self.name}" 1eAfk

28 

29 

30def get_app_client(separate_input_output_schemas: bool = True) -> TestClient: 1adbc

31 app = FastAPI(separate_input_output_schemas=separate_input_output_schemas) 1ghluvmenijowxpfqrsyztk

32 

33 @app.post("/items/", responses={402: {"model": Item}}) 1ghluvmenijowxpfqrsyztk

34 def create_item(item: Item) -> Item: 1ghluvmenijowxpfqrsyztk

35 return item 1hlnjors

36 

37 @app.post("/items-list/") 1ghluvmenijowxpfqrsyztk

38 def create_item_list(item: list[Item]): 1ghluvmenijowxpfqrsyztk

39 return item 1gBiq

40 

41 @app.get("/items/") 1ghluvmenijowxpfqrsyztk

42 def read_items() -> list[Item]: 1ghluvmenijowxpfqrsyztk

43 return [ 1mCpt

44 Item( 

45 name="Portal Gun", 

46 description="Device to travel through the multi-rick-verse", 

47 sub=SubItem(subname="subname"), 

48 ), 

49 Item(name="Plumbus"), 

50 ] 

51 

52 @app.post("/with-computed-field/") 1ghluvmenijowxpfqrsyztk

53 def create_with_computed_field( 1ghluvmenijowxpfqrsyztk

54 with_computed_field: WithComputedField, 

55 ) -> WithComputedField: 

56 return with_computed_field 1eAfk

57 

58 client = TestClient(app) 1ghluvmenijowxpfqrsyztk

59 return client 1ghluvmenijowxpfqrsyztk

60 

61 

62def test_create_item(): 1adbc

63 client = get_app_client() 1lnos

64 client_no = get_app_client(separate_input_output_schemas=False) 1lnos

65 response = client.post("/items/", json={"name": "Plumbus"}) 1lnos

66 response2 = client_no.post("/items/", json={"name": "Plumbus"}) 1lnos

67 assert response.status_code == response2.status_code == 200, response.text 1lnos

68 assert ( 1lno

69 response.json() 

70 == response2.json() 

71 == {"name": "Plumbus", "description": None, "sub": None} 

72 ) 

73 

74 

75def test_create_item_with_sub(): 1adbc

76 client = get_app_client() 1hDjr

77 client_no = get_app_client(separate_input_output_schemas=False) 1hDjr

78 data = { 1hDjr

79 "name": "Plumbus", 

80 "sub": {"subname": "SubPlumbus", "sub_description": "Sub WTF"}, 

81 } 

82 response = client.post("/items/", json=data) 1hDjr

83 response2 = client_no.post("/items/", json=data) 1hDjr

84 assert response.status_code == response2.status_code == 200, response.text 1hDjr

85 assert ( 1hDj

86 response.json() 

87 == response2.json() 

88 == { 

89 "name": "Plumbus", 

90 "description": None, 

91 "sub": {"subname": "SubPlumbus", "sub_description": "Sub WTF", "tags": []}, 

92 } 

93 ) 

94 

95 

96def test_create_item_list(): 1adbc

97 client = get_app_client() 1gBiq

98 client_no = get_app_client(separate_input_output_schemas=False) 1gBiq

99 data = [ 1gBiq

100 {"name": "Plumbus"}, 

101 { 

102 "name": "Portal Gun", 

103 "description": "Device to travel through the multi-rick-verse", 

104 }, 

105 ] 

106 response = client.post("/items-list/", json=data) 1gBiq

107 response2 = client_no.post("/items-list/", json=data) 1gBiq

108 assert response.status_code == response2.status_code == 200, response.text 1gBiq

109 assert ( 1gBi

110 response.json() 

111 == response2.json() 

112 == [ 

113 {"name": "Plumbus", "description": None, "sub": None}, 

114 { 

115 "name": "Portal Gun", 

116 "description": "Device to travel through the multi-rick-verse", 

117 "sub": None, 

118 }, 

119 ] 

120 ) 

121 

122 

123def test_read_items(): 1adbc

124 client = get_app_client() 1mCpt

125 client_no = get_app_client(separate_input_output_schemas=False) 1mCpt

126 response = client.get("/items/") 1mCpt

127 response2 = client_no.get("/items/") 1mCpt

128 assert response.status_code == response2.status_code == 200, response.text 1mCpt

129 assert ( 1mCp

130 response.json() 

131 == response2.json() 

132 == [ 

133 { 

134 "name": "Portal Gun", 

135 "description": "Device to travel through the multi-rick-verse", 

136 "sub": {"subname": "subname", "sub_description": None, "tags": []}, 

137 }, 

138 {"name": "Plumbus", "description": None, "sub": None}, 

139 ] 

140 ) 

141 

142 

143def test_with_computed_field(): 1adbc

144 client = get_app_client() 1eAfk

145 client_no = get_app_client(separate_input_output_schemas=False) 1eAfk

146 response = client.post("/with-computed-field/", json={"name": "example"}) 1eAfk

147 response2 = client_no.post("/with-computed-field/", json={"name": "example"}) 1eAfk

148 assert response.status_code == response2.status_code == 200, response.text 1eAfk

149 assert ( 1eAf

150 response.json() 

151 == response2.json() 

152 == { 

153 "name": "example", 

154 "computed_field": "computed example", 

155 } 

156 ) 

157 

158 

159def test_openapi_schema(): 1adbc

160 client = get_app_client() 1vExz

161 response = client.get("/openapi.json") 1vExz

162 assert response.status_code == 200, response.text 1vExz

163 assert response.json() == snapshot( 1vExz

164 { 

165 "openapi": "3.1.0", 

166 "info": {"title": "FastAPI", "version": "0.1.0"}, 

167 "paths": { 

168 "/items/": { 

169 "get": { 

170 "summary": "Read Items", 

171 "operationId": "read_items_items__get", 

172 "responses": { 

173 "200": { 

174 "description": "Successful Response", 

175 "content": { 

176 "application/json": { 

177 "schema": { 

178 "items": { 

179 "$ref": "#/components/schemas/Item-Output" 

180 }, 

181 "type": "array", 

182 "title": "Response Read Items Items Get", 

183 } 

184 } 

185 }, 

186 } 

187 }, 

188 }, 

189 "post": { 

190 "summary": "Create Item", 

191 "operationId": "create_item_items__post", 

192 "requestBody": { 

193 "content": { 

194 "application/json": { 

195 "schema": { 

196 "$ref": "#/components/schemas/Item-Input" 

197 } 

198 } 

199 }, 

200 "required": True, 

201 }, 

202 "responses": { 

203 "200": { 

204 "description": "Successful Response", 

205 "content": { 

206 "application/json": { 

207 "schema": { 

208 "$ref": "#/components/schemas/Item-Output" 

209 } 

210 } 

211 }, 

212 }, 

213 "402": { 

214 "description": "Payment Required", 

215 "content": { 

216 "application/json": { 

217 "schema": { 

218 "$ref": "#/components/schemas/Item-Output" 

219 } 

220 } 

221 }, 

222 }, 

223 "422": { 

224 "description": "Validation Error", 

225 "content": { 

226 "application/json": { 

227 "schema": { 

228 "$ref": "#/components/schemas/HTTPValidationError" 

229 } 

230 } 

231 }, 

232 }, 

233 }, 

234 }, 

235 }, 

236 "/items-list/": { 

237 "post": { 

238 "summary": "Create Item List", 

239 "operationId": "create_item_list_items_list__post", 

240 "requestBody": { 

241 "content": { 

242 "application/json": { 

243 "schema": { 

244 "items": { 

245 "$ref": "#/components/schemas/Item-Input" 

246 }, 

247 "type": "array", 

248 "title": "Item", 

249 } 

250 } 

251 }, 

252 "required": True, 

253 }, 

254 "responses": { 

255 "200": { 

256 "description": "Successful Response", 

257 "content": {"application/json": {"schema": {}}}, 

258 }, 

259 "422": { 

260 "description": "Validation Error", 

261 "content": { 

262 "application/json": { 

263 "schema": { 

264 "$ref": "#/components/schemas/HTTPValidationError" 

265 } 

266 } 

267 }, 

268 }, 

269 }, 

270 } 

271 }, 

272 "/with-computed-field/": { 

273 "post": { 

274 "summary": "Create With Computed Field", 

275 "operationId": "create_with_computed_field_with_computed_field__post", 

276 "requestBody": { 

277 "content": { 

278 "application/json": { 

279 "schema": { 

280 "$ref": "#/components/schemas/WithComputedField-Input" 

281 } 

282 } 

283 }, 

284 "required": True, 

285 }, 

286 "responses": { 

287 "200": { 

288 "description": "Successful Response", 

289 "content": { 

290 "application/json": { 

291 "schema": { 

292 "$ref": "#/components/schemas/WithComputedField-Output" 

293 } 

294 } 

295 }, 

296 }, 

297 "422": { 

298 "description": "Validation Error", 

299 "content": { 

300 "application/json": { 

301 "schema": { 

302 "$ref": "#/components/schemas/HTTPValidationError" 

303 } 

304 } 

305 }, 

306 }, 

307 }, 

308 }, 

309 }, 

310 }, 

311 "components": { 

312 "schemas": { 

313 "HTTPValidationError": { 

314 "properties": { 

315 "detail": { 

316 "items": { 

317 "$ref": "#/components/schemas/ValidationError" 

318 }, 

319 "type": "array", 

320 "title": "Detail", 

321 } 

322 }, 

323 "type": "object", 

324 "title": "HTTPValidationError", 

325 }, 

326 "Item-Input": { 

327 "properties": { 

328 "name": {"type": "string", "title": "Name"}, 

329 "description": { 

330 "anyOf": [{"type": "string"}, {"type": "null"}], 

331 "title": "Description", 

332 }, 

333 "sub": { 

334 "anyOf": [ 

335 {"$ref": "#/components/schemas/SubItem-Input"}, 

336 {"type": "null"}, 

337 ] 

338 }, 

339 }, 

340 "type": "object", 

341 "required": ["name"], 

342 "title": "Item", 

343 }, 

344 "Item-Output": { 

345 "properties": { 

346 "name": {"type": "string", "title": "Name"}, 

347 "description": { 

348 "anyOf": [{"type": "string"}, {"type": "null"}], 

349 "title": "Description", 

350 }, 

351 "sub": { 

352 "anyOf": [ 

353 {"$ref": "#/components/schemas/SubItem-Output"}, 

354 {"type": "null"}, 

355 ] 

356 }, 

357 }, 

358 "type": "object", 

359 "required": ["name", "description", "sub"], 

360 "title": "Item", 

361 }, 

362 "SubItem-Input": { 

363 "properties": { 

364 "subname": {"type": "string", "title": "Subname"}, 

365 "sub_description": { 

366 "anyOf": [{"type": "string"}, {"type": "null"}], 

367 "title": "Sub Description", 

368 }, 

369 "tags": { 

370 "items": {"type": "string"}, 

371 "type": "array", 

372 "title": "Tags", 

373 "default": [], 

374 }, 

375 }, 

376 "type": "object", 

377 "required": ["subname"], 

378 "title": "SubItem", 

379 }, 

380 "SubItem-Output": { 

381 "properties": { 

382 "subname": {"type": "string", "title": "Subname"}, 

383 "sub_description": { 

384 "anyOf": [{"type": "string"}, {"type": "null"}], 

385 "title": "Sub Description", 

386 }, 

387 "tags": { 

388 "items": {"type": "string"}, 

389 "type": "array", 

390 "title": "Tags", 

391 "default": [], 

392 }, 

393 }, 

394 "type": "object", 

395 "required": ["subname", "sub_description", "tags"], 

396 "title": "SubItem", 

397 }, 

398 "WithComputedField-Input": { 

399 "properties": {"name": {"type": "string", "title": "Name"}}, 

400 "type": "object", 

401 "required": ["name"], 

402 "title": "WithComputedField", 

403 }, 

404 "WithComputedField-Output": { 

405 "properties": { 

406 "name": {"type": "string", "title": "Name"}, 

407 "computed_field": { 

408 "type": "string", 

409 "title": "Computed Field", 

410 "readOnly": True, 

411 }, 

412 }, 

413 "type": "object", 

414 "required": ["name", "computed_field"], 

415 "title": "WithComputedField", 

416 }, 

417 "ValidationError": { 

418 "properties": { 

419 "ctx": {"title": "Context", "type": "object"}, 

420 "input": {"title": "Input"}, 

421 "loc": { 

422 "items": { 

423 "anyOf": [{"type": "string"}, {"type": "integer"}] 

424 }, 

425 "type": "array", 

426 "title": "Location", 

427 }, 

428 "msg": {"type": "string", "title": "Message"}, 

429 "type": {"type": "string", "title": "Error Type"}, 

430 }, 

431 "type": "object", 

432 "required": ["loc", "msg", "type"], 

433 "title": "ValidationError", 

434 }, 

435 } 

436 }, 

437 } 

438 ) 

439 

440 

441def test_openapi_schema_no_separate(): 1adbc

442 client = get_app_client(separate_input_output_schemas=False) 1uFwy

443 response = client.get("/openapi.json") 1uFwy

444 assert response.status_code == 200, response.text 1uFwy

445 assert response.json() == snapshot( 1uFwy

446 { 

447 "openapi": "3.1.0", 

448 "info": {"title": "FastAPI", "version": "0.1.0"}, 

449 "paths": { 

450 "/items/": { 

451 "get": { 

452 "summary": "Read Items", 

453 "operationId": "read_items_items__get", 

454 "responses": { 

455 "200": { 

456 "description": "Successful Response", 

457 "content": { 

458 "application/json": { 

459 "schema": { 

460 "items": { 

461 "$ref": "#/components/schemas/Item" 

462 }, 

463 "type": "array", 

464 "title": "Response Read Items Items Get", 

465 } 

466 } 

467 }, 

468 } 

469 }, 

470 }, 

471 "post": { 

472 "summary": "Create Item", 

473 "operationId": "create_item_items__post", 

474 "requestBody": { 

475 "content": { 

476 "application/json": { 

477 "schema": {"$ref": "#/components/schemas/Item"} 

478 } 

479 }, 

480 "required": True, 

481 }, 

482 "responses": { 

483 "200": { 

484 "description": "Successful Response", 

485 "content": { 

486 "application/json": { 

487 "schema": {"$ref": "#/components/schemas/Item"} 

488 } 

489 }, 

490 }, 

491 "402": { 

492 "description": "Payment Required", 

493 "content": { 

494 "application/json": { 

495 "schema": {"$ref": "#/components/schemas/Item"} 

496 } 

497 }, 

498 }, 

499 "422": { 

500 "description": "Validation Error", 

501 "content": { 

502 "application/json": { 

503 "schema": { 

504 "$ref": "#/components/schemas/HTTPValidationError" 

505 } 

506 } 

507 }, 

508 }, 

509 }, 

510 }, 

511 }, 

512 "/items-list/": { 

513 "post": { 

514 "summary": "Create Item List", 

515 "operationId": "create_item_list_items_list__post", 

516 "requestBody": { 

517 "content": { 

518 "application/json": { 

519 "schema": { 

520 "items": {"$ref": "#/components/schemas/Item"}, 

521 "type": "array", 

522 "title": "Item", 

523 } 

524 } 

525 }, 

526 "required": True, 

527 }, 

528 "responses": { 

529 "200": { 

530 "description": "Successful Response", 

531 "content": {"application/json": {"schema": {}}}, 

532 }, 

533 "422": { 

534 "description": "Validation Error", 

535 "content": { 

536 "application/json": { 

537 "schema": { 

538 "$ref": "#/components/schemas/HTTPValidationError" 

539 } 

540 } 

541 }, 

542 }, 

543 }, 

544 } 

545 }, 

546 "/with-computed-field/": { 

547 "post": { 

548 "summary": "Create With Computed Field", 

549 "operationId": "create_with_computed_field_with_computed_field__post", 

550 "requestBody": { 

551 "content": { 

552 "application/json": { 

553 "schema": { 

554 "$ref": "#/components/schemas/WithComputedField-Input" 

555 } 

556 } 

557 }, 

558 "required": True, 

559 }, 

560 "responses": { 

561 "200": { 

562 "description": "Successful Response", 

563 "content": { 

564 "application/json": { 

565 "schema": { 

566 "$ref": "#/components/schemas/WithComputedField-Output" 

567 } 

568 } 

569 }, 

570 }, 

571 "422": { 

572 "description": "Validation Error", 

573 "content": { 

574 "application/json": { 

575 "schema": { 

576 "$ref": "#/components/schemas/HTTPValidationError" 

577 } 

578 } 

579 }, 

580 }, 

581 }, 

582 }, 

583 }, 

584 }, 

585 "components": { 

586 "schemas": { 

587 "HTTPValidationError": { 

588 "properties": { 

589 "detail": { 

590 "items": { 

591 "$ref": "#/components/schemas/ValidationError" 

592 }, 

593 "type": "array", 

594 "title": "Detail", 

595 } 

596 }, 

597 "type": "object", 

598 "title": "HTTPValidationError", 

599 }, 

600 "Item": { 

601 "properties": { 

602 "name": {"type": "string", "title": "Name"}, 

603 "description": { 

604 "anyOf": [{"type": "string"}, {"type": "null"}], 

605 "title": "Description", 

606 }, 

607 "sub": { 

608 "anyOf": [ 

609 {"$ref": "#/components/schemas/SubItem"}, 

610 {"type": "null"}, 

611 ] 

612 }, 

613 }, 

614 "type": "object", 

615 "required": ["name"], 

616 "title": "Item", 

617 }, 

618 "SubItem": { 

619 "properties": { 

620 "subname": {"type": "string", "title": "Subname"}, 

621 "sub_description": { 

622 "anyOf": [{"type": "string"}, {"type": "null"}], 

623 "title": "Sub Description", 

624 }, 

625 "tags": { 

626 "items": {"type": "string"}, 

627 "type": "array", 

628 "title": "Tags", 

629 "default": [], 

630 }, 

631 }, 

632 "type": "object", 

633 "required": ["subname"], 

634 "title": "SubItem", 

635 }, 

636 "WithComputedField-Input": { 

637 "properties": {"name": {"type": "string", "title": "Name"}}, 

638 "type": "object", 

639 "required": ["name"], 

640 "title": "WithComputedField", 

641 }, 

642 "WithComputedField-Output": { 

643 "properties": { 

644 "name": {"type": "string", "title": "Name"}, 

645 "computed_field": { 

646 "type": "string", 

647 "title": "Computed Field", 

648 "readOnly": True, 

649 }, 

650 }, 

651 "type": "object", 

652 "required": ["name", "computed_field"], 

653 "title": "WithComputedField", 

654 }, 

655 "ValidationError": { 

656 "properties": { 

657 "ctx": {"title": "Context", "type": "object"}, 

658 "input": {"title": "Input"}, 

659 "loc": { 

660 "items": { 

661 "anyOf": [{"type": "string"}, {"type": "integer"}] 

662 }, 

663 "type": "array", 

664 "title": "Location", 

665 }, 

666 "msg": {"type": "string", "title": "Message"}, 

667 "type": {"type": "string", "title": "Error Type"}, 

668 }, 

669 "type": "object", 

670 "required": ["loc", "msg", "type"], 

671 "title": "ValidationError", 

672 }, 

673 } 

674 }, 

675 } 

676 )