Commit 72e7256ae49c8422d6008aed15677168cb705cc9

Authored by tangwang
1 parent f1505d1b

清理文件

scripts/generate_test_data.py deleted
@@ -1,421 +0,0 @@ @@ -1,421 +0,0 @@
1 -#!/usr/bin/env python3  
2 -"""  
3 -Generate test data for Shoplazza SPU and SKU tables.  
4 -  
5 -Generates 100 SPU records with 1-5 SKUs each.  
6 -"""  
7 -  
8 -import sys  
9 -import os  
10 -import random  
11 -import argparse  
12 -from pathlib import Path  
13 -from datetime import datetime, timedelta  
14 -  
15 -# Add parent directory to path  
16 -sys.path.insert(0, str(Path(__file__).parent.parent))  
17 -  
18 -  
19 -def generate_spu_data(num_spus: int = 100, tenant_id: str = "1", start_id: int = 1):  
20 - """  
21 - Generate SPU test data.  
22 -  
23 - Args:  
24 - num_spus: Number of SPUs to generate  
25 - tenant_id: Tenant ID  
26 - start_id: Starting ID for SPUs  
27 -  
28 - Returns:  
29 - List of SPU data dictionaries  
30 - """  
31 - categories = ["电子产品", "服装", "家居用品", "美妆", "食品", "运动用品", "图书", "玩具"]  
32 - vendors = ["Sony", "Nike", "Apple", "Samsung", "华为", "小米", "美的", "海尔"]  
33 -  
34 - products = [  
35 - ("蓝牙耳机", "Bluetooth Headphone", "高品质无线蓝牙耳机", "High-quality wireless Bluetooth headphone"),  
36 - ("运动鞋", "Running Shoes", "舒适透气的运动鞋", "Comfortable and breathable running shoes"),  
37 - ("智能手机", "Smartphone", "高性能智能手机", "High-performance smartphone"),  
38 - ("笔记本电脑", "Laptop", "轻薄便携笔记本电脑", "Lightweight and portable laptop"),  
39 - ("智能手表", "Smart Watch", "多功能智能手表", "Multi-function smart watch"),  
40 - ("平板电脑", "Tablet", "高清平板电脑", "High-definition tablet"),  
41 - ("无线鼠标", "Wireless Mouse", "人体工学无线鼠标", "Ergonomic wireless mouse"),  
42 - ("机械键盘", "Mechanical Keyboard", "RGB背光机械键盘", "RGB backlit mechanical keyboard"),  
43 - ("显示器", "Monitor", "4K高清显示器", "4K high-definition monitor"),  
44 - ("音响", "Speaker", "蓝牙无线音响", "Bluetooth wireless speaker"),  
45 - ]  
46 -  
47 - spus = []  
48 - for i in range(num_spus):  
49 - spu_id = start_id + i  
50 - product = random.choice(products)  
51 - category = random.choice(categories)  
52 - vendor = random.choice(vendors)  
53 -  
54 - # Generate handle  
55 - handle = f"product-{spu_id}"  
56 -  
57 - # Generate title (Chinese)  
58 - title_zh = f"{product[0]} {vendor}"  
59 -  
60 - # Generate brief  
61 - brief_zh = product[2]  
62 -  
63 - # Generate description  
64 - description_zh = f"<p>{product[2]},来自{vendor}品牌。{product[3]}</p>"  
65 -  
66 - # Generate SEO fields  
67 - seo_title = f"{title_zh} - {category}"  
68 - seo_description = f"购买{vendor}{product[0]},{product[2]}"  
69 - seo_keywords = f"{product[0]},{vendor},{category}"  
70 -  
71 - # Generate tags  
72 - tags = f"{category},{vendor},{product[0]}"  
73 -  
74 - # Generate image  
75 - image_src = f"//cdn.example.com/products/{spu_id}.jpg"  
76 -  
77 - # Generate dates  
78 - created_at = datetime.now() - timedelta(days=random.randint(1, 365))  
79 - updated_at = created_at + timedelta(days=random.randint(0, 30))  
80 -  
81 - spu = {  
82 - 'id': spu_id,  
83 - 'shop_id': 1,  
84 - 'shoplazza_id': f"spu-{spu_id}",  
85 - 'handle': handle,  
86 - 'title': title_zh,  
87 - 'brief': brief_zh,  
88 - 'description': description_zh,  
89 - 'spu': '',  
90 - 'vendor': vendor,  
91 - 'vendor_url': f"https://{vendor.lower()}.com",  
92 - 'seo_title': seo_title,  
93 - 'seo_description': seo_description,  
94 - 'seo_keywords': seo_keywords,  
95 - 'image_src': image_src,  
96 - 'image_width': 800,  
97 - 'image_height': 600,  
98 - 'image_path': f"products/{spu_id}.jpg",  
99 - 'image_alt': title_zh,  
100 - 'inventory_policy': '',  
101 - 'inventory_quantity': 0,  
102 - 'inventory_tracking': '0',  
103 - 'published': 1,  
104 - 'published_at': created_at.strftime('%Y-%m-%d %H:%M:%S'),  
105 - 'requires_shipping': 1,  
106 - 'taxable': 0,  
107 - 'fake_sales': 0,  
108 - 'display_fake_sales': 0,  
109 - 'mixed_wholesale': 0,  
110 - 'need_variant_image': 0,  
111 - 'has_only_default_variant': 0,  
112 - 'tags': tags,  
113 - 'note': '',  
114 - 'category': category,  
115 - 'shoplazza_created_at': created_at.strftime('%Y-%m-%d %H:%M:%S'),  
116 - 'shoplazza_updated_at': updated_at.strftime('%Y-%m-%d %H:%M:%S'),  
117 - 'tenant_id': tenant_id,  
118 - 'creator': '1',  
119 - 'create_time': created_at.strftime('%Y-%m-%d %H:%M:%S'),  
120 - 'updater': '1',  
121 - 'update_time': updated_at.strftime('%Y-%m-%d %H:%M:%S'),  
122 - 'deleted': 0  
123 - }  
124 - spus.append(spu)  
125 -  
126 - return spus  
127 -  
128 -  
129 -def generate_sku_data(spus: list, start_sku_id: int = 1):  
130 - """  
131 - Generate SKU test data for SPUs.  
132 -  
133 - Args:  
134 - spus: List of SPU data  
135 - start_sku_id: Starting ID for SKUs  
136 -  
137 - Returns:  
138 - List of SKU data dictionaries  
139 - """  
140 - colors = ["黑色", "白色", "红色", "蓝色", "绿色", "灰色"]  
141 - sizes = ["S", "M", "L", "XL", "XXL"]  
142 -  
143 - skus = []  
144 - sku_id = start_sku_id  
145 -  
146 - for spu in spus:  
147 - spu_id = spu['id']  
148 - num_skus = random.randint(1, 5)  
149 -  
150 - # Base price  
151 - base_price = random.uniform(50, 500)  
152 -  
153 - for i in range(num_skus):  
154 - # Generate variant options  
155 - color = random.choice(colors) if num_skus > 1 else None  
156 - size = random.choice(sizes) if num_skus > 2 else None  
157 -  
158 - # Generate title  
159 - title_parts = []  
160 - if color:  
161 - title_parts.append(color)  
162 - if size:  
163 - title_parts.append(size)  
164 - title = " / ".join(title_parts) if title_parts else ""  
165 -  
166 - # Generate SKU  
167 - sku_code = f"SKU-{spu_id}-{i+1}"  
168 -  
169 - # Generate price (variation from base)  
170 - price = base_price + random.uniform(-20, 50)  
171 - compare_at_price = price * random.uniform(1.2, 1.5)  
172 -  
173 - # Generate stock  
174 - stock = random.randint(0, 100)  
175 -  
176 - # Generate dates  
177 - created_at = datetime.now() - timedelta(days=random.randint(1, 365))  
178 - updated_at = created_at + timedelta(days=random.randint(0, 30))  
179 -  
180 - sku = {  
181 - 'id': sku_id,  
182 - 'spu_id': spu_id,  
183 - 'shop_id': 1,  
184 - 'shoplazza_id': f"sku-{sku_id}",  
185 - 'shoplazza_product_id': spu['shoplazza_id'],  
186 - 'shoplazza_image_id': '',  
187 - 'title': title,  
188 - 'sku': sku_code,  
189 - 'barcode': f"BAR{sku_id:08d}",  
190 - 'position': i + 1,  
191 - 'price': round(price, 2),  
192 - 'compare_at_price': round(compare_at_price, 2),  
193 - 'cost_price': round(price * 0.6, 2),  
194 - 'option1': color if color else '',  
195 - 'option2': size if size else '',  
196 - 'option3': '',  
197 - 'inventory_quantity': stock,  
198 - 'weight': round(random.uniform(0.1, 5.0), 2),  
199 - 'weight_unit': 'kg',  
200 - 'image_src': '',  
201 - 'wholesale_price': '[{"price": ' + str(round(price * 0.8, 2)) + ', "minQuantity": 10}]',  
202 - 'note': '',  
203 - 'extend': None, # JSON field, use NULL instead of empty string  
204 - 'shoplazza_created_at': created_at.strftime('%Y-%m-%d %H:%M:%S'),  
205 - 'shoplazza_updated_at': updated_at.strftime('%Y-%m-%d %H:%M:%S'),  
206 - 'tenant_id': spu['tenant_id'],  
207 - 'creator': '1',  
208 - 'create_time': created_at.strftime('%Y-%m-%d %H:%M:%S'),  
209 - 'updater': '1',  
210 - 'update_time': updated_at.strftime('%Y-%m-%d %H:%M:%S'),  
211 - 'deleted': 0  
212 - }  
213 - skus.append(sku)  
214 - sku_id += 1  
215 -  
216 - return skus  
217 -  
218 -  
219 -def escape_sql_string(value: str) -> str:  
220 - """  
221 - Escape SQL string value (replace single quotes with doubled quotes).  
222 -  
223 - Args:  
224 - value: String value to escape  
225 -  
226 - Returns:  
227 - Escaped string  
228 - """  
229 - if value is None:  
230 - return ''  
231 - return str(value).replace("'", "''").replace("\\", "\\\\")  
232 -  
233 -  
234 -def generate_sql_inserts(spus: list, skus: list, output_file: str):  
235 - """  
236 - Generate SQL INSERT statements.  
237 -  
238 - Args:  
239 - spus: List of SPU data  
240 - skus: List of SKU data  
241 - output_file: Output file path  
242 - """  
243 - with open(output_file, 'w', encoding='utf-8') as f:  
244 - f.write("-- SPU Test Data\n")  
245 - f.write("INSERT INTO shoplazza_product_spu (\n")  
246 - f.write(" id, shop_id, shoplazza_id, handle, title, brief, description, spu,\n")  
247 - f.write(" vendor, vendor_url, seo_title, seo_description, seo_keywords,\n")  
248 - f.write(" image_src, image_width, image_height, image_path, image_alt,\n")  
249 - f.write(" inventory_policy, inventory_quantity, inventory_tracking,\n")  
250 - f.write(" published, published_at, requires_shipping, taxable,\n")  
251 - f.write(" fake_sales, display_fake_sales, mixed_wholesale, need_variant_image,\n")  
252 - f.write(" has_only_default_variant, tags, note, category,\n")  
253 - f.write(" shoplazza_created_at, shoplazza_updated_at, tenant_id,\n")  
254 - f.write(" creator, create_time, updater, update_time, deleted\n")  
255 - f.write(") VALUES\n")  
256 -  
257 - for i, spu in enumerate(spus):  
258 - values = (  
259 - f"({spu['id']}, {spu['shop_id']}, '{escape_sql_string(spu['shoplazza_id'])}', "  
260 - f"'{escape_sql_string(spu['handle'])}', '{escape_sql_string(spu['title'])}', "  
261 - f"'{escape_sql_string(spu['brief'])}', '{escape_sql_string(spu['description'])}', "  
262 - f"'{escape_sql_string(spu['spu'])}', '{escape_sql_string(spu['vendor'])}', "  
263 - f"'{escape_sql_string(spu['vendor_url'])}', '{escape_sql_string(spu['seo_title'])}', "  
264 - f"'{escape_sql_string(spu['seo_description'])}', '{escape_sql_string(spu['seo_keywords'])}', "  
265 - f"'{escape_sql_string(spu['image_src'])}', {spu['image_width']}, "  
266 - f"{spu['image_height']}, '{escape_sql_string(spu['image_path'])}', "  
267 - f"'{escape_sql_string(spu['image_alt'])}', '{escape_sql_string(spu['inventory_policy'])}', "  
268 - f"{spu['inventory_quantity']}, '{escape_sql_string(spu['inventory_tracking'])}', "  
269 - f"{spu['published']}, '{escape_sql_string(spu['published_at'])}', "  
270 - f"{spu['requires_shipping']}, {spu['taxable']}, "  
271 - f"{spu['fake_sales']}, {spu['display_fake_sales']}, {spu['mixed_wholesale']}, "  
272 - f"{spu['need_variant_image']}, {spu['has_only_default_variant']}, "  
273 - f"'{escape_sql_string(spu['tags'])}', '{escape_sql_string(spu['note'])}', "  
274 - f"'{escape_sql_string(spu['category'])}', '{escape_sql_string(spu['shoplazza_created_at'])}', "  
275 - f"'{escape_sql_string(spu['shoplazza_updated_at'])}', '{escape_sql_string(spu['tenant_id'])}', "  
276 - f"'{escape_sql_string(spu['creator'])}', '{escape_sql_string(spu['create_time'])}', "  
277 - f"'{escape_sql_string(spu['updater'])}', '{escape_sql_string(spu['update_time'])}', "  
278 - f"{spu['deleted']})"  
279 - )  
280 - f.write(values)  
281 - if i < len(spus) - 1:  
282 - f.write(",\n")  
283 - else:  
284 - f.write(";\n\n")  
285 -  
286 - f.write("-- SKU Test Data\n")  
287 - f.write("INSERT INTO shoplazza_product_sku (\n")  
288 - f.write(" id, spu_id, shop_id, shoplazza_id, shoplazza_product_id, shoplazza_image_id,\n")  
289 - f.write(" title, sku, barcode, position, price, compare_at_price, cost_price,\n")  
290 - f.write(" option1, option2, option3, inventory_quantity, weight, weight_unit,\n")  
291 - f.write(" image_src, wholesale_price, note, extend,\n")  
292 - f.write(" shoplazza_created_at, shoplazza_updated_at, tenant_id,\n")  
293 - f.write(" creator, create_time, updater, update_time, deleted\n")  
294 - f.write(") VALUES\n")  
295 -  
296 - for i, sku in enumerate(skus):  
297 - # Handle extend field (JSON, can be NULL)  
298 - extend_value = 'NULL' if sku['extend'] is None else f"'{escape_sql_string(sku['extend'])}'"  
299 -  
300 - values = (  
301 - f"({sku['id']}, {sku['spu_id']}, {sku['shop_id']}, '{escape_sql_string(sku['shoplazza_id'])}', "  
302 - f"'{escape_sql_string(sku['shoplazza_product_id'])}', '{escape_sql_string(sku['shoplazza_image_id'])}', "  
303 - f"'{escape_sql_string(sku['title'])}', '{escape_sql_string(sku['sku'])}', "  
304 - f"'{escape_sql_string(sku['barcode'])}', {sku['position']}, "  
305 - f"{sku['price']}, {sku['compare_at_price']}, {sku['cost_price']}, "  
306 - f"'{escape_sql_string(sku['option1'])}', '{escape_sql_string(sku['option2'])}', "  
307 - f"'{escape_sql_string(sku['option3'])}', {sku['inventory_quantity']}, {sku['weight']}, "  
308 - f"'{escape_sql_string(sku['weight_unit'])}', '{escape_sql_string(sku['image_src'])}', "  
309 - f"'{escape_sql_string(sku['wholesale_price'])}', '{escape_sql_string(sku['note'])}', "  
310 - f"{extend_value}, '{escape_sql_string(sku['shoplazza_created_at'])}', "  
311 - f"'{escape_sql_string(sku['shoplazza_updated_at'])}', '{escape_sql_string(sku['tenant_id'])}', "  
312 - f"'{escape_sql_string(sku['creator'])}', '{escape_sql_string(sku['create_time'])}', "  
313 - f"'{escape_sql_string(sku['updater'])}', '{escape_sql_string(sku['update_time'])}', "  
314 - f"{sku['deleted']})"  
315 - )  
316 - f.write(values)  
317 - if i < len(skus) - 1:  
318 - f.write(",\n")  
319 - else:  
320 - f.write(";\n")  
321 -  
322 -  
323 -def get_max_ids_from_db(db_config=None):  
324 - """  
325 - Get maximum IDs from database to avoid primary key conflicts.  
326 -  
327 - Args:  
328 - db_config: Optional database config dict with keys: host, port, database, username, password  
329 -  
330 - Returns:  
331 - tuple: (max_spu_id, max_sku_id) or (0, 0) if cannot connect  
332 - """  
333 - if not db_config:  
334 - return 0, 0  
335 -  
336 - try:  
337 - from utils.db_connector import create_db_connection  
338 - from sqlalchemy import text  
339 -  
340 - db_engine = create_db_connection(  
341 - host=db_config['host'],  
342 - port=db_config['port'],  
343 - database=db_config['database'],  
344 - username=db_config['username'],  
345 - password=db_config['password']  
346 - )  
347 -  
348 - with db_engine.connect() as conn:  
349 - result = conn.execute(text('SELECT MAX(id) FROM shoplazza_product_spu'))  
350 - max_spu_id = result.scalar() or 0  
351 -  
352 - result = conn.execute(text('SELECT MAX(id) FROM shoplazza_product_sku'))  
353 - max_sku_id = result.scalar() or 0  
354 -  
355 - return max_spu_id, max_sku_id  
356 - except Exception as e:  
357 - print(f"Warning: Could not get max IDs from database: {e}")  
358 - return 0, 0  
359 -  
360 -  
361 -def main():  
362 - parser = argparse.ArgumentParser(description='Generate test data for Shoplazza tables')  
363 - parser.add_argument('--num-spus', type=int, default=100, help='Number of SPUs to generate')  
364 - parser.add_argument('--tenant-id', default='1', help='Tenant ID')  
365 - parser.add_argument('--start-spu-id', type=int, default=None, help='Starting SPU ID (default: auto-calculate from DB)')  
366 - parser.add_argument('--start-sku-id', type=int, default=None, help='Starting SKU ID (default: auto-calculate from DB)')  
367 - parser.add_argument('--output', default='test_data.sql', help='Output SQL file')  
368 - parser.add_argument('--db-host', help='Database host (for auto-calculating start IDs)')  
369 - parser.add_argument('--db-port', type=int, default=3306, help='Database port (default: 3306)')  
370 - parser.add_argument('--db-database', help='Database name (for auto-calculating start IDs)')  
371 - parser.add_argument('--db-username', help='Database username (for auto-calculating start IDs)')  
372 - parser.add_argument('--db-password', help='Database password (for auto-calculating start IDs)')  
373 -  
374 - args = parser.parse_args()  
375 -  
376 - # Auto-calculate start IDs if not provided and DB config available  
377 - start_spu_id = args.start_spu_id  
378 - start_sku_id = args.start_sku_id  
379 -  
380 - if (start_spu_id is None or start_sku_id is None) and args.db_host and args.db_database and args.db_username and args.db_password:  
381 - print("Auto-calculating start IDs from database...")  
382 - db_config = {  
383 - 'host': args.db_host,  
384 - 'port': args.db_port,  
385 - 'database': args.db_database,  
386 - 'username': args.db_username,  
387 - 'password': args.db_password  
388 - }  
389 - max_spu_id, max_sku_id = get_max_ids_from_db(db_config)  
390 - if start_spu_id is None:  
391 - start_spu_id = max_spu_id + 1  
392 - if start_sku_id is None:  
393 - start_sku_id = max_sku_id + 1  
394 - print(f" Max SPU ID in DB: {max_spu_id}, using start SPU ID: {start_spu_id}")  
395 - print(f" Max SKU ID in DB: {max_sku_id}, using start SKU ID: {start_sku_id}")  
396 - else:  
397 - if start_spu_id is None:  
398 - start_spu_id = 1  
399 - if start_sku_id is None:  
400 - start_sku_id = 1  
401 - print(f"Using start SPU ID: {start_spu_id}, start SKU ID: {start_sku_id}")  
402 -  
403 - print(f"Generating {args.num_spus} SPUs with skus...")  
404 -  
405 - # Generate SPU data  
406 - spus = generate_spu_data(args.num_spus, args.tenant_id, start_spu_id)  
407 - print(f"Generated {len(spus)} SPUs")  
408 -  
409 - # Generate SKU data  
410 - skus = generate_sku_data(spus, start_sku_id)  
411 - print(f"Generated {len(skus)} SKUs")  
412 -  
413 - # Generate SQL file  
414 - generate_sql_inserts(spus, skus, args.output)  
415 - print(f"SQL file generated: {args.output}")  
416 -  
417 -  
418 -if __name__ == '__main__':  
419 - import json  
420 - main()  
421 -  
scripts/generate_test_summary.py deleted
@@ -1,179 +0,0 @@ @@ -1,179 +0,0 @@
1 -#!/usr/bin/env python3  
2 -"""  
3 -生成测试摘要脚本  
4 -  
5 -用于CI/CD流水线中汇总所有测试结果  
6 -"""  
7 -  
8 -import json  
9 -import os  
10 -import sys  
11 -import glob  
12 -from pathlib import Path  
13 -from datetime import datetime  
14 -from typing import Dict, Any, List  
15 -  
16 -  
17 -def collect_test_results() -> Dict[str, Any]:  
18 - """收集所有测试结果"""  
19 - results = {  
20 - 'timestamp': datetime.now().isoformat(),  
21 - 'suites': {},  
22 - 'summary': {  
23 - 'total_tests': 0,  
24 - 'passed': 0,  
25 - 'failed': 0,  
26 - 'skipped': 0,  
27 - 'errors': 0,  
28 - 'total_duration': 0.0  
29 - }  
30 - }  
31 -  
32 - # 查找所有测试结果文件  
33 - test_files = glob.glob('*_test_results.json')  
34 -  
35 - for test_file in test_files:  
36 - try:  
37 - with open(test_file, 'r', encoding='utf-8') as f:  
38 - test_data = json.load(f)  
39 -  
40 - suite_name = test_file.replace('_test_results.json', '')  
41 -  
42 - if 'summary' in test_data:  
43 - summary = test_data['summary']  
44 - results['suites'][suite_name] = {  
45 - 'total': summary.get('total', 0),  
46 - 'passed': summary.get('passed', 0),  
47 - 'failed': summary.get('failed', 0),  
48 - 'skipped': summary.get('skipped', 0),  
49 - 'errors': summary.get('error', 0),  
50 - 'duration': summary.get('duration', 0.0)  
51 - }  
52 -  
53 - # 更新总体统计  
54 - results['summary']['total_tests'] += summary.get('total', 0)  
55 - results['summary']['passed'] += summary.get('passed', 0)  
56 - results['summary']['failed'] += summary.get('failed', 0)  
57 - results['summary']['skipped'] += summary.get('skipped', 0)  
58 - results['summary']['errors'] += summary.get('error', 0)  
59 - results['summary']['total_duration'] += summary.get('duration', 0.0)  
60 -  
61 - except Exception as e:  
62 - print(f"Error reading {test_file}: {e}")  
63 - continue  
64 -  
65 - # 计算成功率  
66 - if results['summary']['total_tests'] > 0:  
67 - results['summary']['success_rate'] = (  
68 - results['summary']['passed'] / results['summary']['total_tests'] * 100  
69 - )  
70 - else:  
71 - results['summary']['success_rate'] = 0.0  
72 -  
73 - return results  
74 -  
75 -  
76 -def generate_text_report(results: Dict[str, Any]) -> str:  
77 - """生成文本格式的测试报告"""  
78 - lines = []  
79 -  
80 - # 标题  
81 - lines.append("=" * 60)  
82 - lines.append("搜索引擎自动化测试报告")  
83 - lines.append("=" * 60)  
84 - lines.append(f"时间: {results['timestamp']}")  
85 - lines.append("")  
86 -  
87 - # 摘要  
88 - summary = results['summary']  
89 - lines.append("📊 测试摘要")  
90 - lines.append("-" * 30)  
91 - lines.append(f"总测试数: {summary['total_tests']}")  
92 - lines.append(f"✅ 通过: {summary['passed']}")  
93 - lines.append(f"❌ 失败: {summary['failed']}")  
94 - lines.append(f"⏭️ 跳过: {summary['skipped']}")  
95 - lines.append(f"🚨 错误: {summary['errors']}")  
96 - lines.append(f"📈 成功率: {summary['success_rate']:.1f}%")  
97 - lines.append(f"⏱️ 总耗时: {summary['total_duration']:.2f}秒")  
98 - lines.append("")  
99 -  
100 - # 状态判断  
101 - if summary['failed'] == 0 and summary['errors'] == 0:  
102 - lines.append("🎉 所有测试都通过了!")  
103 - else:  
104 - lines.append("⚠️ 存在失败的测试,请查看详细日志。")  
105 - lines.append("")  
106 -  
107 - # 各测试套件详情  
108 - if results['suites']:  
109 - lines.append("📋 测试套件详情")  
110 - lines.append("-" * 30)  
111 -  
112 - for suite_name, suite_data in results['suites'].items():  
113 - lines.append(f"\n{suite_name.upper()}:")  
114 - lines.append(f" 总数: {suite_data['total']}")  
115 - lines.append(f" ✅ 通过: {suite_data['passed']}")  
116 - lines.append(f" ❌ 失败: {suite_data['failed']}")  
117 - lines.append(f" ⏭️ 跳过: {suite_data['skipped']}")  
118 - lines.append(f" 🚨 错误: {suite_data['errors']}")  
119 - lines.append(f" ⏱️ 耗时: {suite_data['duration']:.2f}秒")  
120 -  
121 - # 添加状态图标  
122 - if suite_data['failed'] == 0 and suite_data['errors'] == 0:  
123 - lines.append(f" 状态: ✅ 全部通过")  
124 - else:  
125 - lines.append(f" 状态: ❌ 存在问题")  
126 -  
127 - lines.append("")  
128 - lines.append("=" * 60)  
129 -  
130 - return "\n".join(lines)  
131 -  
132 -  
133 -def generate_json_report(results: Dict[str, Any]) -> str:  
134 - """生成JSON格式的测试报告"""  
135 - return json.dumps(results, indent=2, ensure_ascii=False)  
136 -  
137 -  
138 -def main():  
139 - """主函数"""  
140 - # 收集测试结果  
141 - print("收集测试结果...")  
142 - results = collect_test_results()  
143 -  
144 - # 生成报告  
145 - print("生成测试报告...")  
146 - text_report = generate_text_report(results)  
147 - json_report = generate_json_report(results)  
148 -  
149 - # 保存报告  
150 - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")  
151 -  
152 - # 文本报告  
153 - text_file = f"final_test_report.txt"  
154 - with open(text_file, 'w', encoding='utf-8') as f:  
155 - f.write(text_report)  
156 -  
157 - # JSON报告  
158 - json_file = f"final_test_report.json"  
159 - with open(json_file, 'w', encoding='utf-8') as f:  
160 - f.write(json_report)  
161 -  
162 - print(f"测试报告已生成:")  
163 - print(f" 文本报告: {text_file}")  
164 - print(f" JSON报告: {json_file}")  
165 -  
166 - # 输出摘要到控制台  
167 - print("\n" + "=" * 60)  
168 - print(text_report)  
169 -  
170 - # 返回退出码  
171 - summary = results['summary']  
172 - if summary['failed'] > 0 or summary['errors'] > 0:  
173 - return 1  
174 - else:  
175 - return 0  
176 -  
177 -  
178 -if __name__ == "__main__":  
179 - sys.exit(main())  
180 \ No newline at end of file 0 \ No newline at end of file
scripts/import_tenant2_csv.py renamed to scripts/indexer__old_2025_11/import_tenant2_csv.py
scripts/import_test_data.py renamed to scripts/indexer__old_2025_11/import_test_data.py
scripts/ingest.sh renamed to scripts/indexer__old_2025_11/ingest.sh
scripts/ingest_shoplazza.py renamed to scripts/indexer__old_2025_11/ingest_shoplazza.py
scripts/recreate_and_import.py renamed to scripts/indexer__old_2025_11/recreate_and_import.py
scripts/run_tests.py deleted
@@ -1,705 +0,0 @@ @@ -1,705 +0,0 @@
1 -#!/usr/bin/env python3  
2 -"""  
3 -测试执行脚本  
4 -  
5 -运行完整的测试流水线,包括:  
6 -- 环境检查  
7 -- 单元测试  
8 -- 集成测试  
9 -- 性能测试  
10 -- 测试报告生成  
11 -"""  
12 -  
13 -import os  
14 -import sys  
15 -import subprocess  
16 -import time  
17 -import json  
18 -import argparse  
19 -import logging  
20 -from pathlib import Path  
21 -from typing import Dict, List, Optional, Any  
22 -from dataclasses import dataclass, asdict  
23 -from datetime import datetime  
24 -  
25 -  
26 -# 添加项目根目录到Python路径  
27 -project_root = Path(__file__).parent.parent  
28 -sys.path.insert(0, str(project_root))  
29 -  
30 -  
31 -@dataclass  
32 -class TestResult:  
33 - """测试结果数据结构"""  
34 - name: str  
35 - status: str # "passed", "failed", "skipped", "error"  
36 - duration: float  
37 - details: Optional[Dict[str, Any]] = None  
38 - output: Optional[str] = None  
39 - error: Optional[str] = None  
40 -  
41 -  
42 -@dataclass  
43 -class TestSuiteResult:  
44 - """测试套件结果"""  
45 - name: str  
46 - total_tests: int  
47 - passed: int  
48 - failed: int  
49 - skipped: int  
50 - errors: int  
51 - duration: float  
52 - results: List[TestResult]  
53 -  
54 -  
55 -class TestRunner:  
56 - """测试运行器"""  
57 -  
58 - def __init__(self, config: Dict[str, Any]):  
59 - self.config = config  
60 - self.logger = self._setup_logger()  
61 - self.results: List[TestSuiteResult] = []  
62 - self.start_time = time.time()  
63 -  
64 - def _setup_logger(self) -> logging.Logger:  
65 - """设置日志记录器"""  
66 - log_level = getattr(logging, self.config.get('log_level', 'INFO').upper())  
67 - logging.basicConfig(  
68 - level=log_level,  
69 - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',  
70 - handlers=[  
71 - logging.StreamHandler(),  
72 - logging.FileHandler(  
73 - project_root / 'test_logs' / f'test_run_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'  
74 - )  
75 - ]  
76 - )  
77 - return logging.getLogger(__name__)  
78 -  
79 - def _run_command(self, cmd: List[str], cwd: Optional[Path] = None, env: Optional[Dict[str, str]] = None) -> subprocess.CompletedProcess:  
80 - """运行命令"""  
81 - try:  
82 - self.logger.info(f"执行命令: {' '.join(cmd)}")  
83 -  
84 - # 设置环境变量  
85 - process_env = os.environ.copy()  
86 - if env:  
87 - process_env.update(env)  
88 -  
89 - result = subprocess.run(  
90 - cmd,  
91 - cwd=cwd or project_root,  
92 - env=process_env,  
93 - capture_output=True,  
94 - text=True,  
95 - timeout=self.config.get('test_timeout', 300)  
96 - )  
97 -  
98 - self.logger.debug(f"命令返回码: {result.returncode}")  
99 - if result.stdout:  
100 - self.logger.debug(f"标准输出: {result.stdout[:500]}...")  
101 - if result.stderr:  
102 - self.logger.debug(f"标准错误: {result.stderr[:500]}...")  
103 -  
104 - return result  
105 -  
106 - except subprocess.TimeoutExpired:  
107 - self.logger.error(f"命令执行超时: {' '.join(cmd)}")  
108 - raise  
109 - except Exception as e:  
110 - self.logger.error(f"命令执行失败: {e}")  
111 - raise  
112 -  
113 - def check_environment(self) -> bool:  
114 - """检查测试环境"""  
115 - self.logger.info("检查测试环境...")  
116 -  
117 - checks = []  
118 -  
119 - # 检查Python环境  
120 - try:  
121 - python_version = sys.version  
122 - self.logger.info(f"Python版本: {python_version}")  
123 - checks.append(("Python", True, f"版本 {python_version}"))  
124 - except Exception as e:  
125 - checks.append(("Python", False, str(e)))  
126 -  
127 - # 检查conda环境  
128 - try:  
129 - result = self._run_command(['conda', '--version'])  
130 - if result.returncode == 0:  
131 - conda_version = result.stdout.strip()  
132 - self.logger.info(f"Conda版本: {conda_version}")  
133 - checks.append(("Conda", True, conda_version))  
134 - else:  
135 - checks.append(("Conda", False, "未找到conda"))  
136 - except Exception as e:  
137 - checks.append(("Conda", False, str(e)))  
138 -  
139 - # 检查依赖包  
140 - required_packages = [  
141 - 'pytest', 'fastapi', 'elasticsearch', 'numpy',  
142 - 'torch', 'transformers', 'pyyaml'  
143 - ]  
144 -  
145 - for package in required_packages:  
146 - try:  
147 - result = self._run_command(['python', '-c', f'import {package}'])  
148 - if result.returncode == 0:  
149 - checks.append((package, True, "已安装"))  
150 - else:  
151 - checks.append((package, False, "导入失败"))  
152 - except Exception as e:  
153 - checks.append((package, False, str(e)))  
154 -  
155 - # 检查Elasticsearch  
156 - try:  
157 - es_host = os.getenv('ES_HOST', 'http://localhost:9200')  
158 - result = self._run_command(['curl', '-s', f'{es_host}/_cluster/health'])  
159 - if result.returncode == 0:  
160 - health_data = json.loads(result.stdout)  
161 - status = health_data.get('status', 'unknown')  
162 - self.logger.info(f"Elasticsearch状态: {status}")  
163 - checks.append(("Elasticsearch", True, f"状态: {status}"))  
164 - else:  
165 - checks.append(("Elasticsearch", False, "连接失败"))  
166 - except Exception as e:  
167 - checks.append(("Elasticsearch", False, str(e)))  
168 -  
169 - # 检查API服务  
170 - try:  
171 - api_host = os.getenv('API_HOST', '127.0.0.1')  
172 - api_port = os.getenv('API_PORT', '6003')  
173 - result = self._run_command(['curl', '-s', f'http://{api_host}:{api_port}/health'])  
174 - if result.returncode == 0:  
175 - health_data = json.loads(result.stdout)  
176 - status = health_data.get('status', 'unknown')  
177 - self.logger.info(f"API服务状态: {status}")  
178 - checks.append(("API服务", True, f"状态: {status}"))  
179 - else:  
180 - checks.append(("API服务", False, "连接失败"))  
181 - except Exception as e:  
182 - checks.append(("API服务", False, str(e)))  
183 -  
184 - # 输出检查结果  
185 - self.logger.info("环境检查结果:")  
186 - all_passed = True  
187 - for name, passed, details in checks:  
188 - status = "✓" if passed else "✗"  
189 - self.logger.info(f" {status} {name}: {details}")  
190 - if not passed:  
191 - all_passed = False  
192 -  
193 - return all_passed  
194 -  
195 - def run_unit_tests(self) -> TestSuiteResult:  
196 - """运行单元测试"""  
197 - self.logger.info("运行单元测试...")  
198 -  
199 - start_time = time.time()  
200 - cmd = [  
201 - 'python', '-m', 'pytest',  
202 - 'tests/unit/',  
203 - '-v',  
204 - '--tb=short',  
205 - '--json-report',  
206 - '--json-report-file=test_logs/unit_test_results.json'  
207 - ]  
208 -  
209 - try:  
210 - result = self._run_command(cmd)  
211 - duration = time.time() - start_time  
212 -  
213 - # 解析测试结果  
214 - if result.returncode == 0:  
215 - status = "passed"  
216 - else:  
217 - status = "failed"  
218 -  
219 - # 尝试解析JSON报告  
220 - test_results = []  
221 - passed = failed = skipped = errors = 0  
222 -  
223 - try:  
224 - with open(project_root / 'test_logs' / 'unit_test_results.json', 'r') as f:  
225 - report_data = json.load(f)  
226 -  
227 - summary = report_data.get('summary', {})  
228 - total = summary.get('total', 0)  
229 - passed = summary.get('passed', 0)  
230 - failed = summary.get('failed', 0)  
231 - skipped = summary.get('skipped', 0)  
232 - errors = summary.get('error', 0)  
233 -  
234 - # 获取详细结果  
235 - for test in report_data.get('tests', []):  
236 - test_results.append(TestResult(  
237 - name=test.get('nodeid', ''),  
238 - status=test.get('outcome', 'unknown'),  
239 - duration=test.get('duration', 0.0),  
240 - details=test  
241 - ))  
242 -  
243 - except Exception as e:  
244 - self.logger.warning(f"无法解析单元测试JSON报告: {e}")  
245 -  
246 - suite_result = TestSuiteResult(  
247 - name="单元测试",  
248 - total_tests=passed + failed + skipped + errors,  
249 - passed=passed,  
250 - failed=failed,  
251 - skipped=skipped,  
252 - errors=errors,  
253 - duration=duration,  
254 - results=test_results  
255 - )  
256 -  
257 - self.results.append(suite_result)  
258 - self.logger.info(f"单元测试完成: {suite_result.total_tests}个测试, "  
259 - f"{suite_result.passed}通过, {suite_result.failed}失败, "  
260 - f"{suite_result.skipped}跳过, {suite_result.errors}错误")  
261 -  
262 - return suite_result  
263 -  
264 - except Exception as e:  
265 - self.logger.error(f"单元测试执行失败: {e}")  
266 - raise  
267 -  
268 - def run_integration_tests(self) -> TestSuiteResult:  
269 - """运行集成测试"""  
270 - self.logger.info("运行集成测试...")  
271 -  
272 - start_time = time.time()  
273 - cmd = [  
274 - 'python', '-m', 'pytest',  
275 - 'tests/integration/',  
276 - '-v',  
277 - '--tb=short',  
278 - '-m', 'not slow', # 排除慢速测试  
279 - '--json-report',  
280 - '--json-report-file=test_logs/integration_test_results.json'  
281 - ]  
282 -  
283 - try:  
284 - result = self._run_command(cmd)  
285 - duration = time.time() - start_time  
286 -  
287 - # 解析测试结果  
288 - if result.returncode == 0:  
289 - status = "passed"  
290 - else:  
291 - status = "failed"  
292 -  
293 - # 尝试解析JSON报告  
294 - test_results = []  
295 - passed = failed = skipped = errors = 0  
296 -  
297 - try:  
298 - with open(project_root / 'test_logs' / 'integration_test_results.json', 'r') as f:  
299 - report_data = json.load(f)  
300 -  
301 - summary = report_data.get('summary', {})  
302 - total = summary.get('total', 0)  
303 - passed = summary.get('passed', 0)  
304 - failed = summary.get('failed', 0)  
305 - skipped = summary.get('skipped', 0)  
306 - errors = summary.get('error', 0)  
307 -  
308 - for test in report_data.get('tests', []):  
309 - test_results.append(TestResult(  
310 - name=test.get('nodeid', ''),  
311 - status=test.get('outcome', 'unknown'),  
312 - duration=test.get('duration', 0.0),  
313 - details=test  
314 - ))  
315 -  
316 - except Exception as e:  
317 - self.logger.warning(f"无法解析集成测试JSON报告: {e}")  
318 -  
319 - suite_result = TestSuiteResult(  
320 - name="集成测试",  
321 - total_tests=passed + failed + skipped + errors,  
322 - passed=passed,  
323 - failed=failed,  
324 - skipped=skipped,  
325 - errors=errors,  
326 - duration=duration,  
327 - results=test_results  
328 - )  
329 -  
330 - self.results.append(suite_result)  
331 - self.logger.info(f"集成测试完成: {suite_result.total_tests}个测试, "  
332 - f"{suite_result.passed}通过, {suite_result.failed}失败, "  
333 - f"{suite_result.skipped}跳过, {suite_result.errors}错误")  
334 -  
335 - return suite_result  
336 -  
337 - except Exception as e:  
338 - self.logger.error(f"集成测试执行失败: {e}")  
339 - raise  
340 -  
341 - def run_api_tests(self) -> TestSuiteResult:  
342 - """运行API测试"""  
343 - self.logger.info("运行API测试...")  
344 -  
345 - start_time = time.time()  
346 - cmd = [  
347 - 'python', '-m', 'pytest',  
348 - 'tests/integration/test_api_integration.py',  
349 - '-v',  
350 - '--tb=short',  
351 - '--json-report',  
352 - '--json-report-file=test_logs/api_test_results.json'  
353 - ]  
354 -  
355 - try:  
356 - result = self._run_command(cmd)  
357 - duration = time.time() - start_time  
358 -  
359 - # 解析测试结果  
360 - if result.returncode == 0:  
361 - status = "passed"  
362 - else:  
363 - status = "failed"  
364 -  
365 - # 尝试解析JSON报告  
366 - test_results = []  
367 - passed = failed = skipped = errors = 0  
368 -  
369 - try:  
370 - with open(project_root / 'test_logs' / 'api_test_results.json', 'r') as f:  
371 - report_data = json.load(f)  
372 -  
373 - summary = report_data.get('summary', {})  
374 - total = summary.get('total', 0)  
375 - passed = summary.get('passed', 0)  
376 - failed = summary.get('failed', 0)  
377 - skipped = summary.get('skipped', 0)  
378 - errors = summary.get('error', 0)  
379 -  
380 - for test in report_data.get('tests', []):  
381 - test_results.append(TestResult(  
382 - name=test.get('nodeid', ''),  
383 - status=test.get('outcome', 'unknown'),  
384 - duration=test.get('duration', 0.0),  
385 - details=test  
386 - ))  
387 -  
388 - except Exception as e:  
389 - self.logger.warning(f"无法解析API测试JSON报告: {e}")  
390 -  
391 - suite_result = TestSuiteResult(  
392 - name="API测试",  
393 - total_tests=passed + failed + skipped + errors,  
394 - passed=passed,  
395 - failed=failed,  
396 - skipped=skipped,  
397 - errors=errors,  
398 - duration=duration,  
399 - results=test_results  
400 - )  
401 -  
402 - self.results.append(suite_result)  
403 - self.logger.info(f"API测试完成: {suite_result.total_tests}个测试, "  
404 - f"{suite_result.passed}通过, {suite_result.failed}失败, "  
405 - f"{suite_result.skipped}跳过, {suite_result.errors}错误")  
406 -  
407 - return suite_result  
408 -  
409 - except Exception as e:  
410 - self.logger.error(f"API测试执行失败: {e}")  
411 - raise  
412 -  
413 - def run_performance_tests(self) -> TestSuiteResult:  
414 - """运行性能测试"""  
415 - self.logger.info("运行性能测试...")  
416 -  
417 - start_time = time.time()  
418 -  
419 - # 简单的性能测试 - 测试搜索响应时间  
420 - test_queries = [  
421 - "红色连衣裙",  
422 - "智能手机",  
423 - "笔记本电脑 AND (游戏 OR 办公)",  
424 - "无线蓝牙耳机"  
425 - ]  
426 -  
427 - test_results = []  
428 - passed = failed = 0  
429 -  
430 - for query in test_queries:  
431 - try:  
432 - query_start = time.time()  
433 - result = self._run_command([  
434 - 'curl', '-s',  
435 - f'http://{os.getenv("API_HOST", "127.0.0.1")}:{os.getenv("API_PORT", "6003")}/search',  
436 - '-d', f'q={query}'  
437 - ])  
438 - query_duration = time.time() - query_start  
439 -  
440 - if result.returncode == 0:  
441 - response_data = json.loads(result.stdout)  
442 - took_ms = response_data.get('took_ms', 0)  
443 -  
444 - # 性能阈值:响应时间不超过2秒  
445 - if took_ms <= 2000:  
446 - test_results.append(TestResult(  
447 - name=f"搜索性能测试: {query}",  
448 - status="passed",  
449 - duration=query_duration,  
450 - details={"took_ms": took_ms, "response_size": len(result.stdout)}  
451 - ))  
452 - passed += 1  
453 - else:  
454 - test_results.append(TestResult(  
455 - name=f"搜索性能测试: {query}",  
456 - status="failed",  
457 - duration=query_duration,  
458 - details={"took_ms": took_ms, "threshold": 2000}  
459 - ))  
460 - failed += 1  
461 - else:  
462 - test_results.append(TestResult(  
463 - name=f"搜索性能测试: {query}",  
464 - status="failed",  
465 - duration=query_duration,  
466 - error=result.stderr  
467 - ))  
468 - failed += 1  
469 -  
470 - except Exception as e:  
471 - test_results.append(TestResult(  
472 - name=f"搜索性能测试: {query}",  
473 - status="error",  
474 - duration=0.0,  
475 - error=str(e)  
476 - ))  
477 - failed += 1  
478 -  
479 - duration = time.time() - start_time  
480 -  
481 - suite_result = TestSuiteResult(  
482 - name="性能测试",  
483 - total_tests=len(test_results),  
484 - passed=passed,  
485 - failed=failed,  
486 - skipped=0,  
487 - errors=0,  
488 - duration=duration,  
489 - results=test_results  
490 - )  
491 -  
492 - self.results.append(suite_result)  
493 - self.logger.info(f"性能测试完成: {suite_result.total_tests}个测试, "  
494 - f"{suite_result.passed}通过, {suite_result.failed}失败")  
495 -  
496 - return suite_result  
497 -  
498 - def generate_report(self) -> str:  
499 - """生成测试报告"""  
500 - self.logger.info("生成测试报告...")  
501 -  
502 - # 计算总体统计  
503 - total_tests = sum(suite.total_tests for suite in self.results)  
504 - total_passed = sum(suite.passed for suite in self.results)  
505 - total_failed = sum(suite.failed for suite in self.results)  
506 - total_skipped = sum(suite.skipped for suite in self.results)  
507 - total_errors = sum(suite.errors for suite in self.results)  
508 - total_duration = sum(suite.duration for suite in self.results)  
509 -  
510 - # 生成报告数据  
511 - report_data = {  
512 - "timestamp": datetime.now().isoformat(),  
513 - "summary": {  
514 - "total_tests": total_tests,  
515 - "passed": total_passed,  
516 - "failed": total_failed,  
517 - "skipped": total_skipped,  
518 - "errors": total_errors,  
519 - "success_rate": (total_passed / total_tests * 100) if total_tests > 0 else 0,  
520 - "total_duration": total_duration  
521 - },  
522 - "suites": [asdict(suite) for suite in self.results]  
523 - }  
524 -  
525 - # 保存JSON报告  
526 - report_file = project_root / 'test_logs' / f'test_report_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json'  
527 - with open(report_file, 'w', encoding='utf-8') as f:  
528 - json.dump(report_data, f, indent=2, ensure_ascii=False)  
529 -  
530 - # 生成文本报告  
531 - text_report = self._generate_text_report(report_data)  
532 -  
533 - report_file_text = project_root / 'test_logs' / f'test_report_{datetime.now().strftime("%Y%m%d_%H%M%S")}.txt'  
534 - with open(report_file_text, 'w', encoding='utf-8') as f:  
535 - f.write(text_report)  
536 -  
537 - self.logger.info(f"测试报告已保存: {report_file}")  
538 - self.logger.info(f"文本报告已保存: {report_file_text}")  
539 -  
540 - return text_report  
541 -  
542 - def _generate_text_report(self, report_data: Dict[str, Any]) -> str:  
543 - """生成文本格式的测试报告"""  
544 - lines = []  
545 -  
546 - # 标题  
547 - lines.append("=" * 60)  
548 - lines.append("搜索引擎测试报告")  
549 - lines.append("=" * 60)  
550 - lines.append(f"时间: {report_data['timestamp']}")  
551 - lines.append("")  
552 -  
553 - # 摘要  
554 - summary = report_data['summary']  
555 - lines.append("测试摘要")  
556 - lines.append("-" * 30)  
557 - lines.append(f"总测试数: {summary['total_tests']}")  
558 - lines.append(f"通过: {summary['passed']}")  
559 - lines.append(f"失败: {summary['failed']}")  
560 - lines.append(f"跳过: {summary['skipped']}")  
561 - lines.append(f"错误: {summary['errors']}")  
562 - lines.append(f"成功率: {summary['success_rate']:.1f}%")  
563 - lines.append(f"总耗时: {summary['total_duration']:.2f}秒")  
564 - lines.append("")  
565 -  
566 - # 各测试套件详情  
567 - lines.append("测试套件详情")  
568 - lines.append("-" * 30)  
569 -  
570 - for suite in report_data['suites']:  
571 - lines.append(f"\n{suite['name']}:")  
572 - lines.append(f" 总数: {suite['total_tests']}, 通过: {suite['passed']}, "  
573 - f"失败: {suite['failed']}, 跳过: {suite['skipped']}, 错误: {suite['errors']}")  
574 - lines.append(f" 耗时: {suite['duration']:.2f}秒")  
575 -  
576 - # 显示失败的测试  
577 - failed_tests = [r for r in suite['results'] if r['status'] in ['failed', 'error']]  
578 - if failed_tests:  
579 - lines.append(" 失败的测试:")  
580 - for test in failed_tests[:5]: # 只显示前5个  
581 - lines.append(f" - {test['name']}: {test['status']}")  
582 - if test.get('error'):  
583 - lines.append(f" 错误: {test['error'][:100]}...")  
584 - if len(failed_tests) > 5:  
585 - lines.append(f" ... 还有 {len(failed_tests) - 5} 个失败的测试")  
586 -  
587 - return "\n".join(lines)  
588 -  
589 - def run_all_tests(self) -> bool:  
590 - """运行所有测试"""  
591 - try:  
592 - # 确保日志目录存在  
593 - (project_root / 'test_logs').mkdir(exist_ok=True)  
594 -  
595 - # 加载环境变量  
596 - env_file = project_root / 'test_env.sh'  
597 - if env_file.exists():  
598 - self.logger.info("加载测试环境变量...")  
599 - result = self._run_command(['bash', str(env_file)])  
600 - if result.returncode != 0:  
601 - self.logger.warning("环境变量加载失败,继续使用默认配置")  
602 -  
603 - # 检查环境  
604 - if not self.check_environment():  
605 - self.logger.error("环境检查失败,请先启动测试环境")  
606 - return False  
607 -  
608 - # 运行各类测试  
609 - test_suites = [  
610 - ("unit", self.run_unit_tests),  
611 - ("integration", self.run_integration_tests),  
612 - ("api", self.run_api_tests),  
613 - ("performance", self.run_performance_tests)  
614 - ]  
615 -  
616 - failed_suites = []  
617 -  
618 - for suite_name, suite_func in test_suites:  
619 - if suite_name in self.config.get('skip_suites', []):  
620 - self.logger.info(f"跳过 {suite_name} 测试")  
621 - continue  
622 -  
623 - try:  
624 - suite_result = suite_func()  
625 - if suite_result.failed > 0 or suite_result.errors > 0:  
626 - failed_suites.append(suite_name)  
627 - except Exception as e:  
628 - self.logger.error(f"{suite_name} 测试执行失败: {e}")  
629 - failed_suites.append(suite_name)  
630 -  
631 - # 生成报告  
632 - report = self.generate_report()  
633 - print(report)  
634 -  
635 - # 返回测试结果  
636 - return len(failed_suites) == 0  
637 -  
638 - except Exception as e:  
639 - self.logger.error(f"测试执行失败: {e}")  
640 - return False  
641 -  
642 -  
643 -def main():  
644 - """主函数"""  
645 - parser = argparse.ArgumentParser(description="运行搜索引擎测试流水线")  
646 - parser.add_argument('--skip-suites', nargs='+',  
647 - choices=['unit', 'integration', 'api', 'performance'],  
648 - help='跳过指定的测试套件')  
649 - parser.add_argument('--log-level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'],  
650 - default='INFO', help='日志级别')  
651 - parser.add_argument('--test-timeout', type=int, default=300,  
652 - help='单个测试超时时间(秒)')  
653 - parser.add_argument('--start-env', action='store_true',  
654 - help='启动测试环境后运行测试')  
655 - parser.add_argument('--stop-env', action='store_true',  
656 - help='测试完成后停止测试环境')  
657 -  
658 - args = parser.parse_args()  
659 -  
660 - # 配置  
661 - config = {  
662 - 'skip_suites': args.skip_suites or [],  
663 - 'log_level': args.log_level,  
664 - 'test_timeout': args.test_timeout  
665 - }  
666 -  
667 - # 启动环境  
668 - if args.start_env:  
669 - print("启动测试环境...")  
670 - result = subprocess.run([  
671 - 'bash', str(project_root / 'scripts' / 'start_test_environment.sh')  
672 - ], capture_output=True, text=True)  
673 -  
674 - if result.returncode != 0:  
675 - print(f"测试环境启动失败: {result.stderr}")  
676 - return 1  
677 -  
678 - print("测试环境启动成功")  
679 - time.sleep(5) # 等待服务完全启动  
680 -  
681 - try:  
682 - # 运行测试  
683 - runner = TestRunner(config)  
684 - success = runner.run_all_tests()  
685 -  
686 - if success:  
687 - print("\n🎉 所有测试通过!")  
688 - return_code = 0  
689 - else:  
690 - print("\n❌ 部分测试失败,请查看日志")  
691 - return_code = 1  
692 -  
693 - finally:  
694 - # 停止环境  
695 - if args.stop_env:  
696 - print("\n停止测试环境...")  
697 - subprocess.run([  
698 - 'bash', str(project_root / 'scripts' / 'stop_test_environment.sh')  
699 - ])  
700 -  
701 - return return_code  
702 -  
703 -  
704 -if __name__ == "__main__":  
705 - sys.exit(main())  
706 \ No newline at end of file 0 \ No newline at end of file
scripts/start_test_environment.sh deleted
@@ -1,275 +0,0 @@ @@ -1,275 +0,0 @@
1 -#!/bin/bash  
2 -  
3 -# 启动测试环境脚本  
4 -# 用于在commit前自动化测试时启动必要的依赖服务  
5 -  
6 -set -e  
7 -  
8 -# 颜色定义  
9 -RED='\033[0;31m'  
10 -GREEN='\033[0;32m'  
11 -YELLOW='\033[1;33m'  
12 -BLUE='\033[0;34m'  
13 -NC='\033[0m' # No Color  
14 -  
15 -# 配置  
16 -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"  
17 -PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"  
18 -TEST_LOG_DIR="$PROJECT_ROOT/test_logs"  
19 -PID_FILE="$PROJECT_ROOT/test_environment.pid"  
20 -  
21 -# 日志文件  
22 -LOG_FILE="$TEST_LOG_DIR/test_environment.log"  
23 -ES_LOG_FILE="$TEST_LOG_DIR/elasticsearch.log"  
24 -API_LOG_FILE="$TEST_LOG_DIR/api_test.log"  
25 -  
26 -echo -e "${GREEN}========================================${NC}"  
27 -echo -e "${GREEN}启动测试环境${NC}"  
28 -echo -e "${GREEN}========================================${NC}"  
29 -  
30 -# 创建日志目录  
31 -mkdir -p "$TEST_LOG_DIR"  
32 -  
33 -# 检查是否已经运行  
34 -if [ -f "$PID_FILE" ]; then  
35 - OLD_PID=$(cat "$PID_FILE")  
36 - if ps -p $OLD_PID > /dev/null 2>&1; then  
37 - echo -e "${YELLOW}测试环境已在运行 (PID: $OLD_PID)${NC}"  
38 - echo -e "${BLUE}如需重启,请先运行: ./scripts/stop_test_environment.sh${NC}"  
39 - exit 0  
40 - else  
41 - rm -f "$PID_FILE"  
42 - fi  
43 -fi  
44 -  
45 -# 激活conda环境  
46 -echo -e "${BLUE}激活conda环境...${NC}"  
47 -source /home/tw/miniconda3/etc/profile.d/conda.sh  
48 -conda activate searchengine  
49 -  
50 -# 设置环境变量  
51 -echo -e "${BLUE}设置测试环境变量...${NC}"  
52 -export PYTHONPATH="$PROJECT_ROOT:$PYTHONPATH"  
53 -export TESTING_MODE=true  
54 -export LOG_LEVEL=DEBUG  
55 -  
56 -# Elasticsearch配置  
57 -export ES_HOST="http://localhost:9200"  
58 -export ES_USERNAME="elastic"  
59 -export ES_PASSWORD="changeme"  
60 -  
61 -# API配置  
62 -export API_HOST="127.0.0.1"  
63 -export API_PORT="6003" # 使用不同的端口避免冲突  
64 -export TENANT_ID="test_tenant"  
65 -  
66 -# 测试配置  
67 -export TEST_TIMEOUT=60  
68 -export TEST_RETRY_COUNT=3  
69 -  
70 -echo -e "${BLUE}环境配置:${NC}"  
71 -echo " ES_HOST: $ES_HOST"  
72 -echo " API_HOST: $API_HOST:$API_PORT"  
73 -echo " TENANT_ID: $TENANT_ID"  
74 -echo " LOG_LEVEL: $LOG_LEVEL"  
75 -echo " TESTING_MODE: $TESTING_MODE"  
76 -  
77 -# 检查Elasticsearch是否运行  
78 -echo -e "${BLUE}检查Elasticsearch状态...${NC}"  
79 -if curl -s "$ES_HOST/_cluster/health" > /dev/null; then  
80 - echo -e "${GREEN}✓ Elasticsearch正在运行${NC}"  
81 -else  
82 - echo -e "${YELLOW}⚠ Elasticsearch未运行,尝试启动...${NC}"  
83 -  
84 - # 尝试启动Elasticsearch(如果安装了本地版本)  
85 - if command -v elasticsearch &> /dev/null; then  
86 - echo -e "${BLUE}启动本地Elasticsearch...${NC}"  
87 - elasticsearch -d -p "$TEST_LOG_DIR/es.pid"  
88 - sleep 10  
89 -  
90 - # 再次检查  
91 - if curl -s "$ES_HOST/_cluster/health" > /dev/null; then  
92 - echo -e "${GREEN}✓ Elasticsearch启动成功${NC}"  
93 - else  
94 - echo -e "${RED}✗ Elasticsearch启动失败${NC}"  
95 - echo -e "${YELLOW}请手动启动Elasticsearch或配置远程ES地址${NC}"  
96 - exit 1  
97 - fi  
98 - else  
99 - echo -e "${RED}✗ 未找到本地Elasticsearch${NC}"  
100 - echo -e "${YELLOW}请启动Elasticsearch服务或修改ES_HOST配置${NC}"  
101 - exit 1  
102 - fi  
103 -fi  
104 -  
105 -# 等待Elasticsearch就绪  
106 -echo -e "${BLUE}等待Elasticsearch就绪...${NC}"  
107 -for i in {1..30}; do  
108 - if curl -s "$ES_HOST/_cluster/health?wait_for_status=yellow&timeout=1s" | grep -q '"status":"green\|yellow"'; then  
109 - echo -e "${GREEN}✓ Elasticsearch已就绪${NC}"  
110 - break  
111 - fi  
112 - if [ $i -eq 30 ]; then  
113 - echo -e "${RED}✗ Elasticsearch就绪超时${NC}"  
114 - exit 1  
115 - fi  
116 - sleep 1  
117 -done  
118 -  
119 -# 创建测试索引(如果需要)  
120 -echo -e "${BLUE}准备测试数据索引...${NC}"  
121 -curl -X PUT "$ES_HOST/test_products" -H 'Content-Type: application/json' -d'  
122 -{  
123 - "settings": {  
124 - "number_of_shards": 1,  
125 - "number_of_replicas": 0,  
126 - "analysis": {  
127 - "analyzer": {  
128 - "ansj": {  
129 - "type": "custom",  
130 - "tokenizer": "keyword"  
131 - }  
132 - }  
133 - }  
134 - },  
135 - "mappings": {  
136 - "properties": {  
137 - "name": {  
138 - "type": "text",  
139 - "analyzer": "ansj"  
140 - },  
141 - "brand_name": {  
142 - "type": "text",  
143 - "analyzer": "ansj"  
144 - },  
145 - "tags": {  
146 - "type": "text",  
147 - "analyzer": "ansj"  
148 - },  
149 - "price": {  
150 - "type": "double"  
151 - },  
152 - "category_id": {  
153 - "type": "integer"  
154 - },  
155 - "spu_id": {  
156 - "type": "keyword"  
157 - },  
158 - "text_embedding": {  
159 - "type": "dense_vector",  
160 - "dims": 1024  
161 - }  
162 - }  
163 - }  
164 -}' > /dev/null 2>&1 || echo -e "${YELLOW}索引可能已存在${NC}"  
165 -  
166 -# 插入测试数据  
167 -echo -e "${BLUE}插入测试数据...${NC}"  
168 -curl -X POST "$ES_HOST/test_products/_bulk" -H 'Content-Type: application/json' -d'  
169 -{"index": {"_id": "1"}}  
170 -{"name": "红色连衣裙", "brand_name": "测试品牌", "tags": ["红色", "连衣裙", "女装"], "price": 299.0, "category_id": 1, "spu_id": "dress_001"}  
171 -{"index": {"_id": "2"}}  
172 -{"name": "蓝色连衣裙", "brand_name": "测试品牌", "tags": ["蓝色", "连衣裙", "女装"], "price": 399.0, "category_id": 1, "spu_id": "dress_002"}  
173 -{"index": {"_id": "3"}}  
174 -{"name": "智能手机", "brand_name": "科技品牌", "tags": ["智能", "手机", "数码"], "price": 2999.0, "category_id": 2, "spu_id": "phone_001"}  
175 -{"index": {"_id": "4"}}  
176 -{"name": "笔记本电脑", "brand_name": "科技品牌", "tags": ["笔记本", "电脑", "办公"], "price": 5999.0, "category_id": 3, "spu_id": "laptop_001"}  
177 -' > /dev/null 2>&1 || echo -e "${YELLOW}测试数据可能已存在${NC}"  
178 -  
179 -# 启动测试API服务  
180 -echo -e "${BLUE}启动测试API服务...${NC}"  
181 -cd "$PROJECT_ROOT"  
182 -  
183 -# 使用后台模式启动API  
184 -python -m api.app \  
185 - --host $API_HOST \  
186 - --port $API_PORT \  
187 - --tenant $TENANT_ID \  
188 - --es-host $ES_HOST \  
189 - > "$API_LOG_FILE" 2>&1 &  
190 -  
191 -API_PID=$!  
192 -echo $API_PID > "$PID_FILE"  
193 -  
194 -# 等待API服务启动  
195 -echo -e "${BLUE}等待API服务启动...${NC}"  
196 -for i in {1..30}; do  
197 - if curl -s "http://$API_HOST:$API_PORT/health" > /dev/null; then  
198 - echo -e "${GREEN}✓ API服务已就绪 (PID: $API_PID)${NC}"  
199 - break  
200 - fi  
201 - if [ $i -eq 30 ]; then  
202 - echo -e "${RED}✗ API服务启动超时${NC}"  
203 - kill $API_PID 2>/dev/null || true  
204 - rm -f "$PID_FILE"  
205 - exit 1  
206 - fi  
207 - sleep 1  
208 -done  
209 -  
210 -# 验证测试环境  
211 -echo -e "${BLUE}验证测试环境...${NC}"  
212 -  
213 -# 测试Elasticsearch连接  
214 -if curl -s "$ES_HOST/_cluster/health" | grep -q '"status":"green\|yellow"'; then  
215 - echo -e "${GREEN}✓ Elasticsearch连接正常${NC}"  
216 -else  
217 - echo -e "${RED}✗ Elasticsearch连接失败${NC}"  
218 - exit 1  
219 -fi  
220 -  
221 -# 测试API健康检查  
222 -if curl -s "http://$API_HOST:$API_PORT/health" | grep -q '"status"'; then  
223 - echo -e "${GREEN}✓ API服务健康检查通过${NC}"  
224 -else  
225 - echo -e "${RED}✗ API服务健康检查失败${NC}"  
226 - exit 1  
227 -fi  
228 -  
229 -# 测试基本搜索功能  
230 -if curl -s "http://$API_HOST:$API_PORT/search?q=红色连衣裙" | grep -q '"hits"'; then  
231 - echo -e "${GREEN}✓ 基本搜索功能正常${NC}"  
232 -else  
233 - echo -e "${YELLOW}⚠ 基本搜索功能可能有问题,但继续进行${NC}"  
234 -fi  
235 -  
236 -# 输出环境信息  
237 -echo -e "${GREEN}========================================${NC}"  
238 -echo -e "${GREEN}测试环境启动完成!${NC}"  
239 -echo -e "${GREEN}========================================${NC}"  
240 -echo -e "${BLUE}服务信息:${NC}"  
241 -echo " Elasticsearch: $ES_HOST"  
242 -echo " API服务: http://$API_HOST:$API_PORT"  
243 -echo " 测试客户: $TENANT_ID"  
244 -echo -e "${BLUE}进程信息:${NC}"  
245 -echo " API PID: $API_PID"  
246 -echo " PID文件: $PID_FILE"  
247 -echo -e "${BLUE}日志文件:${NC}"  
248 -echo " 环境日志: $LOG_FILE"  
249 -echo " API日志: $API_LOG_FILE"  
250 -echo " ES日志: $ES_LOG_FILE"  
251 -echo -e "${BLUE}测试命令:${NC}"  
252 -echo " 运行所有测试: python scripts/run_tests.py"  
253 -echo " 单元测试: pytest tests/unit/ -v"  
254 -echo " 集成测试: pytest tests/integration/ -v"  
255 -echo " API测试: pytest tests/integration/test_api_integration.py -v"  
256 -echo "e${NC}"  
257 -echo -e "${BLUE}停止环境: ./scripts/stop_test_environment.sh${NC}"  
258 -  
259 -# 保存环境变量到文件供测试脚本使用  
260 -cat > "$PROJECT_ROOT/test_env.sh" << EOF  
261 -#!/bin/bash  
262 -export ES_HOST="$ES_HOST"  
263 -export ES_USERNAME="$ES_USERNAME"  
264 -export ES_PASSWORD="$ES_PASSWORD"  
265 -export API_HOST="$API_HOST"  
266 -export API_PORT="$API_PORT"  
267 -export TENANT_ID="$TENANT_ID"  
268 -export TESTING_MODE="$TESTING_MODE"  
269 -export LOG_LEVEL="$LOG_LEVEL"  
270 -export PYTHONPATH="$PROJECT_ROOT:\$PYTHONPATH"  
271 -EOF  
272 -  
273 -chmod +x "$PROJECT_ROOT/test_env.sh"  
274 -  
275 -echo -e "${GREEN}测试环境已准备就绪!${NC}"  
276 \ No newline at end of file 0 \ No newline at end of file
scripts/stop_test_environment.sh deleted
@@ -1,82 +0,0 @@ @@ -1,82 +0,0 @@
1 -#!/bin/bash  
2 -  
3 -# 停止测试环境脚本  
4 -  
5 -set -e  
6 -  
7 -# 颜色定义  
8 -RED='\033[0;31m'  
9 -GREEN='\033[0;32m'  
10 -YELLOW='\033[1;33m'  
11 -BLUE='\033[0;34m'  
12 -NC='\033[0m' # No Color  
13 -  
14 -# 配置  
15 -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"  
16 -PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"  
17 -PID_FILE="$PROJECT_ROOT/test_environment.pid"  
18 -ES_PID_FILE="$PROJECT_ROOT/test_logs/es.pid"  
19 -  
20 -echo -e "${BLUE}========================================${NC}"  
21 -echo -e "${BLUE}停止测试环境${NC}"  
22 -echo -e "${BLUE}========================================${NC}"  
23 -  
24 -# 停止API服务  
25 -if [ -f "$PID_FILE" ]; then  
26 - API_PID=$(cat "$PID_FILE")  
27 - if ps -p $API_PID > /dev/null 2>&1; then  
28 - echo -e "${BLUE}停止API服务 (PID: $API_PID)...${NC}"  
29 - kill $API_PID  
30 -  
31 - # 等待进程结束  
32 - for i in {1..10}; do  
33 - if ! ps -p $API_PID > /dev/null 2>&1; then  
34 - echo -e "${GREEN}✓ API服务已停止${NC}"  
35 - break  
36 - fi  
37 - if [ $i -eq 10 ]; then  
38 - echo -e "${YELLOW}强制停止API服务...${NC}"  
39 - kill -9 $API_PID 2>/dev/null || true  
40 - fi  
41 - sleep 1  
42 - done  
43 - else  
44 - echo -e "${YELLOW}API服务进程不存在${NC}"  
45 - fi  
46 - rm -f "$PID_FILE"  
47 -else  
48 - echo -e "${YELLOW}未找到API服务PID文件${NC}"  
49 -fi  
50 -  
51 -# 停止Elasticsearch(如果是本地启动的)  
52 -if [ -f "$ES_PID_FILE" ]; then  
53 - ES_PID=$(cat "$ES_PID_FILE")  
54 - if ps -p $ES_PID > /dev/null 2>&1; then  
55 - echo -e "${BLUE}停止本地Elasticsearch (PID: $ES_PID)...${NC}"  
56 - kill $ES_PID  
57 - rm -f "$ES_PID_FILE"  
58 - echo -e "${GREEN}✓ Elasticsearch已停止${NC}"  
59 - else  
60 - echo -e "${YELLOW}Elasticsearch进程不存在${NC}"  
61 - rm -f "$ES_PID_FILE"  
62 - fi  
63 -else  
64 - echo -e "${BLUE}跳过本地Elasticsearch停止(未找到PID文件)${NC}"  
65 -fi  
66 -  
67 -# 清理测试环境文件  
68 -echo -e "${BLUE}清理测试环境文件...${NC}"  
69 -rm -f "$PROJECT_ROOT/test_env.sh"  
70 -  
71 -# 清理测试索引(可选)  
72 -read -p "是否删除测试索引? (y/N): " -n 1 -r  
73 -echo  
74 -if [[ $REPLY =~ ^[Yy]$ ]]; then  
75 - echo -e "${BLUE}删除测试索引...${NC}"  
76 - curl -X DELETE "http://localhost:9200/test_products" 2>/dev/null || true  
77 - echo -e "${GREEN}✓ 测试索引已删除${NC}"  
78 -fi  
79 -  
80 -echo -e "${GREEN}========================================${NC}"  
81 -echo -e "${GREEN}测试环境已停止!${NC}"  
82 -echo -e "${GREEN}========================================${NC}"  
83 \ No newline at end of file 0 \ No newline at end of file
scripts/test_base.py deleted
@@ -1,242 +0,0 @@ @@ -1,242 +0,0 @@
1 -#!/usr/bin/env python3  
2 -"""  
3 -Test script for base configuration.  
4 -  
5 -Tests data ingestion, search API, response format, and tenant isolation.  
6 -"""  
7 -  
8 -import sys  
9 -import os  
10 -import argparse  
11 -import requests  
12 -import json  
13 -from pathlib import Path  
14 -  
15 -# Add parent directory to path  
16 -sys.path.insert(0, str(Path(__file__).parent.parent))  
17 -  
18 -  
19 -def test_search_api(base_url: str, tenant_id: str, query: str = "耳机"):  
20 - """  
21 - Test search API.  
22 -  
23 - Args:  
24 - base_url: API base URL  
25 - tenant_id: Tenant ID  
26 - query: Search query  
27 -  
28 - Returns:  
29 - Response JSON or None if failed  
30 - """  
31 - url = f"{base_url}/search/"  
32 - headers = {  
33 - "X-Tenant-ID": tenant_id,  
34 - "Content-Type": "application/json"  
35 - }  
36 - payload = {  
37 - "query": query,  
38 - "size": 10,  
39 - "from": 0  
40 - }  
41 -  
42 - print(f"\nTesting search API:")  
43 - print(f" URL: {url}")  
44 - print(f" Query: {query}")  
45 - print(f" Tenant ID: {tenant_id}")  
46 -  
47 - try:  
48 - response = requests.post(url, json=payload, headers=headers, timeout=30)  
49 - response.raise_for_status()  
50 - data = response.json()  
51 -  
52 - print(f" Status: {response.status_code}")  
53 - print(f" Total: {data.get('total', 0)}")  
54 - print(f" Results: {len(data.get('results', []))}")  
55 -  
56 - return data  
57 - except Exception as e:  
58 - print(f" ERROR: {e}")  
59 - return None  
60 -  
61 -  
62 -def validate_response_format(data: dict):  
63 - """  
64 - Validate response format.  
65 -  
66 - Args:  
67 - data: Response data  
68 -  
69 - Returns:  
70 - List of validation errors (empty if valid)  
71 - """  
72 - errors = []  
73 -  
74 - # Check for results field (not hits)  
75 - if 'hits' in data:  
76 - errors.append("Response contains 'hits' field (should be 'results')")  
77 -  
78 - if 'results' not in data:  
79 - errors.append("Response missing 'results' field")  
80 - else:  
81 - results = data['results']  
82 - if not isinstance(results, list):  
83 - errors.append("'results' should be a list")  
84 - else:  
85 - # Validate first result structure  
86 - if results:  
87 - result = results[0]  
88 - required_fields = ['spu_id', 'title', 'skus', 'relevance_score']  
89 - for field in required_fields:  
90 - if field not in result:  
91 - errors.append(f"Result missing required field: {field}")  
92 -  
93 - # Check for ES internal fields  
94 - es_internal_fields = ['_id', '_score', '_source']  
95 - for field in es_internal_fields:  
96 - if field in result:  
97 - errors.append(f"Result contains ES internal field: {field}")  
98 -  
99 - # Validate skus  
100 - if 'skus' in result:  
101 - skus = result['skus']  
102 - if not isinstance(skus, list):  
103 - errors.append("'skus' should be a list")  
104 - elif skus:  
105 - sku = skus[0]  
106 - sku_required = ['sku_id', 'price', 'sku', 'stock']  
107 - for field in sku_required:  
108 - if field not in sku:  
109 - errors.append(f"SKU missing required field: {field}")  
110 -  
111 - # Check for suggestions and related_searches  
112 - if 'suggestions' not in data:  
113 - errors.append("Response missing 'suggestions' field")  
114 - if 'related_searches' not in data:  
115 - errors.append("Response missing 'related_searches' field")  
116 -  
117 - return errors  
118 -  
119 -  
120 -def test_facets(base_url: str, tenant_id: str):  
121 - """  
122 - Test facets aggregation.  
123 -  
124 - Args:  
125 - base_url: API base URL  
126 - tenant_id: Tenant ID  
127 -  
128 - Returns:  
129 - Response JSON or None if failed  
130 - """  
131 - url = f"{base_url}/search/"  
132 - headers = {  
133 - "X-Tenant-ID": tenant_id,  
134 - "Content-Type": "application/json"  
135 - }  
136 - payload = {  
137 - "query": "商品",  
138 - "size": 10,  
139 - "facets": ["category.keyword", "vendor.keyword"]  
140 - }  
141 -  
142 - print(f"\nTesting facets:")  
143 - print(f" Facets: {payload['facets']}")  
144 -  
145 - try:  
146 - response = requests.post(url, json=payload, headers=headers, timeout=30)  
147 - response.raise_for_status()  
148 - data = response.json()  
149 -  
150 - if 'facets' in data and data['facets']:  
151 - print(f" Facets returned: {len(data['facets'])}")  
152 - for facet in data['facets']:  
153 - print(f" - {facet.get('field')}: {len(facet.get('values', []))} values")  
154 - else:  
155 - print(" WARNING: No facets returned")  
156 -  
157 - return data  
158 - except Exception as e:  
159 - print(f" ERROR: {e}")  
160 - return None  
161 -  
162 -  
163 -def test_tenant_isolation(base_url: str, tenant_id_1: str, tenant_id_2: str):  
164 - """  
165 - Test tenant isolation.  
166 -  
167 - Args:  
168 - base_url: API base URL  
169 - tenant_id_1: First tenant ID  
170 - tenant_id_2: Second tenant ID  
171 - """  
172 - print(f"\nTesting tenant isolation:")  
173 - print(f" Tenant 1: {tenant_id_1}")  
174 - print(f" Tenant 2: {tenant_id_2}")  
175 -  
176 - # Search for tenant 1  
177 - data1 = test_search_api(base_url, tenant_id_1, "商品")  
178 - # Search for tenant 2  
179 - data2 = test_search_api(base_url, tenant_id_2, "商品")  
180 -  
181 - if data1 and data2:  
182 - results1 = set(r.get('spu_id') for r in data1.get('results', []))  
183 - results2 = set(r.get('spu_id') for r in data2.get('results', []))  
184 -  
185 - overlap = results1 & results2  
186 - if overlap:  
187 - print(f" WARNING: Found {len(overlap)} overlapping results between tenants")  
188 - else:  
189 - print(f" OK: No overlapping results (tenant isolation working)")  
190 -  
191 -  
192 -def main():  
193 - parser = argparse.ArgumentParser(description='Test base configuration')  
194 - parser.add_argument('--api-url', default='http://localhost:8000', help='API base URL')  
195 - parser.add_argument('--tenant-id', default='1', help='Tenant ID for testing')  
196 - parser.add_argument('--test-tenant-2', help='Second tenant ID for isolation test')  
197 -  
198 - args = parser.parse_args()  
199 -  
200 - print("=" * 60)  
201 - print("Base Configuration Test Suite")  
202 - print("=" * 60)  
203 -  
204 - # Test 1: Basic search  
205 - print("\n[Test 1] Basic Search")  
206 - data = test_search_api(args.api_url, args.tenant_id)  
207 - if not data:  
208 - print("FAILED: Basic search test")  
209 - return 1  
210 -  
211 - # Test 2: Response format validation  
212 - print("\n[Test 2] Response Format Validation")  
213 - errors = validate_response_format(data)  
214 - if errors:  
215 - print("FAILED: Response format validation")  
216 - for error in errors:  
217 - print(f" - {error}")  
218 - return 1  
219 - else:  
220 - print("PASSED: Response format is correct")  
221 -  
222 - # Test 3: Facets  
223 - print("\n[Test 3] Facets Aggregation")  
224 - facet_data = test_facets(args.api_url, args.tenant_id)  
225 - if not facet_data:  
226 - print("WARNING: Facets test failed (may be expected if no data)")  
227 -  
228 - # Test 4: Tenant isolation (if second tenant provided)  
229 - if args.test_tenant_2:  
230 - print("\n[Test 4] Tenant Isolation")  
231 - test_tenant_isolation(args.api_url, args.tenant_id, args.test_tenant_2)  
232 -  
233 - print("\n" + "=" * 60)  
234 - print("All tests completed")  
235 - print("=" * 60)  
236 -  
237 - return 0  
238 -  
239 -  
240 -if __name__ == '__main__':  
241 - sys.exit(main())  
242 -  
scripts/test_frontend.sh deleted
@@ -1,94 +0,0 @@ @@ -1,94 +0,0 @@
1 -#!/bin/bash  
2 -  
3 -# Test Frontend - Quick verification script  
4 -  
5 -set -e  
6 -  
7 -GREEN='\033[0;32m'  
8 -YELLOW='\033[1;33m'  
9 -RED='\033[0;31m'  
10 -NC='\033[0m'  
11 -  
12 -API_URL="http://120.76.41.98:6002"  
13 -  
14 -echo -e "${GREEN}========================================${NC}"  
15 -echo -e "${GREEN}Frontend Test Script${NC}"  
16 -echo -e "${GREEN}========================================${NC}"  
17 -  
18 -echo -e "\n${YELLOW}Testing API endpoints...${NC}"  
19 -  
20 -# Test 1: Health check  
21 -echo -e "\n1. Testing health endpoint..."  
22 -if curl -s "${API_URL}/health" > /dev/null; then  
23 - echo -e "${GREEN}✓ Health check passed${NC}"  
24 -else  
25 - echo -e "${RED}✗ Health check failed${NC}"  
26 - exit 1  
27 -fi  
28 -  
29 -# Test 2: Frontend HTML  
30 -echo -e "\n2. Testing frontend HTML..."  
31 -if curl -s "${API_URL}/" | grep -q "Product Search"; then  
32 - echo -e "${GREEN}✓ Frontend HTML accessible${NC}"  
33 -else  
34 - echo -e "${RED}✗ Frontend HTML not found${NC}"  
35 - exit 1  
36 -fi  
37 -  
38 -# Test 3: Static CSS  
39 -echo -e "\n3. Testing static CSS..."  
40 -if curl -s "${API_URL}/static/css/style.css" | grep -q "page-container"; then  
41 - echo -e "${GREEN}✓ CSS file accessible${NC}"  
42 -else  
43 - echo -e "${RED}✗ CSS file not found${NC}"  
44 - exit 1  
45 -fi  
46 -  
47 -# Test 4: Static JS  
48 -echo -e "\n4. Testing static JavaScript..."  
49 -if curl -s "${API_URL}/static/js/app.js" | grep -q "performSearch"; then  
50 - echo -e "${GREEN}✓ JavaScript file accessible${NC}"  
51 -else  
52 - echo -e "${RED}✗ JavaScript file not found${NC}"  
53 - exit 1  
54 -fi  
55 -  
56 -# Test 5: Search API  
57 -echo -e "\n5. Testing search API..."  
58 -SEARCH_RESULT=$(curl -s -X POST "${API_URL}/search/" \  
59 - -H "Content-Type: application/json" \  
60 - -d '{"query":"玩具","size":5}')  
61 -  
62 -if echo "$SEARCH_RESULT" | grep -q "hits"; then  
63 - echo -e "${GREEN}✓ Search API working${NC}"  
64 - TOTAL=$(echo "$SEARCH_RESULT" | grep -o '"total":[0-9]*' | cut -d: -f2)  
65 - echo -e " Found ${YELLOW}${TOTAL}${NC} results"  
66 -else  
67 - echo -e "${RED}✗ Search API failed${NC}"  
68 - exit 1  
69 -fi  
70 -  
71 -echo -e "\n${GREEN}========================================${NC}"  
72 -echo -e "${GREEN}All tests passed! ✓${NC}"  
73 -echo -e "${GREEN}========================================${NC}"  
74 -  
75 -echo -e "\n${YELLOW}Frontend is ready!${NC}"  
76 -echo -e "Open in browser: ${GREEN}${API_URL}/${NC}"  
77 -  
78 -echo -e "\n${YELLOW}Quick Start Guide:${NC}"  
79 -echo "1. Open browser and go to: ${API_URL}/"  
80 -echo "2. Enter a search query (e.g., '玩具')"  
81 -echo "3. Click on filter tags to refine results"  
82 -echo "4. Use sort buttons with arrows to sort"  
83 -echo "5. Use pagination at the bottom to browse"  
84 -  
85 -echo -e "\n${YELLOW}Key Features:${NC}"  
86 -echo "- Clean white background design"  
87 -echo "- Horizontal filter tags (categories, brands, suppliers)"  
88 -echo "- Sort buttons with up/down arrows for ascending/descending"  
89 -echo "- Product grid with images, prices, MOQ info"  
90 -echo "- Full pagination support"  
91 -echo "- Responsive design for mobile and desktop"  
92 -  
93 -echo -e "\n${GREEN}Enjoy your new frontend! 🎉${NC}"  
94 -  
scripts/test_cloud_embedding.py renamed to tests/test_cloud_embedding.py
scripts/test_cnclip_service.py renamed to tests/test_cnclip_service.py
scripts/test_facet_api.py renamed to tests/test_facet_api.py