test_api_integration.py
11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
"""
API集成测试
测试API接口的完整集成,包括请求处理、响应格式、错误处理等
"""
import pytest
import json
import asyncio
from unittest.mock import patch, Mock, AsyncMock
from fastapi.testclient import TestClient
# 导入API应用
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from api.app import app
@pytest.mark.integration
@pytest.mark.api
class TestAPIIntegration:
"""API集成测试"""
@pytest.fixture
def client(self):
"""创建测试客户端"""
return TestClient(app)
def test_search_api_basic(self, client):
"""测试基础搜索API"""
response = client.get("/search", params={"q": "红色连衣裙"})
assert response.status_code == 200
data = response.json()
# 验证响应结构
assert "hits" in data
assert "total" in data
assert "max_score" in data
assert "took_ms" in data
assert "query_info" in data
assert "performance_summary" in data
# 验证hits是列表
assert isinstance(data["hits"], list)
assert isinstance(data["total"], int)
assert isinstance(data["max_score"], (int, float))
assert isinstance(data["took_ms"], int)
def test_search_api_with_parameters(self, client):
"""测试带参数的搜索API"""
params = {
"q": "智能手机",
"size": 15,
"from": 5,
"enable_translation": False,
"enable_embedding": False,
"enable_rerank": True,
"min_score": 1.0
}
response = client.get("/search", params=params)
assert response.status_code == 200
data = response.json()
# 验证参数被正确传递
performance = data.get("performance_summary", {})
metadata = performance.get("metadata", {})
search_params = metadata.get("search_params", {})
assert search_params.get("size") == 15
assert search_params.get("from") == 5
assert search_params.get("min_score") == 1.0
feature_flags = metadata.get("feature_flags", {})
assert feature_flags.get("enable_translation") is False
assert feature_flags.get("enable_embedding") is False
assert feature_flags.get("enable_rerank") is True
def test_search_api_complex_query(self, client):
"""测试复杂查询API"""
response = client.get("/search", params={"q": "手机 AND (华为 OR 苹果) ANDNOT 二手"})
assert response.status_code == 200
data = response.json()
# 验证复杂查询被处理
query_info = data.get("query_info", {})
performance = data.get("performance_summary", {})
query_analysis = performance.get("query_analysis", {})
# 对于复杂查询,is_simple_query应该是False
assert query_analysis.get("is_simple_query") is False
def test_search_api_missing_query(self, client):
"""测试缺少查询参数的API"""
response = client.get("/search")
assert response.status_code == 422 # Validation error
data = response.json()
# 验证错误信息
assert "detail" in data
def test_search_api_empty_query(self, client):
"""测试空查询API"""
response = client.get("/search", params={"q": ""})
assert response.status_code == 200
data = response.json()
# 空查询应该返回有效结果
assert "hits" in data
assert isinstance(data["hits"], list)
def test_search_api_with_filters(self, client):
"""测试带过滤器的搜索API"""
response = client.get("/search", params={
"q": "连衣裙",
"filters": json.dumps({"category_id": 1, "brand": "测试品牌"})
})
assert response.status_code == 200
data = response.json()
# 验证过滤器被应用
performance = data.get("performance_summary", {})
metadata = performance.get("metadata", {})
search_params = metadata.get("search_params", {})
filters = search_params.get("filters", {})
assert filters.get("category_id") == 1
assert filters.get("brand") == "测试品牌"
def test_search_api_performance_summary(self, client):
"""测试API性能摘要"""
response = client.get("/search", params={"q": "性能测试查询"})
assert response.status_code == 200
data = response.json()
performance = data.get("performance_summary", {})
# 验证性能摘要结构
assert "request_info" in performance
assert "query_analysis" in performance
assert "performance" in performance
assert "results" in performance
assert "metadata" in performance
# 验证request_info
request_info = performance["request_info"]
assert "reqid" in request_info
assert "uid" in request_info
assert len(request_info["reqid"]) == 8 # 8字符的reqid
# 验证performance
perf_data = performance["performance"]
assert "total_duration_ms" in perf_data
assert "stage_timings_ms" in perf_data
assert "stage_percentages" in perf_data
assert isinstance(perf_data["total_duration_ms"], (int, float))
assert perf_data["total_duration_ms"] >= 0
def test_search_api_error_handling(self, client):
"""测试API错误处理"""
# 模拟内部错误
with patch('api.app._searcher') as mock_searcher:
mock_searcher.search.side_effect = Exception("内部服务错误")
response = client.get("/search", params={"q": "错误测试"})
assert response.status_code == 500
data = response.json()
# 验证错误响应格式
assert "error" in data
assert "request_id" in data
assert len(data["request_id"]) == 8
def test_health_check_api(self, client):
"""测试健康检查API"""
response = client.get("/health")
assert response.status_code == 200
data = response.json()
# 验证健康检查响应
assert "status" in data
assert "timestamp" in data
assert "service" in data
assert "version" in data
assert data["status"] in ["healthy", "unhealthy"]
assert data["service"] == "search-engine-api"
def test_metrics_api(self, client):
"""测试指标API"""
response = client.get("/metrics")
# 根据实现,可能是JSON格式或Prometheus格式
assert response.status_code in [200, 404] # 404如果未实现
def test_concurrent_search_api(self, client):
"""测试并发搜索API"""
async def test_concurrent():
tasks = []
for i in range(10):
task = asyncio.create_task(
asyncio.to_thread(
client.get,
"/search",
params={"q": f"并发测试查询-{i}"}
)
)
tasks.append(task)
responses = await asyncio.gather(*tasks)
# 验证所有响应都成功
for response in responses:
assert response.status_code == 200
data = response.json()
assert "hits" in data
assert "performance_summary" in data
# 运行并发测试
asyncio.run(test_concurrent())
def test_search_api_response_time(self, client):
"""测试API响应时间"""
import time
start_time = time.time()
response = client.get("/search", params={"q": "响应时间测试"})
end_time = time.time()
response_time_ms = (end_time - start_time) * 1000
assert response.status_code == 200
# API响应时间应该合理(例如,小于5秒)
assert response_time_ms < 5000
# 验证响应中的时间信息
data = response.json()
assert data["took_ms"] >= 0
performance = data.get("performance_summary", {})
perf_data = performance.get("performance", {})
total_duration = perf_data.get("total_duration_ms", 0)
# 总处理时间应该包括API开销
assert total_duration > 0
def test_search_api_large_query(self, client):
"""测试大查询API"""
# 构造一个较长的查询
long_query = " " * 1000 + "红色连衣裙"
response = client.get("/search", params={"q": long_query})
assert response.status_code == 200
data = response.json()
# 验证长查询被正确处理
query_analysis = data.get("performance_summary", {}).get("query_analysis", {})
assert query_analysis.get("original_query") == long_query
def test_search_api_unicode_support(self, client):
"""测试API Unicode支持"""
unicode_queries = [
"红色连衣裙", # 中文
"red dress", # 英文
"robe rouge", # 法文
"赤いドレス", # 日文
"أحمر فستان", # 阿拉伯文
"👗🔴", # Emoji
]
for query in unicode_queries:
response = client.get("/search", params={"q": query})
assert response.status_code == 200
data = response.json()
# 验证Unicode查询被正确处理
query_analysis = data.get("performance_summary", {}).get("query_analysis", {})
assert query_analysis.get("original_query") == query
def test_search_api_request_id_tracking(self, client):
"""测试API请求ID跟踪"""
response = client.get("/search", params={"q": "请求ID测试"})
assert response.status_code == 200
data = response.json()
# 验证每个请求都有唯一的reqid
performance = data.get("performance_summary", {})
request_info = performance.get("request_info", {})
reqid = request_info.get("reqid")
assert reqid is not None
assert len(reqid) == 8
assert reqid.isalnum()
def test_search_api_rate_limiting(self, client):
"""测试API速率限制(如果实现了)"""
# 快速发送多个请求
responses = []
for i in range(20): # 发送20个快速请求
response = client.get("/search", params={"q": f"速率限制测试-{i}"})
responses.append(response)
# 检查是否有请求被限制
status_codes = [r.status_code for r in responses]
rate_limited = any(code == 429 for code in status_codes)
# 根据是否实现速率限制,验证结果
if rate_limited:
# 如果有速率限制,应该有一些429响应
assert 429 in status_codes
else:
# 如果没有速率限制,所有请求都应该成功
assert all(code == 200 for code in status_codes)
def test_search_api_cors_headers(self, client):
"""测试API CORS头"""
response = client.get("/search", params={"q": "CORS测试"})
assert response.status_code == 200
# 检查CORS头(如果配置了CORS)
# 这取决于实际的CORS配置
# response.headers.get("Access-Control-Allow-Origin")