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