llm 通过json列表判断

This commit is contained in:
gongwenxin
2025-06-19 18:33:03 +08:00
parent d3333d4f90
commit ba175cb2ae
32 changed files with 12241 additions and 8427 deletions
@@ -0,0 +1,139 @@
from typing import Dict, Any, List, Optional
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, ValidationResult, APIResponseContext, APIRequestContext, TestSeverity
class ResponseSchemaFormatCheck(BaseAPITestCase):
"""
检查API响应的schema格式是否符合{"code":int or number or string,"message":"","data": any}的标准格式
"""
id = "TC-DMS-CORE-SCHEMA-001"
name = "DMS核心存储服务API响应格式检查"
description = "验证API响应的schema是否符合标准格式:{'code':int or number or string, 'message':string, 'data': any}"
severity = TestSeverity.HIGH
tags = ["schema", "format", "dms-core", "response"]
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)
def validate_response(self, response_context: APIResponseContext, request_context: APIRequestContext) -> List[ValidationResult]:
"""
仅验证API响应schema的格式,不验证实际响应
"""
results = []
# 获取规范中的响应schema
status_codes_to_check = ["200", "201", "default"]
for status_code in status_codes_to_check:
response_schema = self._get_response_schema_for_status(status_code)
if response_schema:
validation_results = self._validate_response_schema_format(response_schema, status_code)
results.extend(validation_results)
# 找到一个有效的响应schema后就不再继续检查
break
# 如果没有找到任何响应schema,记录为失败
if not results:
results.append(self.failed(
message="无法找到API响应的schema定义,无法验证响应格式。",
details={"endpoint": self.endpoint_spec.get("path", "未知")}
))
return results
def _get_response_schema_for_status(self, status_code: str) -> Optional[Dict[str, Any]]:
"""获取指定状态码的响应schema"""
responses = self.endpoint_spec.get("responses", {})
if status_code not in responses:
return None
response_spec = responses[status_code]
return self._get_resolved_response_schema(response_spec, status_code)
def _validate_response_schema_format(self, schema: Dict[str, Any], status_code: str) -> List[ValidationResult]:
"""验证响应schema是否符合标准格式"""
results = []
# 如果schema为空,则记录为失败
if not schema or not isinstance(schema, dict):
results.append(self.failed(
message=f"响应schema不是有效的对象: {schema}",
details={"status_code": status_code}
))
return results
# 解析schema,确保它是一个已解析的schema
schema = self._get_resolved_schema(schema)
# 检查schema是否有properties
if "properties" not in schema:
results.append(self.failed(
message=f"响应schema中缺少'properties'定义",
details={"status_code": status_code, "schema": schema}
))
return results
properties = schema.get("properties", {})
required_fields = schema.get("required", [])
# 检查必须的字段: code, message, data
expected_fields = ["code", "message", "data"]
missing_fields = [field for field in expected_fields if field not in properties]
if missing_fields:
results.append(self.failed(
message=f"响应schema中缺少必要字段: {', '.join(missing_fields)}",
details={
"status_code": status_code,
"available_fields": list(properties.keys()),
"missing_fields": missing_fields
}
))
else:
# 检查字段类型
type_errors = []
# 检查code字段类型
code_schema = properties.get("code", {})
if not( code_schema.get("type") == "integer" or code_schema.get("type") == "number" or code_schema.get("type") == "string"):
type_errors.append(f"'code'字段应为integer类型,实际为{code_schema.get('type')}")
# 检查message字段类型
message_schema = properties.get("message", {})
if message_schema.get("type") != "string":
type_errors.append(f"'message'字段应为string类型,实际为{message_schema.get('type')}")
# # 检查data字段类型
data_schema = properties.get("data", {})
# if data_schema.get("type") != "object" and data_schema.get("type") is not None:
# type_errors.append(f"'data'字段应为object类型,实际为{data_schema.get('type')}")
if type_errors:
results.append(self.failed(
message=f"响应schema中字段类型不符合要求: {'; '.join(type_errors)}",
details={
"status_code": status_code,
"code_schema": code_schema,
"message_schema": message_schema,
"data_schema": data_schema
}
))
# 检查必填字段
for field in expected_fields:
if field not in required_fields:
results.append(ValidationResult(
passed=True, # 作为警告而非错误
message=f"字段'{field}'在schema中未标记为必填(required)",
details={"status_code": status_code, "required_fields": required_fields}
))
# 如果没有错误,则记录为通过
if not any(not result.passed for result in results):
results.append(self.passed(
message="响应schema符合标准格式: {'code':int or number or string, 'message':string, 'data': any}",
details={"status_code": status_code}
))
return results
@@ -0,0 +1,128 @@
import re
from typing import Dict, Any, List, Optional
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, ValidationResult, APIResponseContext, APIRequestContext, TestSeverity
class URLVersionCheckCase(BaseAPITestCase):
"""
检查API URL是否包含版本号(如v1, api/v2, v3.0等)并以/api开头
"""
id = "TC-DMS-URL-VERSION-001"
name = "DMS API URL版本号检查"
description = "检查API URL是否包含标准格式的版本号,支持的格式包括:v1, api/v2, v3.0, version/1, 1.0等,并且路径需要以/api开头"
severity = TestSeverity.MEDIUM
tags = ["url", "version", "dms-core", "api-design"]
# 这个测试用例不需要发送实际请求
skip_execution = True
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)
def validate_response(self, response_context: APIResponseContext, request_context: APIRequestContext) -> List[ValidationResult]:
"""
检查API URL是否以/api开头并包含版本号
"""
results = []
# 获取API路径
path = self.endpoint_spec.get('path', '')
if not path:
results.append(self.failed(
message="无法获取API路径",
details={"endpoint_spec_keys": list(self.endpoint_spec.keys())}
))
return results
# 检查是否是系统级API(可能不需要遵循标准路径格式)
is_system_api = re.match(r'^/(health|ping|status|metrics|system)(/|$)', path)
# 1. 检查路径是否以/api开头
starts_with_api = path.startswith('/api/')
if not starts_with_api and not is_system_api:
results.append(self.failed(
message=f"API路径 '{path}' 不是以'/api/'开头",
details={"full_path": path, "requirement": "路径必须以'/api/'开头"}
))
elif starts_with_api:
results.append(self.passed(
message=f"API路径 '{path}' 正确以'/api/'开头",
details={"full_path": path}
))
# 2. 检查路径中是否包含版本号
version_patterns = [
# 标准版本格式: /v1/, /v2/, /v3/ 等
r'/v\d+/',
# 带小数点的版本: /v1.0/, /v2.1/ 等
r'/v\d+\.\d+/',
# 使用 'version' 单词: /version/1/, /version/2/ 等
r'/version/\d+/',
# API前缀版本: /api/v1/, /api/v2/ 等
r'/api/v\d+/',
# 直接数字版本: /1/, /2/ (仅在特定位置)
r'/api/\d+/',
# 特殊格式: 如 /v1-beta/, /v2-alpha/ 等
r'/v\d+[\-_](alpha|beta|rc\d*)/',
# 年份版本: /2023/, /2024/ 等 (仅在特定位置)
r'/20\d{2}/',
]
# 检查是否包含版本号
matched_pattern = None
version_str = None
for pattern in version_patterns:
match = re.search(pattern, path)
if match:
matched_pattern = pattern
version_str = match.group(0).strip('/')
break
if matched_pattern and version_str:
results.append(self.passed(
message=f"API路径 '{path}' 包含版本标识: '{version_str}'",
details={
"pattern_matched": matched_pattern,
"version_string": version_str,
"full_path": path
}
))
else:
# 特殊情况:检查是否是根API或系统级API(可能不需要版本号)
if is_system_api:
results.append(self.passed(
message=f"API路径 '{path}' 是系统级API,不需要版本号",
details={"full_path": path, "api_type": "system"}
))
else:
results.append(self.failed(
message=f"API路径 '{path}' 不包含任何已知格式的版本标识",
details={
"full_path": path,
"supported_patterns": [p.replace('\\d+', 'N').replace('\\d{2}', 'NN') for p in version_patterns]
}
))
# 提供改进建议
# 确保建议路径始终以/api开头并包含版本号
base_path_parts = path.split('/')
base_path_parts = [p for p in base_path_parts if p] # 移除空字符串
if not starts_with_api:
# 如果不是以/api开头,建议路径应该是/api/v1/原始路径
suggested_path = f"/api/v1/{'/'.join(base_path_parts)}"
else:
# 如果已经以/api开头但缺少版本号,插入v1在api之后
suggested_path = "/api/v1"
if len(base_path_parts) > 1: # 有api后面的部分
suggested_path += f"/{'/'.join(base_path_parts[1:])}"
results.append(ValidationResult(
passed=False,
message=f"建议将路径修改为符合规范的格式,例如: '{suggested_path}'",
details={"original_path": path, "suggested_path": suggested_path}
))
return results
@@ -71,9 +71,4 @@ class TimeFormatCheckTestCase(BaseAPITestCase):
if 'properties' in prop_spec or 'allOf' in prop_spec or 'oneOf' in prop_spec or 'anyOf' in prop_spec:
self._check_schema_properties(prop_spec, results, current_path)
elif prop_spec.get('type') == 'array' and 'items' in prop_spec:
self._check_schema_properties(self._get_resolved_schema(prop_spec['items']), results, f"{current_path}[]")
def _get_resolved_schema(self, schema_or_ref):
if '$ref' in schema_or_ref:
return schema_utils.util_resolve_ref(schema_or_ref['$ref'], self.global_api_spec)
return schema_or_ref
self._check_schema_properties(self._get_resolved_schema(prop_spec['items']), results, f"{current_path}[]")
@@ -0,0 +1,141 @@
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, ValidationResult, APIResponseContext, APIRequestContext, TestSeverity
from typing import Dict, Any, List, Optional
import re
class PaginationParamsCheckTestCase(BaseAPITestCase):
"""
检查API请求中是否包含标准分页参数:pageNo、pageSize和isSearchCount
只有名称含有"查询"一类,并且不含有"详情"一类的API才应用这个验证
"""
id = "TC-DMS-PAGINATION-001"
name = "分页参数检查"
description = "检查API请求参数中是否包含标准分页参数:pageNo、pageSize和isSearchCount。只有名称含有'查询''列表'等并且不含有'详情'一类的API才应用此验证。"
severity = TestSeverity.MEDIUM
tags = ["pagination", "params", "backend-guide"]
# 这个测试用例不需要发送实际请求
skip_execution = True
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)
# 定义需要验证的API名称关键词
self.include_keywords = ["查询", "列表", "分页", "page", "list", "query", "search", "find"]
# 定义排除的API名称关键词
self.exclude_keywords = ["详情", "明细", "detail", "info", "get", "查看"]
def validate_response(self, response_context: APIResponseContext, request_context: APIRequestContext) -> List[ValidationResult]:
"""
检查API请求中是否包含标准分页参数
"""
results = []
# 获取API路径和方法
path = self.endpoint_spec.get('path', '')
method = self.endpoint_spec.get('method', '').lower()
# 获取API的摘要和描述信息
summary = self.endpoint_spec.get('summary', '')
description = self.endpoint_spec.get('description', '')
operation_id = self.endpoint_spec.get('operationId', '')
# 组合所有可能包含API名称或功能描述的字段
api_description_text = f"{summary} {description} {operation_id} {path}".lower()
# 检查是否包含需要验证的关键词,且不包含排除的关键词
contains_include_keyword = any(keyword.lower() in api_description_text for keyword in self.include_keywords)
contains_exclude_keyword = any(keyword.lower() in api_description_text for keyword in self.exclude_keywords)
# 如果不满足准入规则,直接返回通过
if not contains_include_keyword or contains_exclude_keyword:
results.append(self.passed(
message=f"跳过检查:API不符合分页参数检查的准入规则(需包含'查询'/'列表'等关键词,且不包含'详情'等关键词)",
details={
"path": path,
"method": method.upper(),
"summary": summary,
"contains_include_keyword": contains_include_keyword,
"contains_exclude_keyword": contains_exclude_keyword
}
))
return results
# 如果是GET请求或可能返回列表的请求,才进行检查
if method not in ['get', 'post']:
results.append(self.passed(
message=f"跳过检查:{method.upper()} 方法,不适用于分页参数检查"
))
return results
# 初始化检查结果
found_page_no = False
found_page_size = False
found_is_search_count = False
# 检查查询参数
parameters = self.endpoint_spec.get('parameters', [])
for param in parameters:
param_name = param.get('name', '')
param_in = param.get('in', '')
if param_in == 'query':
if param_name == 'pageNo':
found_page_no = True
elif param_name == 'pageSize':
found_page_size = True
elif param_name == 'isSearchCount':
found_is_search_count = True
# 检查请求体(如果是POST请求)
if method == 'post':
request_body = self.endpoint_spec.get('requestBody', {})
content = request_body.get('content', {})
for media_type, media_content in content.items():
if 'schema' in media_content:
schema = self._get_resolved_schema(media_content['schema'])
if 'properties' in schema:
properties = schema['properties']
# 检查请求体属性
if 'pageNo' in properties:
found_page_no = True
if 'pageSize' in properties:
found_page_size = True
if 'isSearchCount' in properties:
found_is_search_count = True
# 汇总检查结果
if found_page_no and found_page_size and found_is_search_count:
results.append(self.passed(
message=f"API请求包含所有标准分页参数:pageNo、pageSize和isSearchCount",
details={"path": path, "method": method.upper()}
))
else:
# 计算缺失的参数
missing_params = []
if not found_page_no:
missing_params.append("pageNo")
if not found_page_size:
missing_params.append("pageSize")
if not found_is_search_count:
missing_params.append("isSearchCount")
if missing_params:
results.append(self.failed(
message=f"API请求缺少标准分页参数:{', '.join(missing_params)}",
details={
"path": path,
"method": method.upper(),
"missing_params": missing_params,
"found_params": {
"pageNo": found_page_no,
"pageSize": found_page_size,
"isSearchCount": found_is_search_count
}
}
))
return results