增加宽容的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
+6 -1
View File
@@ -2,6 +2,7 @@ from enum import Enum
from typing import Any, Dict, Optional, List, Tuple, Type, Union
import logging
from .utils import schema_utils
from pydantic import BaseModel, Field
class TestSeverity(Enum):
"""测试用例的严重程度"""
@@ -114,7 +115,11 @@ 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], json_schema_validator: Optional[Any] = None, llm_service: Optional[Any] = None):
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):
"""
初始化测试用例。
Args:
+7 -1
View File
@@ -42,6 +42,8 @@ except ImportError:
LLMService = None
logging.getLogger(__name__).info("LLMService 未找到,LLM 相关功能将不可用。")
from ddms_compliance_suite.utils.schema_provider import SchemaProvider
_dynamic_model_cache: Dict[str, Type[BaseModel]] = {}
class ExecutedTestCaseResult:
@@ -510,6 +512,10 @@ class APITestOrchestrator:
logging.info(f"strictness_level: {self.strictness_level}")
elif strictness_level:
logging.warning(f"提供了无效的严格等级 '{strictness_level}'。将使用默认行为。有效值: {', '.join([e.name for e in TestSeverity])}")
# 将这些属性的初始化移到此处,并设为None,避免在_execute_tests_from_parsed_spec之前被错误使用
self.json_schema_validator: Optional[JSONSchemaValidator] = None
self.schema_provider: Optional[SchemaProvider] = None
def get_api_call_details(self) -> List[APICallDetail]:
"""Returns the collected list of API call details."""
@@ -981,7 +987,7 @@ class APITestOrchestrator:
endpoint_spec=endpoint_spec_dict,
global_api_spec=global_spec_dict,
json_schema_validator=self.json_validator,
llm_service=self.llm_service # Pass the orchestrator's LLM service instance
llm_service=self.llm_service
)
self.logger.info(f"开始执行测试用例 '{test_case_instance.id}' ({test_case_instance.name}) for endpoint '{endpoint_spec_dict.get('method', 'N/A')} {endpoint_spec_dict.get('path', 'N/A')}'")
@@ -0,0 +1,38 @@
from typing import Any, List
import logging
logger = logging.getLogger(__name__)
def extract_data_for_validation(response_json: Any) -> List[Any]:
"""
从原始API响应JSON中智能提取需要被验证的核心业务数据列表。
即使只有一个对象,也返回一个单元素的列表。
策略:
1. 如果响应体是包含 'code''data' 的标准包装,则提取 'data' 的内容。
2. 如果处理后的数据是列表,直接返回该列表。
3. 如果处理后的数据是单个对象(字典),将其包装在单元素列表中返回。
4. 如果数据为空或不适用,返回空列表。
"""
if not response_json:
return []
data_to_process = response_json
# 策略 1: 解开标准包装
if isinstance(response_json, dict) and 'code' in response_json and 'data' in response_json:
logger.debug("检测到标准响应包装,提取 'data' 字段内容进行处理。")
data_to_process = response_json['data']
# 策略 2: 统一返回列表
if isinstance(data_to_process, list):
logger.debug(f"数据本身为列表,包含 {len(data_to_process)} 个元素,直接返回。")
return data_to_process
if isinstance(data_to_process, dict):
logger.debug("数据为单个对象,将其包装在列表中返回。")
return [data_to_process]
# 对于其他情况(如数据为None或非对象/列表类型),返回空列表
logger.warning(f"待处理的数据既不是列表也不是对象,无法提取进行验证。数据: {str(data_to_process)[:100]}")
return []
@@ -0,0 +1,60 @@
import logging
import json
from typing import Dict, Any, Optional
class SchemaProvider:
def __init__(self, global_api_spec: Dict[str, Any]):
self.global_api_spec = global_api_spec
self.logger = logging.getLogger(__name__)
def get_schema(self, endpoint_spec: Dict[str, Any], status_code: int) -> Optional[Dict]:
"""
获取端点在特定状态码下的响应Schema。
当前实现:从API规范中查找。
未来可扩展:优先从动态映射中获取,如果失败或未配置,则回退到当前实现。
"""
# --- 预留的动态获取逻辑扩展点 ---
# if self._use_dynamic_provider(endpoint_spec):
# schema = self._fetch_dynamic_schema(endpoint_spec)
# if schema:
# return schema
# ---------------------------------
return self._get_schema_from_spec(endpoint_spec, status_code)
def _get_schema_from_spec(self, endpoint_spec: Dict[str, Any], status_code: int) -> Optional[Dict]:
"""
(私有方法) 从API规范中提取Schema,这是当前版本的主要实现。
"""
self.logger.debug(f"尝试从API规范中为状态码 {status_code} 查找Schema。")
expected_schema = None
# 兼容 OpenAPI/Swagger 格式
if 'responses' in endpoint_spec:
responses = endpoint_spec['responses']
# 优先匹配精确的状态码
if str(status_code) in responses:
response_def = 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')
# 回退到 'default'
elif 'default' in responses:
response_def = responses['default']
if 'content' in response_def and 'application/json' in response_def['content']:
expected_schema = response_def['content']['application/json'].get('schema')
# 兼容 YAPI 格式 (简化)
elif 'res_body_type' in endpoint_spec and endpoint_spec['res_body_type'] == 'json':
if endpoint_spec.get('res_body_is_json_schema') and endpoint_spec.get('res_body'):
try:
expected_schema = json.loads(endpoint_spec['res_body'])
except (json.JSONDecodeError, TypeError):
self.logger.error(f"从YAPI res_body解析JSON schema失败。res_body: {endpoint_spec['res_body']}")
return None # 解析失败
if not expected_schema:
self.logger.info(f"在API规范中未找到针对状态码 {status_code} 的JSON schema。")
return expected_schema