v0.1
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -93,16 +93,18 @@ class BaseAPITestCase:
|
||||
use_llm_for_query_params: bool = False
|
||||
use_llm_for_headers: bool = False
|
||||
|
||||
def __init__(self, endpoint_spec: Dict[str, Any], global_api_spec: Dict[str, Any]):
|
||||
def __init__(self, endpoint_spec: Dict[str, Any], global_api_spec: Dict[str, Any], json_schema_validator: Optional[Any] = None):
|
||||
"""
|
||||
初始化测试用例。
|
||||
Args:
|
||||
endpoint_spec: 当前被测API端点的详细定义 (来自YAPI/Swagger解析结果)。
|
||||
global_api_spec: 完整的API规范文档 (来自YAPI/Swagger解析结果)。
|
||||
json_schema_validator: APITestOrchestrator 传入的 JSONSchemaValidator 实例 (可选)。
|
||||
"""
|
||||
self.endpoint_spec = endpoint_spec
|
||||
self.global_api_spec = global_api_spec
|
||||
self.logger = logging.getLogger(f"testcase.{self.id}")
|
||||
self.json_schema_validator = json_schema_validator # 存储传入的校验器实例
|
||||
self.logger.debug(f"Test case '{self.id}' initialized for endpoint: {self.endpoint_spec.get('method', '')} {self.endpoint_spec.get('path', '')}")
|
||||
|
||||
# --- 1. 请求生成与修改阶段 ---
|
||||
@@ -118,6 +120,21 @@ class BaseAPITestCase:
|
||||
self.logger.debug(f"Hook: generate_request_body, current body type: {type(current_body)}")
|
||||
return current_body
|
||||
|
||||
# --- 1.5. 请求URL修改阶段 (新增钩子) ---
|
||||
def modify_request_url(self, current_url: str) -> str:
|
||||
"""
|
||||
允许测试用例在请求发送前修改最终构建的URL。
|
||||
默认不进行任何修改。
|
||||
|
||||
Args:
|
||||
current_url: 由编排器构建的当前请求URL (已包含base_url和路径参数)。
|
||||
|
||||
Returns:
|
||||
最终要使用的URL。
|
||||
"""
|
||||
self.logger.debug(f"Hook: modify_request_url, original URL: {current_url}")
|
||||
return current_url
|
||||
|
||||
# --- 2. 请求预校验阶段 ---
|
||||
def validate_request_url(self, url: str, request_context: APIRequestContext) -> List[ValidationResult]:
|
||||
self.logger.debug(f"Hook: validate_request_url, url: {url}")
|
||||
@@ -141,6 +158,48 @@ class BaseAPITestCase:
|
||||
self.logger.debug(f"Hook: check_performance, elapsed: {response_context.elapsed_time}")
|
||||
return []
|
||||
|
||||
# --- Helper methods ---
|
||||
def validate_data_against_schema(
|
||||
self,
|
||||
data_to_validate: Any,
|
||||
schema_definition: Dict[str, Any],
|
||||
context_message_prefix: str = "Data"
|
||||
) -> List[ValidationResult]:
|
||||
"""
|
||||
使用注入的 JSONSchemaValidator 针对给定的 schema 验证数据。
|
||||
Args:
|
||||
data_to_validate: 要验证的数据 (通常是解析后的 JSON 对象)。
|
||||
schema_definition: JSON Schema 定义字典。
|
||||
context_message_prefix: 用于错误消息的上下文前缀。
|
||||
Returns:
|
||||
一个 ValidationResult 对象的列表。
|
||||
"""
|
||||
results = []
|
||||
if not self.json_schema_validator:
|
||||
self.logger.warning(f"JSONSchemaValidator 未注入到测试用例 '{self.id}'。无法执行 schema 验证。")
|
||||
results.append(self.failed(f"{context_message_prefix} schema validation skipped: Validator not available."))
|
||||
return results
|
||||
|
||||
is_valid, errors = self.json_schema_validator.validate(data_to_validate, schema_definition)
|
||||
|
||||
if is_valid:
|
||||
results.append(self.passed(f"{context_message_prefix} conforms to the JSON schema."))
|
||||
else:
|
||||
error_messages = []
|
||||
if isinstance(errors, list):
|
||||
for error in errors: # jsonschema.exceptions.ValidationError 对象
|
||||
error_messages.append(f"- Path: '{list(error.path)}', Message: {error.message}") # error.path 是一个deque
|
||||
elif isinstance(errors, str): # 兼容旧版或简单错误字符串
|
||||
error_messages.append(errors)
|
||||
|
||||
full_message = f"{context_message_prefix} does not conform to the JSON schema. Errors:\n" + "\n".join(error_messages)
|
||||
results.append(self.failed(
|
||||
message=full_message,
|
||||
details={"schema_errors": error_messages, "validated_data_sample": str(data_to_validate)[:200]}
|
||||
))
|
||||
self.logger.warning(f"{context_message_prefix} schema validation failed: {full_message}")
|
||||
return results
|
||||
|
||||
# --- Helper to easily create a passed ValidationResult ---
|
||||
@staticmethod
|
||||
def passed(message: str, details: Optional[Dict[str, Any]] = None) -> ValidationResult:
|
||||
|
||||
@@ -665,7 +665,8 @@ class APITestOrchestrator:
|
||||
try:
|
||||
test_case_instance = test_case_class(
|
||||
endpoint_spec=endpoint_spec_dict,
|
||||
global_api_spec=global_api_spec_dict
|
||||
global_api_spec=global_api_spec_dict,
|
||||
json_schema_validator=self.validator # <--- 注入 JSONSchemaValidator
|
||||
)
|
||||
test_case_instance.logger.info(f"开始执行测试用例 '{test_case_instance.id}' for endpoint '{endpoint_spec_dict.get('method')} {endpoint_spec_dict.get('path')}'")
|
||||
|
||||
@@ -694,9 +695,26 @@ class APITestOrchestrator:
|
||||
# 注意: 如果 _prepare_initial_request_data 填充的 final_url 已经包含了 base_url,这里的拼接逻辑需要调整
|
||||
# 假设 final_url_template 只是 path string e.g. /users/{id}
|
||||
|
||||
# ---- 调用测试用例的 URL 修改钩子 ----
|
||||
effective_url = final_url # 默认使用原始构建的URL
|
||||
if hasattr(test_case_instance, 'modify_request_url') and callable(getattr(test_case_instance, 'modify_request_url')):
|
||||
try:
|
||||
modified_url_by_tc = test_case_instance.modify_request_url(final_url)
|
||||
if modified_url_by_tc != final_url:
|
||||
test_case_instance.logger.info(f"Test case '{test_case_instance.id}' modified URL from '{final_url}' to '{modified_url_by_tc}'")
|
||||
effective_url = modified_url_by_tc # 使用测试用例修改后的URL
|
||||
else:
|
||||
test_case_instance.logger.debug(f"Test case '{test_case_instance.id}' did not modify the URL via modify_request_url hook.")
|
||||
except Exception as e_url_mod:
|
||||
test_case_instance.logger.error(f"Error in test case '{test_case_instance.id}' during modify_request_url: {e_url_mod}. Using original URL '{final_url}'.", exc_info=True)
|
||||
# effective_url 保持为 final_url
|
||||
else:
|
||||
test_case_instance.logger.debug(f"Test case '{test_case_instance.id}' does not have a callable modify_request_url method. Using original URL.")
|
||||
# ---- 结束 URL 修改钩子调用 ----
|
||||
|
||||
api_request_context = APIRequestContext(
|
||||
method=method, # 使用从 _prepare_initial_request_data 获取的 method
|
||||
url=final_url,
|
||||
url=effective_url, # <--- 使用 effective_url
|
||||
path_params=current_path_params,
|
||||
query_params=current_q_params,
|
||||
headers=current_headers,
|
||||
@@ -1156,8 +1174,13 @@ class APITestOrchestrator:
|
||||
global_api_spec=global_api_spec
|
||||
)
|
||||
endpoint_test_result.add_executed_test_case_result(executed_case_result)
|
||||
if executed_case_result.status.value == TestResult.Status.FAILED.value:
|
||||
# 红色
|
||||
self.logger.debug(f"\033[91m ❌ 测试用例 '{tc_class.id}' 执行失败。\033[0m")
|
||||
else:
|
||||
self.logger.debug(f"\033[92m ✅ 测试用例 '{tc_class.id}' 执行成功。\033[0m")
|
||||
self.logger.debug(f"测试用例 '{tc_class.id}' 执行完毕,状态: {executed_case_result.status.value}")
|
||||
|
||||
|
||||
endpoint_test_result.finalize_endpoint_test()
|
||||
self.logger.info(f"端点 '{endpoint_id}' 测试完成,最终状态: {endpoint_test_result.overall_status.value}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user