增加宽容的schema验证

This commit is contained in:
gongwenxin
2025-06-27 19:46:53 +08:00
parent f003fbbbd1
commit 39effa9461
36 changed files with 24456 additions and 68425 deletions
@@ -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