Coverage for tests/test_get_model_definitions_formfeed_escape.py: 100%
41 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-09-22 00:03 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-09-22 00:03 +0000
1from typing import Any, Iterator, Set, Type 1abcdef
3import fastapi._compat 1abcdef
4import fastapi.openapi.utils 1abcdef
5import pydantic.schema 1abcdef
6import pytest 1abcdef
7from fastapi import FastAPI 1abcdef
8from pydantic import BaseModel 1abcdef
9from starlette.testclient import TestClient 1abcdef
11from .utils import needs_pydanticv1 1abcdef
14class Address(BaseModel): 1abcdef
15 """
16 This is a public description of an Address
17 \f
18 You can't see this part of the docstring, it's private!
19 """
21 line_1: str 1abcdef
22 city: str 1abcdef
23 state_province: str 1abcdef
26class Facility(BaseModel): 1abcdef
27 id: str 1abcdef
28 address: Address 1abcdef
31app = FastAPI() 1abcdef
33client = TestClient(app) 1abcdef
36@app.get("/facilities/{facility_id}") 1abcdef
37def get_facility(facility_id: str) -> Facility: ... 1abcdef
40openapi_schema = { 1abcdef
41 "components": {
42 "schemas": {
43 "Address": {
44 # NOTE: the description of this model shows only the public-facing text, before the `\f` in docstring
45 "description": "This is a public description of an Address\n",
46 "properties": {
47 "city": {"title": "City", "type": "string"},
48 "line_1": {"title": "Line 1", "type": "string"},
49 "state_province": {"title": "State Province", "type": "string"},
50 },
51 "required": ["line_1", "city", "state_province"],
52 "title": "Address",
53 "type": "object",
54 },
55 "Facility": {
56 "properties": {
57 "address": {"$ref": "#/components/schemas/Address"},
58 "id": {"title": "Id", "type": "string"},
59 },
60 "required": ["id", "address"],
61 "title": "Facility",
62 "type": "object",
63 },
64 "HTTPValidationError": {
65 "properties": {
66 "detail": {
67 "items": {"$ref": "#/components/schemas/ValidationError"},
68 "title": "Detail",
69 "type": "array",
70 }
71 },
72 "title": "HTTPValidationError",
73 "type": "object",
74 },
75 "ValidationError": {
76 "properties": {
77 "loc": {
78 "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
79 "title": "Location",
80 "type": "array",
81 },
82 "msg": {"title": "Message", "type": "string"},
83 "type": {"title": "Error Type", "type": "string"},
84 },
85 "required": ["loc", "msg", "type"],
86 "title": "ValidationError",
87 "type": "object",
88 },
89 }
90 },
91 "info": {"title": "FastAPI", "version": "0.1.0"},
92 "openapi": "3.1.0",
93 "paths": {
94 "/facilities/{facility_id}": {
95 "get": {
96 "operationId": "get_facility_facilities__facility_id__get",
97 "parameters": [
98 {
99 "in": "path",
100 "name": "facility_id",
101 "required": True,
102 "schema": {"title": "Facility Id", "type": "string"},
103 }
104 ],
105 "responses": {
106 "200": {
107 "content": {
108 "application/json": {
109 "schema": {"$ref": "#/components/schemas/Facility"}
110 }
111 },
112 "description": "Successful Response",
113 },
114 "422": {
115 "content": {
116 "application/json": {
117 "schema": {
118 "$ref": "#/components/schemas/HTTPValidationError"
119 }
120 }
121 },
122 "description": "Validation Error",
123 },
124 },
125 "summary": "Get Facility",
126 }
127 }
128 },
129}
132def test_openapi_schema(): 1abcdef
133 """
134 Sanity check to ensure our app's openapi schema renders as we expect
135 """
136 response = client.get("/openapi.json") 1mnopqr
137 assert response.status_code == 200, response.text 1mnopqr
138 assert response.json() == openapi_schema 1mnopqr
141class SortedTypeSet(set): 1abcdef
142 """
143 Set of Types whose `__iter__()` method yields results sorted by the type names
144 """
146 def __init__(self, seq: Set[Type[Any]], *, sort_reversed: bool): 1abcdef
147 super().__init__(seq) 1ghijkl
148 self.sort_reversed = sort_reversed 1ghijkl
150 def __iter__(self) -> Iterator[Type[Any]]: 1abcdef
151 members_sorted = sorted( 1ghijkl
152 super().__iter__(),
153 key=lambda type_: type_.__name__,
154 reverse=self.sort_reversed,
155 )
156 yield from members_sorted 1ghijkl
159@needs_pydanticv1 1abcdef
160@pytest.mark.parametrize("sort_reversed", [True, False]) 1abcdef
161def test_model_description_escaped_with_formfeed(sort_reversed: bool): 1abcdef
162 """
163 Regression test for bug fixed by https://github.com/fastapi/fastapi/pull/6039.
165 Test `get_model_definitions` with models passed in different order.
166 """
167 all_fields = fastapi.openapi.utils.get_fields_from_routes(app.routes) 1ghijkl
169 flat_models = fastapi._compat.get_flat_models_from_fields( 1ghijkl
170 all_fields, known_models=set()
171 )
172 model_name_map = pydantic.schema.get_model_name_map(flat_models) 1ghijkl
174 expected_address_description = "This is a public description of an Address\n" 1ghijkl
176 models = fastapi._compat.get_model_definitions( 1ghijkl
177 flat_models=SortedTypeSet(flat_models, sort_reversed=sort_reversed),
178 model_name_map=model_name_map,
179 )
180 assert models["Address"]["description"] == expected_address_description 1ghijkl