增加宽容的schema验证
This commit is contained in:
BIN
Binary file not shown.
BIN
Binary file not shown.
+81
@@ -0,0 +1,81 @@
|
||||
from typing import Dict, Any, List, Optional
|
||||
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity, ValidationResult, APIRequestContext
|
||||
from ddms_compliance_suite.utils.response_utils import extract_data_for_validation
|
||||
from ddms_compliance_suite.utils.schema_provider import SchemaProvider
|
||||
import logging
|
||||
|
||||
class FlexibleSchemaValidationCase(BaseAPITestCase):
|
||||
"""
|
||||
一个灵活的Schema验证测试用例,能够处理非标准的响应结构和动态的Schema来源。
|
||||
"""
|
||||
id = "TC-CORE-FUNC-002"
|
||||
name = "灵活的返回体JSON Schema验证"
|
||||
description = (
|
||||
"验证API响应体是否符合预期的JSON Schema。此用例能够智能处理被包装的响应(如{code, data}),"
|
||||
"并支持从列表响应中验证每个元素。它依赖于SchemaProvider获取schema,并设计为处理需要动态获取schema的场景。"
|
||||
)
|
||||
severity = TestSeverity.CRITICAL
|
||||
tags = ["core-functionality", "schema-validation", "flexible"]
|
||||
execution_order = 110 # 略高于标准Schema验证,以便在适用时优先执行
|
||||
|
||||
def __init__(self, endpoint_spec: Dict[str, Any], global_api_spec: Dict[str, Any], json_schema_validator: Optional[Any] = None, llm_service: Optional[Any] = None):
|
||||
super().__init__(endpoint_spec, global_api_spec, json_schema_validator, llm_service)
|
||||
# We need to initialize the schema_provider here, as it's no longer injected.
|
||||
self.schema_provider = SchemaProvider(global_api_spec) if global_api_spec else None
|
||||
self.logger.info(f"测试用例 '{self.id}' 已为端点 '{self.endpoint_spec.get('method')} {self.endpoint_spec.get('path')}' 初始化。")
|
||||
|
||||
|
||||
def execute(self, request_context: APIRequestContext) -> List[ValidationResult]:
|
||||
"""
|
||||
执行灵活的schema验证。
|
||||
"""
|
||||
results = []
|
||||
response_context = self.api_caller.call_api(request_context)
|
||||
|
||||
if not response_context:
|
||||
return [self.failed("API调用失败,无法获取响应进行验证。")]
|
||||
|
||||
# 1. 使用 SchemaProvider 获取 Schema
|
||||
if not self.schema_provider:
|
||||
return [self.failed("SchemaProvider 未被初始化,无法执行此测试用例。")]
|
||||
|
||||
expected_schema = self.schema_provider.get_schema(self.endpoint_spec, response_context.status_code)
|
||||
|
||||
if not expected_schema:
|
||||
# 如果是成功响应但找不到schema,这可能是一个问题
|
||||
if 200 <= response_context.status_code < 300:
|
||||
return [self.failed(f"成功响应(状态码 {response_context.status_code}),但无法为其找到或生成JSON Schema。")]
|
||||
else:
|
||||
return [self.passed(f"非成功响应(状态码 {response_context.status_code})且未定义Schema,跳过验证。")]
|
||||
|
||||
# 2. 使用 response_utils 提取待验证的数据列表
|
||||
if not response_context.json_content:
|
||||
return [self.failed(f"响应内容不是有效的JSON格式,无法进行Schema验证。响应文本: {response_context.text_content[:200]}...")]
|
||||
|
||||
data_to_validate_list = extract_data_for_validation(response_context.json_content)
|
||||
|
||||
if not data_to_validate_list:
|
||||
# extract_data_for_validation 在找不到数据或遇到空列表时返回空列表
|
||||
return [self.passed("未从响应中提取到需要验证的数据项(可能为空列表),跳过验证。")]
|
||||
|
||||
# 3. 遍历列表,对每个数据项进行验证
|
||||
all_items_passed = True
|
||||
for i, item in enumerate(data_to_validate_list):
|
||||
item_context_prefix = f"响应列表中的第 {i+1} 个元素"
|
||||
validation_results = self.validate_data_against_schema(
|
||||
data_to_validate=item,
|
||||
schema_definition=expected_schema,
|
||||
context_message_prefix=item_context_prefix
|
||||
)
|
||||
|
||||
for res in validation_results:
|
||||
if not res.passed:
|
||||
all_items_passed = False
|
||||
# 为错误信息添加更多上下文
|
||||
res.message = f"{item_context_prefix} {res.message}"
|
||||
results.append(res)
|
||||
|
||||
if all_items_passed:
|
||||
results.append(self.passed(f"成功验证了响应中的 {len(data_to_validate_list)} 个数据项,均符合Schema。"))
|
||||
|
||||
return results
|
||||
@@ -1,6 +1,7 @@
|
||||
from typing import Dict, Any, Optional, List
|
||||
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity, ValidationResult, APIRequestContext, APIResponseContext
|
||||
import json
|
||||
from ddms_compliance_suite.utils.schema_provider import SchemaProvider
|
||||
|
||||
class ResponseSchemaValidationCase(BaseAPITestCase):
|
||||
id = "TC-CORE-FUNC-001"
|
||||
@@ -10,11 +11,10 @@ class ResponseSchemaValidationCase(BaseAPITestCase):
|
||||
tags = ["core-functionality", "schema-validation", "output-format"]
|
||||
execution_order = 100 # Default, can be adjusted
|
||||
|
||||
# This test is generally applicable, especially for GET requests or successful POST/PUT.
|
||||
# It might need refinement based on specific endpoint characteristics (e.g., no response body for DELETE)
|
||||
|
||||
def __init__(self, endpoint_spec: Dict[str, Any], global_api_spec: Dict[str, Any], json_schema_validator: Optional[Any] = None, llm_service: Optional[Any] = None):
|
||||
super().__init__(endpoint_spec, global_api_spec, json_schema_validator, llm_service=llm_service)
|
||||
self.schema_provider = SchemaProvider(global_api_spec) if global_api_spec else None
|
||||
self.logger.info(f"测试用例 '{self.id}' 已为端点 '{self.endpoint_spec.get('method')} {self.endpoint_spec.get('path')}' 初始化。")
|
||||
|
||||
def validate_response(self, response_context: APIResponseContext, request_context: APIRequestContext) -> List[ValidationResult]:
|
||||
@@ -22,37 +22,13 @@ class ResponseSchemaValidationCase(BaseAPITestCase):
|
||||
method = request_context.method.upper()
|
||||
status_code = response_context.status_code
|
||||
|
||||
# Determine the expected response schema based on method and status code
|
||||
# This logic might need to be more sophisticated depending on how schemas are structured in your API spec (YAPI/Swagger)
|
||||
expected_schema = None
|
||||
response_spec_key = None
|
||||
|
||||
if 'responses' in self.endpoint_spec: # OpenAPI/Swagger style
|
||||
if str(status_code) in self.endpoint_spec['responses']:
|
||||
response_def = self.endpoint_spec['responses'][str(status_code)]
|
||||
if 'content' in response_def and 'application/json' in response_def['content']:
|
||||
expected_schema = response_def['content']['application/json'].get('schema')
|
||||
response_spec_key = f"responses.{status_code}.content.application/json.schema"
|
||||
elif 'default' in self.endpoint_spec['responses']: # Fallback to default response
|
||||
response_def = self.endpoint_spec['responses']['default']
|
||||
if 'content' in response_def and 'application/json' in response_def['content']:
|
||||
expected_schema = response_def['content']['application/json'].get('schema')
|
||||
response_spec_key = f"responses.default.content.application/json.schema"
|
||||
elif 'res_body_type' in self.endpoint_spec and self.endpoint_spec['res_body_type'] == 'json': # YAPI style (simplified)
|
||||
if 'res_body_is_json_schema' in self.endpoint_spec and self.endpoint_spec['res_body_is_json_schema']:
|
||||
if self.endpoint_spec.get('res_body'):
|
||||
try:
|
||||
# YAPI often stores schema as a JSON string
|
||||
expected_schema = json.loads(self.endpoint_spec['res_body'])
|
||||
response_spec_key = "res_body (从JSON字符串解析)"
|
||||
except json.JSONDecodeError as e:
|
||||
self.logger.error(f"从YAPI res_body解析JSON schema失败: {e}")
|
||||
results.append(self.failed(f"无法从YAPI规范解析响应schema: {e}"))
|
||||
return results
|
||||
if not self.schema_provider:
|
||||
return [self.failed("SchemaProvider 未被初始化,无法执行此测试用例。")]
|
||||
|
||||
# Only proceed with schema validation if we have a schema and a JSON response body
|
||||
expected_schema = self.schema_provider.get_schema(self.endpoint_spec, status_code)
|
||||
|
||||
if expected_schema and response_context.json_content is not None:
|
||||
self.logger.info(f"将根据路径 '{response_spec_key or '未知位置'}' 的schema验证响应体。")
|
||||
self.logger.info(f"将根据从API规范中获取的schema验证响应体。")
|
||||
schema_validation_results = self.validate_data_against_schema(
|
||||
data_to_validate=response_context.json_content,
|
||||
schema_definition=expected_schema,
|
||||
@@ -60,22 +36,18 @@ class ResponseSchemaValidationCase(BaseAPITestCase):
|
||||
)
|
||||
results.extend(schema_validation_results)
|
||||
elif response_context.json_content is None and method not in ["DELETE", "HEAD", "OPTIONS"] and status_code in [200, 201, 202]:
|
||||
# If we expected a JSON body (e.g. for successful GET/POST) but got none
|
||||
if expected_schema: # and if a schema was defined
|
||||
if expected_schema:
|
||||
results.append(self.failed(
|
||||
message=f"根据schema期望一个JSON响应体,但未收到可解析的JSON内容。",
|
||||
details={"status_code": status_code, "response_text_sample": (response_context.text_content or "")[:200]}
|
||||
))
|
||||
self.logger.warning(f"期望 {method} {request_context.url} 返回JSON响应体,但未收到或非JSON格式。")
|
||||
elif not expected_schema and response_context.json_content is not None and status_code // 100 == 2:
|
||||
# If there is a JSON body but no schema was found for successful responses
|
||||
self.logger.info(f"响应包含JSON体,但在API规范中未找到针对状态码 {status_code} 的JSON schema。跳过schema验证。")
|
||||
# Optionally, add an informational validation result:
|
||||
# results.append(ValidationResult(passed=True, message="Response has JSON body, but no schema defined for validation.", details={"status_code": status_code}))
|
||||
elif not expected_schema and response_context.json_content is None:
|
||||
self.logger.info(f"状态码 {status_code} 的响应无JSON体也无定义的schema。跳过schema验证。")
|
||||
|
||||
if not results: # If no specific validation was added (e.g. schema not found but not an error)
|
||||
results.append(self.passed("Schema验证步骤完成(未发现问题,或schema不适用/未为此响应定义)。"))
|
||||
if not results:
|
||||
results.append(self.passed("标准Schema验证步骤完成(未发现问题,或schema不适用/未为此响应定义)。"))
|
||||
|
||||
return results
|
||||
Reference in New Issue
Block a user