Skip to content

Commit aad4b0b

Browse files
author
Sindhu Kothuri
committed
Add tests for recursive models, DTO handling, and serialization consistency
1 parent 625d9b5 commit aad4b0b

1 file changed

Lines changed: 180 additions & 0 deletions

File tree

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
from __future__ import annotations
2+
3+
from pydantic import BaseModel
4+
5+
from litestar import Litestar, get
6+
from litestar.dto import DTOConfig
7+
from litestar.plugins.pydantic import PydanticDTO
8+
from litestar.testing import TestClient
9+
10+
11+
class Node(BaseModel):
12+
name: str
13+
child: Node | None = None
14+
15+
16+
def test_self_referencing_pydantic_model_does_not_crash() -> None:
17+
Node.model_rebuild()
18+
node = Node(name="root", child=Node(name="child"))
19+
assert node.name == "root"
20+
assert node.child is not None
21+
assert node.child.name == "child"
22+
23+
24+
@get("/node")
25+
async def get_node() -> Node:
26+
return Node(name="root", child=Node(name="child"))
27+
28+
29+
def test_self_referencing_pydantic_model_openapi_schema() -> None:
30+
Node.model_rebuild()
31+
app = Litestar(route_handlers=[get_node])
32+
schema = app.openapi_schema
33+
assert schema is not None
34+
assert schema.components is not None
35+
assert schema.components.schemas is not None
36+
assert "Node" in schema.components.schemas
37+
38+
node_schema = schema.components.schemas["Node"]
39+
assert node_schema is not None
40+
properties = node_schema.properties
41+
assert properties is not None
42+
assert "child" in properties
43+
child_schema = properties["child"]
44+
assert child_schema is not None
45+
child_schema_dict = child_schema.to_schema()
46+
assert "$ref" in str(child_schema_dict)
47+
assert "Node" in str(child_schema_dict)
48+
49+
50+
class User(BaseModel):
51+
name: str
52+
group: Group | None = None
53+
54+
55+
class Group(BaseModel):
56+
name: str
57+
users: list[User] = []
58+
59+
60+
@get("/group")
61+
async def get_group() -> Group:
62+
return Group(name="admins", users=[User(name="alice")])
63+
64+
65+
def test_mutually_recursive_pydantic_model_openapi_schema() -> None:
66+
User.model_rebuild()
67+
Group.model_rebuild()
68+
app = Litestar(route_handlers=[get_group])
69+
schema = app.openapi_schema
70+
assert schema is not None
71+
assert schema.components is not None
72+
assert schema.components.schemas is not None
73+
assert "User" in schema.components.schemas
74+
assert "Group" in schema.components.schemas
75+
user_schema = schema.components.schemas["User"]
76+
group_schema = schema.components.schemas["Group"]
77+
user_props = user_schema.properties
78+
assert user_props is not None
79+
assert "group" in user_props
80+
group_field = user_props["group"]
81+
group_field_dict = group_field.to_schema()
82+
assert "$ref" in str(group_field_dict)
83+
assert "Group" in str(group_field_dict)
84+
group_props = group_schema.properties
85+
assert group_props is not None
86+
assert "users" in group_props
87+
users_field = group_props["users"]
88+
users_field_dict = users_field.to_schema()
89+
assert "$ref" in str(users_field_dict)
90+
assert "User" in str(users_field_dict)
91+
92+
93+
class NodeDTO(PydanticDTO[Node]):
94+
config = DTOConfig()
95+
96+
97+
def test_recursive_model_dto_generation() -> None:
98+
Node.model_rebuild()
99+
app = Litestar(route_handlers=[get_node], dto=NodeDTO)
100+
assert app is not None
101+
102+
103+
def test_recursive_model_dto_response_serialization() -> None:
104+
Node.model_rebuild()
105+
app = Litestar(route_handlers=[get_node], dto=NodeDTO)
106+
with TestClient(app=app) as client:
107+
response = client.get("/node")
108+
assert response.status_code == 200
109+
assert response.json() == {
110+
"name": "root",
111+
"child": {
112+
"name": "child",
113+
},
114+
}
115+
116+
117+
class UserDTO(PydanticDTO[User]):
118+
config = DTOConfig()
119+
120+
121+
class GroupDTO(PydanticDTO[Group]):
122+
config = DTOConfig()
123+
124+
125+
def test_mutual_recursive_dto_response_serialization() -> None:
126+
User.model_rebuild()
127+
Group.model_rebuild()
128+
app = Litestar(
129+
route_handlers=[get_group],
130+
dto=GroupDTO,
131+
)
132+
with TestClient(app=app) as client:
133+
response = client.get("/group")
134+
assert response.status_code == 200
135+
data = response.json()
136+
assert data["name"] == "admins"
137+
assert isinstance(data["users"], list)
138+
assert data["users"][0]["name"] == "alice"
139+
assert "group" not in data["users"][0] or data["users"][0]["group"] is None
140+
141+
142+
def test_deeply_nested_recursive_dto_response_serialization() -> None:
143+
Node.model_rebuild()
144+
145+
@get("/deep-node")
146+
async def get_deep_node() -> Node:
147+
return Node(
148+
name="level-1",
149+
child=Node(
150+
name="level-2",
151+
child=Node(
152+
name="level-3",
153+
child=Node(name="level-4"),
154+
),
155+
),
156+
)
157+
158+
app = Litestar(route_handlers=[get_deep_node], dto=NodeDTO)
159+
with TestClient(app=app) as client:
160+
response = client.get("/deep-node")
161+
assert response.status_code == 200
162+
assert response.json() == {
163+
"name": "level-1",
164+
"child": {
165+
"name": "level-2",
166+
},
167+
}
168+
169+
170+
def test_recursive_dto_preserves_expected_fields() -> None:
171+
Node.model_rebuild()
172+
app = Litestar(route_handlers=[get_node], dto=NodeDTO)
173+
with TestClient(app=app) as client:
174+
response = client.get("/node")
175+
data = response.json()
176+
assert response.status_code == 200
177+
assert set(data.keys()) == {"name", "child"}
178+
assert set(data["child"].keys()) == {"name"}
179+
assert data["name"] == "root"
180+
assert data["child"]["name"] == "child"

0 commit comments

Comments
 (0)