Skip to content

Commit ab745cd

Browse files
committed
added tests for test_Type_Safe__Route__Converter
1 parent 9f7a3eb commit ab745cd

3 files changed

Lines changed: 286 additions & 3 deletions

File tree

poetry.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
File renamed without changes.
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
from unittest import TestCase
2+
3+
from osbot_utils.type_safe.primitives.domains.web.safe_str.Safe_Str__Email import Safe_Str__Email
4+
5+
from osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Display_Name import Safe_Str__Display_Name
6+
7+
from osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id import Safe_Str__Id
8+
9+
from osbot_utils.utils.Objects import __
10+
from osbot_utils.type_safe.Type_Safe import Type_Safe
11+
from osbot_utils.type_safe.primitives.core.Safe_Str import Safe_Str
12+
from osbot_utils.type_safe.primitives.core.Safe_Int import Safe_Int
13+
from osbot_utils.type_safe.primitives.core.Safe_Float import Safe_Float
14+
from osbot_utils.type_safe.primitives.domains.identifiers.Safe_Id import Safe_Id
15+
from osbot_fast_api.api.routes.Type_Safe__Route__Analyzer import Type_Safe__Route__Analyzer
16+
from osbot_fast_api.api.routes.Type_Safe__Route__Converter import Type_Safe__Route__Converter
17+
from pydantic import BaseModel
18+
19+
20+
class test_Type_Safe__Route__Converter(TestCase):
21+
22+
@classmethod
23+
def setUpClass(cls): # Setup expensive resources once
24+
cls.analyzer = Type_Safe__Route__Analyzer()
25+
cls.converter = Type_Safe__Route__Converter()
26+
27+
def test__init__(self): # Test converter initialization
28+
with Type_Safe__Route__Converter() as _:
29+
assert type(_) is Type_Safe__Route__Converter
30+
31+
def test_enrich_signature_with_conversions__no_conversion_needed(self): # Test signature with no Type_Safe parameters
32+
def simple_endpoint(name: str, age: int):
33+
return {"name": name, "age": age}
34+
35+
with self.analyzer as analyzer:
36+
signature = analyzer.analyze_function(simple_endpoint)
37+
38+
with self.converter as _:
39+
enriched = _.enrich_signature_with_conversions(signature)
40+
41+
assert len(enriched.type_safe_conversions) == 0 # No Type_Safe classes to convert
42+
assert enriched.return_needs_conversion is False
43+
44+
def test_enrich_signature_with_conversions__type_safe_param(self): # Test signature with Type_Safe parameter
45+
class User_Data(Type_Safe):
46+
name : Safe_Str
47+
email : Safe_Str
48+
49+
def create_user(user_data: User_Data):
50+
return user_data
51+
52+
with self.analyzer as analyzer:
53+
signature = analyzer.analyze_function(create_user)
54+
55+
with self.converter as _:
56+
enriched = _.enrich_signature_with_conversions(signature)
57+
58+
assert 'user_data' in enriched.type_safe_conversions # Conversion entry created
59+
type_safe_class, basemodel_class = enriched.type_safe_conversions['user_data']
60+
assert type_safe_class is User_Data
61+
assert issubclass(basemodel_class, BaseModel) # Converted to Pydantic
62+
63+
param_info = enriched.parameters[0]
64+
assert param_info.converted_type is not None
65+
assert issubclass(param_info.converted_type, BaseModel)
66+
67+
def test_enrich_signature_with_conversions__type_safe_return(self): # Test signature with Type_Safe return type
68+
class Response_Data(Type_Safe):
69+
status : Safe_Str
70+
code : Safe_Int
71+
72+
def get_status() -> Response_Data:
73+
return Response_Data(status=Safe_Str("ok"), code=Safe_Int(200))
74+
75+
with self.analyzer as analyzer:
76+
signature = analyzer.analyze_function(get_status)
77+
78+
with self.converter as _:
79+
enriched = _.enrich_signature_with_conversions(signature)
80+
81+
assert enriched.return_needs_conversion is True
82+
assert enriched.return_converted_type is not None
83+
assert issubclass(enriched.return_converted_type, BaseModel)
84+
85+
def test_convert_parameter_value__primitive_conversion(self): # Test converting raw value to Safe primitive
86+
class User_Data(Type_Safe):
87+
user_id : Safe_Id
88+
89+
def endpoint(user_id: Safe_Id):
90+
return user_id
91+
92+
with self.analyzer as analyzer:
93+
signature = analyzer.analyze_function(endpoint)
94+
95+
with self.converter as _:
96+
converted = _.convert_parameter_value('user_id', 'raw-string-id', signature)
97+
98+
assert type(converted) is Safe_Id
99+
assert converted == Safe_Id('raw-string-id')
100+
101+
def test_convert_parameter_value__type_safe_from_dict(self): # Test converting dict to Type_Safe object
102+
class Product_Data(Type_Safe):
103+
name : Safe_Str
104+
price : Safe_Int
105+
106+
def create_product(product: Product_Data):
107+
return product
108+
109+
with self.analyzer as analyzer:
110+
signature = analyzer.analyze_function(create_product)
111+
signature = self.converter.enrich_signature_with_conversions(signature)
112+
113+
product_dict = {'name': 'Widget', 'price': 99}
114+
115+
with self.converter as _:
116+
converted = _.convert_parameter_value('product', product_dict, signature)
117+
118+
assert type(converted) is Product_Data
119+
assert converted.obj() == __(name='Widget', price=99)
120+
121+
def test_convert_parameter_value__type_safe_with_nested_primitives(self): # Test converting dict with nested Safe primitives
122+
class Order_Data(Type_Safe):
123+
order_id : Safe_Id
124+
quantity : Safe_Int
125+
price : Safe_Float
126+
127+
def create_order(order: Order_Data):
128+
return order
129+
130+
with self.analyzer as analyzer:
131+
signature = analyzer.analyze_function(create_order)
132+
signature = self.converter.enrich_signature_with_conversions(signature)
133+
134+
order_dict = { 'order_id' : 'ORD-123',
135+
'quantity' : 5 ,
136+
'price' : 99.99 }
137+
138+
with self.converter as _:
139+
converted = _.convert_parameter_value('order', order_dict, signature)
140+
141+
assert type(converted) is Order_Data
142+
assert type(converted.order_id) is Safe_Id
143+
assert type(converted.quantity) is Safe_Int
144+
assert type(converted.price) is Safe_Float
145+
146+
def test_convert_return_value__type_safe_to_dict(self): # Test converting Type_Safe return to dict
147+
class Response_Data(Type_Safe):
148+
message : Safe_Str
149+
code : Safe_Int
150+
151+
def endpoint() -> Response_Data:
152+
return Response_Data(message=Safe_Str("success"), code=Safe_Int(200))
153+
154+
with self.analyzer as analyzer:
155+
signature = analyzer.analyze_function(endpoint)
156+
signature = self.converter.enrich_signature_with_conversions(signature)
157+
158+
result = Response_Data(message=Safe_Str("success"), code=Safe_Int(200))
159+
160+
with self.converter as _:
161+
converted = _.convert_return_value(result, signature)
162+
163+
assert type(converted) is dict
164+
assert converted == {'message': 'success', 'code': 200}
165+
166+
def test_convert_return_value__primitive_to_base(self): # Test converting Safe primitive return to base type
167+
def endpoint() -> Safe_Id:
168+
return Safe_Id("test-id-123")
169+
170+
with self.analyzer as analyzer:
171+
signature = analyzer.analyze_function(endpoint)
172+
173+
result = Safe_Id("test-id-123")
174+
175+
with self.converter as _:
176+
converted = _.convert_return_value(result, signature)
177+
178+
assert type(converted) is str # Converted to base type
179+
assert converted == "test-id-123"
180+
181+
def test_convert_return_value__no_conversion_needed(self): # Test return value that doesn't need conversion
182+
def endpoint() -> dict:
183+
return {"status": "ok"}
184+
185+
with self.analyzer as analyzer:
186+
signature = analyzer.analyze_function(endpoint)
187+
188+
result = {"status": "ok"}
189+
190+
with self.converter as _:
191+
converted = _.convert_return_value(result, signature)
192+
193+
assert converted is result # No conversion, same object
194+
195+
def test_find_parameter__existing_param(self): # Test finding parameter in signature
196+
def endpoint(user_id: Safe_Id, name: Safe_Str):
197+
return {"user_id": user_id, "name": name}
198+
199+
with self.analyzer as analyzer:
200+
signature = analyzer.analyze_function(endpoint)
201+
202+
with self.converter as _:
203+
param_info = _.find_parameter(signature, 'user_id')
204+
205+
assert param_info is not None
206+
assert param_info.name == 'user_id'
207+
208+
def test_find_parameter__nonexistent_param(self): # Test finding non-existent parameter
209+
def endpoint(user_id: Safe_Id):
210+
return user_id
211+
212+
with self.analyzer as analyzer:
213+
signature = analyzer.analyze_function(endpoint)
214+
215+
with self.converter as _:
216+
param_info = _.find_parameter(signature, 'nonexistent')
217+
218+
assert param_info is None
219+
220+
def test_complex_scenario__mixed_conversions(self): # Test complex scenario with multiple conversion types
221+
class Product_Data(Type_Safe):
222+
name : Safe_Str
223+
price : Safe_Int
224+
category : Safe_Id
225+
226+
class Product_Response(Type_Safe):
227+
product_id : Safe_Id
228+
name : Safe_Str
229+
status : Safe_Str
230+
231+
def create_product(store_id : Safe_Id ,
232+
product_data : Product_Data ,
233+
notify : bool = False
234+
) -> Product_Response:
235+
return Product_Response(
236+
product_id = Safe_Id("PROD-123") ,
237+
name = product_data.name ,
238+
status = Safe_Str("created")
239+
)
240+
241+
with self.analyzer as analyzer:
242+
signature = analyzer.analyze_function(create_product)
243+
244+
with self.converter as _:
245+
enriched = _.enrich_signature_with_conversions(signature)
246+
247+
assert 'store_id' in enriched.primitive_conversions # Primitive conversion
248+
assert 'product_data' in enriched.type_safe_conversions # Type_Safe conversion
249+
assert enriched.return_needs_conversion is True
250+
251+
product_dict = {'name': 'Widget', 'price': 100, 'category': 'CAT-1'}
252+
converted_store_id = _.convert_parameter_value('store_id', 'STORE-42', enriched)
253+
converted_product = _.convert_parameter_value('product_data', product_dict, enriched)
254+
255+
assert type(converted_store_id) is Safe_Id
256+
assert type(converted_product) is Product_Data
257+
assert type(converted_product.name) is Safe_Str
258+
assert type(converted_product.price) is Safe_Int
259+
assert type(converted_product.category) is Safe_Id
260+
261+
def test_convert_parameter_value__from_basemodel_instance(self): # Test converting from Pydantic BaseModel instance
262+
class User_Data(Type_Safe):
263+
name : Safe_Str__Display_Name
264+
email : Safe_Str__Email
265+
266+
def create_user(user: User_Data):
267+
return user
268+
269+
with self.analyzer as analyzer:
270+
signature = analyzer.analyze_function(create_user)
271+
signature = self.converter.enrich_signature_with_conversions(signature)
272+
273+
_, basemodel_class = signature.type_safe_conversions['user']
274+
basemodel_instance = basemodel_class(name='Alice', email='alice@test.com')
275+
276+
with self.converter as _:
277+
converted = _.convert_parameter_value('user', basemodel_instance, signature)
278+
279+
assert type(converted) is User_Data
280+
assert type(converted.name) is Safe_Str__Display_Name
281+
assert type(converted.email) is Safe_Str__Email
282+
assert converted.name == 'Alice'
283+
assert converted.email == 'alice@test.com'

0 commit comments

Comments
 (0)