add:flask app

This commit is contained in:
gongwenxin
2025-06-06 14:52:08 +08:00
parent b72406df99
commit adc1a0053f
18 changed files with 4726 additions and 3468 deletions
+18 -6
View File
@@ -1,4 +1,5 @@
import os
import sys
import importlib.util
import inspect
import logging
@@ -12,27 +13,38 @@ class TestCaseRegistry:
"""
负责发现、加载和管理所有自定义的APITestCase类。
"""
def __init__(self, test_cases_dir: str):
def __init__(self, test_cases_dir: Optional[str]):
"""
初始化 TestCaseRegistry。
Args:
test_cases_dir: 存放自定义测试用例 (.py 文件) 的目录路径。
"""
self.test_cases_dir = test_cases_dir
self.logger = logging.getLogger(__name__)
self.test_cases_dir = test_cases_dir
self._registry: Dict[str, Type[BaseAPITestCase]] = {}
self._test_case_classes: List[Type[BaseAPITestCase]] = []
self.discover_test_cases()
self._discovery_errors: List[str] = []
if self.test_cases_dir:
self.discover_test_cases()
else:
self.logger.info("No custom test cases directory provided. Skipping test case discovery.")
def discover_test_cases(self):
"""
扫描指定目录及其所有子目录,动态导入模块,并注册所有继承自 BaseAPITestCase 的类。
"""
if not os.path.isdir(self.test_cases_dir):
self.logger.warning(f"测试用例目录不存在或不是一个目录: {self.test_cases_dir}")
if not self.test_cases_dir:
self.logger.info("Test cases directory is not set. Skipping discovery.")
return
self.logger.info(f"开始从目录 '{self.test_cases_dir}' 及其子目录发现测试用例...")
if not os.path.isdir(self.test_cases_dir):
self.logger.error(f"Custom test cases directory not found or is not a directory: {self.test_cases_dir}")
self._discovery_errors.append(f"Directory not found: {self.test_cases_dir}")
return
self.logger.info(f"Discovering custom test cases from: {self.test_cases_dir}")
sys.path.insert(0, self.test_cases_dir)
found_count = 0
# 使用 os.walk 进行递归扫描
for root_dir, _, files in os.walk(self.test_cases_dir):
+15 -36
View File
@@ -18,6 +18,7 @@ from dataclasses import asdict as dataclass_asdict, is_dataclass
import copy
from collections import defaultdict
from pathlib import Path
from urllib.parse import urljoin # <-- ADDED
from pydantic import BaseModel, Field, create_model, HttpUrl # Added HttpUrl for Literal type hint if needed
from pydantic.networks import EmailStr
@@ -425,7 +426,7 @@ class APITestOrchestrator:
"use_for_headers": use_llm_for_headers,
}
if llm_api_key and LLMService:
if llm_api_key and llm_base_url and LLMService: # <-- MODIFIED: Added check for llm_base_url
try:
self.llm_service = LLMService(api_key=llm_api_key, base_url=llm_base_url, model_name=llm_model_name)
self.logger.info(f"LLMService initialized with model: {self.llm_service.model_name}.")
@@ -2155,7 +2156,7 @@ class APITestOrchestrator:
final_headers['Content-Type'] = 'application/json'
self.logger.debug(f"{step_log_prefix}: 为JSON请求体设置默认Content-Type: application/json")
full_request_url = self._format_url_with_path_params(api_op_spec.path, final_path_params)
full_request_url = urljoin(self.base_url, self._format_url_with_path_params(api_op_spec.path, final_path_params)) # <-- MODIFIED
api_request_obj = APIRequest(
method=api_op_spec.method,
url=full_request_url,
@@ -2488,6 +2489,8 @@ class APITestOrchestrator:
summary.add_stage_result(failure_result)
self.logger.info(f"API Test Stage execution processed. Considered {total_stages_considered_for_execution} (stage_definition x api_group) combinations.")
return summary # <-- ADDED
def _execute_tests_from_parsed_spec(self,
parsed_spec: ParsedAPISpec,
@@ -2497,54 +2500,30 @@ class APITestOrchestrator:
custom_test_cases_dir: Optional[str] = None
) -> TestSummary:
"""基于已解析的API规范对象执行测试用例。"""
# Restore the original start of the method body, the rest of the method should be intact from before.
if custom_test_cases_dir and (not self.test_case_registry or not hasattr(self.test_case_registry, 'test_cases_dir') or self.test_case_registry.test_cases_dir != custom_test_cases_dir):
self.logger.info(f"Re-initializing TestCaseRegistry from _execute_tests_from_parsed_spec with new directory: {custom_test_cases_dir}")
try:
# Assuming TestCaseRegistry can be re-initialized or its directory updated.
# If TestCaseRegistry is loaded in __init__, this might need adjustment
# For now, let's assume direct re-init is possible if dir changes.
self.test_case_registry = TestCaseRegistry()
self.test_case_registry.discover_and_load_test_cases(custom_test_cases_dir)
self.logger.info(f"TestCaseRegistry (re)initialized, found {len(self.test_case_registry.get_all_test_case_classes())} test case classes.")
except Exception as e:
self.logger.error(f"Failed to re-initialize TestCaseRegistry from _execute_tests_from_parsed_spec: {e}", exc_info=True)
# summary.finalize_summary() # Finalize might be premature here
return summary # Early exit if registry fails
if custom_test_cases_dir and (not self.test_case_registry or self.test_case_registry.test_cases_dir != custom_test_cases_dir):
self.logger.info(f"Re-initializing TestCaseRegistry with new directory: {custom_test_cases_dir}")
self.test_case_registry = TestCaseRegistry(test_cases_dir=custom_test_cases_dir)
endpoints_to_test: List[Union[YAPIEndpoint, SwaggerEndpoint]] = []
if isinstance(parsed_spec, ParsedYAPISpec):
endpoints_to_test = parsed_spec.endpoints
if categories:
# Ensure YAPIEndpoint has 'category_name' if this filter is used.
endpoints_to_test = [ep for ep in endpoints_to_test if hasattr(ep, 'category_name') and ep.category_name in categories]
elif isinstance(parsed_spec, ParsedSwaggerSpec):
endpoints_to_test = parsed_spec.endpoints
if tags:
# Ensure SwaggerEndpoint has 'tags' attribute for this filter.
endpoints_to_test = [ep for ep in endpoints_to_test if hasattr(ep, 'tags') and isinstance(ep.tags, list) and any(tag in ep.tags for tag in tags)]
else:
self.logger.warning(f"Unknown parsed_spec type: {type(parsed_spec)}. Cannot filter endpoints.")
# summary.finalize_summary() # Finalize might be premature
return summary
current_total_defined = summary.total_endpoints_defined
summary.set_total_endpoints_defined(current_total_defined + len(endpoints_to_test))
total_applicable_tcs_for_this_run = 0
summary.set_total_endpoints_defined(summary.total_endpoints_defined + len(endpoints_to_test))
if self.test_case_registry:
for endpoint_spec_obj in endpoints_to_test:
total_applicable_tcs_for_this_run += len(
self.test_case_registry.get_applicable_test_cases(
endpoint_spec_obj.method.upper(), endpoint_spec_obj.path
)
)
current_total_applicable = summary.total_test_cases_applicable
summary.set_total_test_cases_applicable(current_total_applicable + total_applicable_tcs_for_this_run)
total_applicable_tcs = sum(
len(self.test_case_registry.get_applicable_test_cases(ep.method.upper(), ep.path))
for ep in endpoints_to_test
)
summary.set_total_test_cases_applicable(summary.total_test_cases_applicable + total_applicable_tcs)
for endpoint in endpoints_to_test:
# global_api_spec 应该是包含完整定义的 ParsedYAPISpec/ParsedSwaggerSpec 对象
# 而不是其内部的 .spec 字典,因为 _execute_single_test_case 需要这个对象
result = self.run_test_for_endpoint(endpoint, global_api_spec=parsed_spec)
summary.add_endpoint_result(result)