Coverage for tests/test_fastapi.py: 100%

136 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-23 23:00 +0800

1"""測試 FastAPI 自動生成功能""" 

2 

3import pytest 

4from dataclasses import dataclass 

5from autocrud import AutoCRUD, MemoryStorage, FastAPIGenerator 

6 

7 

8@dataclass 

9class Product: 

10 name: str 

11 description: str 

12 price: float 

13 category: str 

14 

15 

16class TestFastAPIGenerator: 

17 """測試 FastAPI 生成器""" 

18 

19 def test_create_generator(self): 

20 """測試創建 FastAPI 生成器""" 

21 storage = MemoryStorage() 

22 crud = AutoCRUD(model=Product, storage=storage, resource_name="products") 

23 

24 generator = FastAPIGenerator(crud) 

25 

26 assert generator.crud == crud 

27 assert generator.request_model is not None 

28 assert generator.response_model is not None 

29 

30 def test_request_model_fields(self): 

31 """測試請求模型欄位""" 

32 storage = MemoryStorage() 

33 crud = AutoCRUD(model=Product, storage=storage, resource_name="products") 

34 generator = FastAPIGenerator(crud) 

35 

36 request_fields = list(generator.request_model.model_fields.keys()) 

37 

38 # 請求模型不應該包含 id 

39 assert 'name' in request_fields 

40 assert 'description' in request_fields 

41 assert 'price' in request_fields 

42 assert 'category' in request_fields 

43 assert 'id' not in request_fields 

44 

45 def test_response_model_fields(self): 

46 """測試響應模型欄位""" 

47 storage = MemoryStorage() 

48 crud = AutoCRUD(model=Product, storage=storage, resource_name="products") 

49 generator = FastAPIGenerator(crud) 

50 

51 response_fields = list(generator.response_model.model_fields.keys()) 

52 

53 # 響應模型應該包含 id 

54 assert 'name' in response_fields 

55 assert 'description' in response_fields 

56 assert 'price' in response_fields 

57 assert 'category' in response_fields 

58 assert 'id' in response_fields 

59 

60 def test_create_fastapi_app(self): 

61 """測試創建 FastAPI 應用""" 

62 storage = MemoryStorage() 

63 crud = AutoCRUD(model=Product, storage=storage, resource_name="products") 

64 generator = FastAPIGenerator(crud) 

65 

66 app = generator.create_fastapi_app( 

67 title="產品管理 API", 

68 description="測試 API", 

69 version="1.0.0" 

70 ) 

71 

72 assert app.title == "產品管理 API" 

73 assert app.description == "測試 API" 

74 assert app.version == "1.0.0" 

75 

76 def test_create_routes(self): 

77 """測試創建路由""" 

78 storage = MemoryStorage() 

79 crud = AutoCRUD(model=Product, storage=storage, resource_name="products") 

80 generator = FastAPIGenerator(crud) 

81 

82 from fastapi import FastAPI 

83 app = FastAPI() 

84 

85 generator.create_routes(app, "/api/v1") 

86 

87 # 檢查路由是否已添加 

88 routes = [] 

89 for route in app.routes: 

90 if hasattr(route, 'methods') and hasattr(route, 'path'): 

91 for method in route.methods: 

92 if method not in ['HEAD', 'OPTIONS']: 

93 routes.append(f"{method} {route.path}") 

94 

95 expected_routes = [ 

96 'POST /api/v1/products', 

97 'GET /api/v1/products/{resource_id}', 

98 'PUT /api/v1/products/{resource_id}', 

99 'DELETE /api/v1/products/{resource_id}', 

100 'GET /api/v1/products' 

101 ] 

102 

103 for expected_route in expected_routes: 

104 assert expected_route in routes 

105 

106 def test_pydantic_model_creation(self, sample_product_data): 

107 """測試 Pydantic 模型創建""" 

108 storage = MemoryStorage() 

109 crud = AutoCRUD(model=Product, storage=storage, resource_name="products") 

110 generator = FastAPIGenerator(crud) 

111 

112 # 測試請求模型 

113 request_instance = generator.request_model(**sample_product_data) 

114 request_dict = request_instance.model_dump() 

115 

116 assert request_dict['name'] == sample_product_data['name'] 

117 assert request_dict['price'] == sample_product_data['price'] 

118 

119 # 測試響應模型 

120 response_data = {**sample_product_data, 'id': 'test-id'} 

121 response_instance = generator.response_model(**response_data) 

122 response_dict = response_instance.model_dump() 

123 

124 assert response_dict['id'] == 'test-id' 

125 assert response_dict['name'] == sample_product_data['name'] 

126 

127 

128class TestAutoCRUDFastAPIIntegration: 

129 """測試 AutoCRUD 與 FastAPI 的整合""" 

130 

131 def test_create_fastapi_app_convenience_method(self): 

132 """測試便利方法創建 FastAPI 應用""" 

133 storage = MemoryStorage() 

134 crud = AutoCRUD(model=Product, storage=storage, resource_name="products") 

135 

136 app = crud.create_fastapi_app( 

137 title="產品 API", 

138 description="便利方法創建的 API" 

139 ) 

140 

141 assert app.title == "產品 API" 

142 assert app.description == "便利方法創建的 API" 

143 

144 def test_fastapi_app_has_health_endpoint(self): 

145 """測試 FastAPI 應用包含健康檢查端點""" 

146 storage = MemoryStorage() 

147 crud = AutoCRUD(model=Product, storage=storage, resource_name="products") 

148 

149 app = crud.create_fastapi_app() 

150 

151 # 檢查是否有健康檢查路由 

152 health_routes = [] 

153 for route in app.routes: 

154 if hasattr(route, 'path') and route.path == '/health': 

155 health_routes.append(route) 

156 

157 assert len(health_routes) > 0 

158 

159 def test_fastapi_app_has_openapi_docs(self): 

160 """測試 FastAPI 應用包含 OpenAPI 文檔端點""" 

161 storage = MemoryStorage() 

162 crud = AutoCRUD(model=Product, storage=storage, resource_name="products") 

163 

164 app = crud.create_fastapi_app() 

165 

166 # 檢查文檔路由 

167 doc_paths = [] 

168 for route in app.routes: 

169 if hasattr(route, 'path'): 

170 doc_paths.append(route.path) 

171 

172 assert '/docs' in doc_paths 

173 assert '/redoc' in doc_paths 

174 assert '/openapi.json' in doc_paths 

175 

176 

177class TestFastAPIAppBehavior: 

178 """測試 FastAPI 應用行為(不實際啟動服務器)""" 

179 

180 def test_app_creation_with_different_models(self): 

181 """測試使用不同模型創建應用""" 

182 @dataclass 

183 class User: 

184 name: str 

185 email: str 

186 age: int 

187 

188 @dataclass 

189 class Book: 

190 title: str 

191 author: str 

192 isbn: str 

193 

194 models = [ 

195 (User, "users"), 

196 (Book, "books"), 

197 (Product, "products") 

198 ] 

199 

200 for model, resource_name in models: 

201 storage = MemoryStorage() 

202 crud = AutoCRUD(model=model, storage=storage, resource_name=resource_name) 

203 

204 app = crud.create_fastapi_app(title=f"{model.__name__} API") 

205 

206 assert app.title == f"{model.__name__} API" 

207 

208 # 檢查是否有對應的路由 

209 routes = [] 

210 for route in app.routes: 

211 if hasattr(route, 'path'): 

212 routes.append(route.path) 

213 

214 assert f'/api/v1/{resource_name}' in routes 

215 

216 def test_custom_prefix_and_settings(self): 

217 """測試自定義前綴和設定""" 

218 storage = MemoryStorage() 

219 crud = AutoCRUD(model=Product, storage=storage, resource_name="products") 

220 generator = FastAPIGenerator(crud) 

221 

222 from fastapi import FastAPI 

223 app = FastAPI() 

224 

225 # 使用自定義前綴 

226 generator.create_routes(app, "/custom/v2") 

227 

228 routes = [] 

229 for route in app.routes: 

230 if hasattr(route, 'path'): 

231 routes.append(route.path) 

232 

233 assert '/custom/v2/products' in routes