add:flask app
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user