gongwenxin df90a5377f mvp
2025-06-16 14:49:49 +08:00

79 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from ddms_compliance_suite.test_framework_core import BaseAPITestCase, ValidationResult, APIResponseContext, APIRequestContext, TestSeverity
import re
from typing import Dict, Any, List, Optional
from ddms_compliance_suite.utils import schema_utils
class TimeFormatCheckTestCase(BaseAPITestCase):
id = "TC-RESTful-003"
name = "时间字段ISO 8601格式检查"
description = "验证返回的时间字段是否遵循 YYYY-MM-DDTHH:MM:SS+08:00 的ISO 8601格式。此检查为静态检查验证规范中`string`类型且`format`为`date-time`的字段是否包含推荐的`pattern`。"
severity = TestSeverity.MEDIUM
tags = ["normative", "schema", "time-format"]
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)
# 推荐的 pattern
self.recommended_iso_8601_pattern = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}([+-]\d{2}:\d{2}|Z)$'
def validate_response(self, response_context: APIResponseContext, request_context: APIRequestContext) -> List[ValidationResult]:
results = []
# 此检查为静态检查分析API规范中的响应部分
responses = self.endpoint_spec.get('responses', {})
for status_code, response_spec in responses.items():
# 通常只关心成功的响应
if not status_code.startswith('2'):
continue
content = self._get_resolved_schema(response_spec.get('content', {}))
if content:
for media_type, media_spec in content.items():
if 'schema' in media_spec:
self._check_schema_properties(media_spec['schema'], results)
if not results:
return [self.passed("在API规范中未找到可供静态检查的时间相关字段类型为string且格式为date-time")]
return results
def _check_schema_properties(self, schema, results, path=""):
if not schema or not isinstance(schema, dict):
return
# 处理 allOf, oneOf, anyOf
for keyword in ['allOf', 'oneOf', 'anyOf']:
if keyword in schema:
for sub_schema in schema[keyword]:
self._check_schema_properties(sub_schema, results, path)
if 'properties' in schema:
for prop_name, prop_spec in schema['properties'].items():
prop_spec = self._get_resolved_schema(prop_spec)
current_path = f"{path}.{prop_name}" if path else prop_name
# 检查类型为string且格式为date-time的字段
if prop_spec.get('type') == 'string' and prop_spec.get('format') == 'date-time':
pattern = prop_spec.get('pattern')
message = f"时间字段 '{current_path}' (format: date-time) "
if not pattern:
results.append(self.failed(
message + f"缺少建议的 `pattern` ({self.recommended_iso_8601_pattern}) 来强制执行ISO 8601格式。",
details={'field': current_path}
))
elif pattern != self.recommended_iso_8601_pattern:
results.append(self.failed(
message + f"其 `pattern` ('{pattern}') 与建议的模式不完全匹配。",
details={'field': current_path, 'current_pattern': pattern, 'recommended': self.recommended_iso_8601_pattern}
))
else:
results.append(self.passed(message + "已定义了建议的 `pattern` 用于格式校验。"))
# 递归检查
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