1+ import re
2+ import pytest
3+ from unittest import TestCase
4+ from fastapi import FastAPI
5+ from osbot_utils .type_safe .primitives .core .Safe_Float import Safe_Float
6+ from osbot_utils .type_safe .primitives .domains .identifiers .safe_str .Safe_Str__Display_Name import Safe_Str__Display_Name
7+ from osbot_utils .type_safe .primitives .core .Safe_UInt import Safe_UInt
8+ from starlette .testclient import TestClient
9+ from osbot_fast_api .api .routes .type_safe .Type_Safe__Route__Registration import Type_Safe__Route__Registration
10+ from osbot_utils .type_safe .Type_Safe import Type_Safe
11+ from osbot_utils .type_safe .primitives .domains .identifiers .Safe_Id import Safe_Id
12+
13+
14+ class Schema__Core_User (Type_Safe ): # Schema for Core FastAPI test
15+ user_id : Safe_Id
16+ name : Safe_Str__Display_Name
17+ age : Safe_UInt
18+
19+
20+ class Schema__Core_Product (Type_Safe ): # Another schema for testing
21+ product_id : Safe_Id
22+ price : Safe_Float
23+ in_stock : bool = True
24+
25+
26+ class test_Fast_API__Type_Safe__support_for_core_FastAPI (TestCase ):
27+ """
28+ Important TEST: This demonstrates that Type_Safe route registration
29+ works with Core FastAPI, not just our Fast_API wrapper class.
30+
31+ This means developers can:
32+ 1. Add Type_Safe to existing FastAPI projects incrementally
33+ 2. Use Type_Safe without adopting our full Fast_API class
34+ 3. Get automatic type conversion and validation in Core FastAPI
35+ """
36+
37+ @classmethod
38+ def setUpClass (cls ): # ONE-TIME setup with core FastAPI
39+ cls .app = FastAPI () # Pure FastAPI - no Fast_API wrapper!
40+ cls .registration = Type_Safe__Route__Registration ()
41+
42+ # Register routes using Type_Safe system
43+ cls .registration .register_route (cls .app .router , cls .create_user , ['POST' ])
44+ cls .registration .register_route (cls .app .router , cls .get_user , ['GET' ])
45+ cls .registration .register_route (cls .app .router , cls .create_product , ['POST' ])
46+
47+ cls .client = TestClient (cls .app )
48+
49+ @staticmethod
50+ def create_user (user : Schema__Core_User ) -> Schema__Core_User : # Type_Safe parameter and return
51+ return user
52+
53+ @staticmethod
54+ def get_user (user_id : Safe_Id ) -> dict : # Type_Safe primitive parameter
55+ return {'user_id' : str (user_id ), 'found' : True }
56+
57+ @staticmethod
58+ def create_product (product : Schema__Core_Product ) -> Schema__Core_Product :
59+ return product
60+
61+ def test__Core_fastapi__handles_type_safe_body_params (self ): # Test Core FastAPI handles Type_Safe body parameters
62+ response = self .client .post ('/create-user' , json = { 'user_id' : 'USER-456' ,
63+ 'name' : 'Core Test' ,
64+ 'age' : 30 })
65+
66+ assert response .status_code == 200
67+ result = response .json ()
68+
69+ assert result ['user_id' ] == 'USER-456' # Type_Safe conversion worked in Core FastAPI!
70+ assert result ['name' ] == 'Core Test'
71+ assert result ['age' ] == 30
72+
73+ def test__Core_fastapi__handles_safe_primitives (self ): # Test Core FastAPI handles Safe_Str primitives
74+ response = self .client .get ('/get-user' , params = {'user_id' : 'USER-789' })
75+
76+ assert response .status_code == 200
77+ result = response .json ()
78+
79+ # Safe_Id was handled correctly
80+ assert result ['user_id' ] == 'USER-789'
81+ assert result ['found' ] is True
82+
83+ def test__Core_fastapi__auto_validates (self ): # Test Core FastAPI gets Type_Safe validation
84+
85+ error_message = "Safe_UInt must be >= 0, got -5"
86+ with pytest .raises (ValueError , match = re .escape (error_message )):
87+ response = self .client .post ('/create-user' , json = { 'user_id' : 'USER-999' , # Invalid age will should be rejected by Safe_UInt
88+ 'name' : 'Invalid User' ,
89+ 'age' : - 5 }) # Negative not allowed in Safe_Int
90+
91+
92+ # Should get validation error (not 200) # note we don't get this far, since self.client is the one raising the exception
93+ # assert response.status_code == 400 # Type_Safe validation worked!
94+
95+ def test__Core_fastapi__sanitizes_input (self ): # Test Core FastAPI gets Type_Safe sanitization
96+ # Safe_Id should sanitize special characters
97+ response = self .client .post ('/create-user' , json = {
98+ 'user_id' : 'USER!@#$%123' , # Special chars will be sanitized
99+ 'name' : 'Test' ,
100+ 'age' : 25
101+ })
102+
103+ assert response .status_code == 200
104+ result = response .json ()
105+
106+ # Safe_Id sanitized the input automatically
107+ assert result ['user_id' ] == 'USER_____123' # Special chars replaced with _
108+
109+ def test__Core_fastapi__complex_schema (self ): # Test Core FastAPI handles complex Type_Safe schemas
110+ response = self .client .post ('/create-product' , json = {
111+ 'product_id' : 'PROD-001' ,
112+ 'price' : 99.99 ,
113+ 'in_stock' : False
114+ })
115+
116+ assert response .status_code == 200
117+ result = response .json ()
118+
119+ assert result ['product_id' ] == 'PROD-001'
120+ assert result ['price' ] == 99.99
121+ assert result ['in_stock' ] is False
122+
123+ def test__Core_fastapi__route_extraction_works (self ): # Test that route extraction works on Core FastAPI
124+ from osbot_fast_api .client .Fast_API__Route__Extractor import Fast_API__Route__Extractor
125+
126+ extractor = Fast_API__Route__Extractor (app = self .app , include_default = False )
127+ collection = extractor .extract_routes ()
128+
129+ # Should extract all our Type_Safe routes
130+ route_names = [r .method_name for r in collection .routes ]
131+ assert 'create_user' in route_names
132+ assert 'get_user' in route_names
133+ assert 'create_product' in route_names
134+
135+ # Verify original types preserved
136+ create_user_route = next (r for r in collection .routes if r .method_name == 'create_user' )
137+ assert create_user_route .body_params [0 ].param_type is Schema__Core_User # Original Type_Safe class
138+ assert create_user_route .return_type is Schema__Core_User
139+
140+ def test__Core_fastapi__serialization_round_trip (self ): # Test serialization works with Core FastAPI routes
141+ from osbot_fast_api .client .Fast_API__Route__Extractor import Fast_API__Route__Extractor
142+
143+ extractor = Fast_API__Route__Extractor (app = self .app , include_default = False )
144+ collection = extractor .extract_routes ()
145+
146+ # Serialize
147+ json_data = collection .json ()
148+
149+ # Deserialize
150+ restored = collection .__class__ .from_json (json_data )
151+
152+ # Perfect round-trip
153+ assert collection .obj () == restored .obj ()
154+
155+ # No __BaseModel artifacts
156+ json_str = str (json_data )
157+ assert '__BaseModel' not in json_str
0 commit comments