mvp
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
# 设置检查测试用例
|
||||
|
||||
本目录包含执行在实际API调用之前的设置检查测试用例。
|
||||
|
||||
## 必需请求头Schema验证
|
||||
|
||||
`required_headers_check.py` 实现了一个测试用例,用于验证API规范中是否包含所有必需的请求头:
|
||||
|
||||
- X-Tenant-ID (也接受 tenant-id 作为变体)
|
||||
- X-Data-Domain (也接受 data-domain 作为变体)
|
||||
- Authorization
|
||||
|
||||
该测试用例不会发送实际的API请求,只会验证API规范的定义是否符合要求。
|
||||
|
||||
### 工作原理
|
||||
|
||||
1. 测试用例检查每个API端点的规范定义
|
||||
2. 验证是否包含所有必需的请求头
|
||||
3. 验证这些请求头是否被标记为必需 (required="1")
|
||||
4. 生成详细的验证结果,包括哪些请求头缺失或未标记为必需
|
||||
|
||||
### 使用方法
|
||||
|
||||
这个测试用例会自动被测试框架发现并应用到所有API端点。由于其`execution_order = 0`设置,它会在其他测试用例之前执行。
|
||||
|
||||
如果发现API规范中缺少必需的请求头,测试会失败并提供详细的错误信息,指出哪些请求头缺失或未标记为必需。
|
||||
|
||||
### 示例结果
|
||||
|
||||
成功情况:
|
||||
```
|
||||
✅ 测试通过: 所有必需的请求头都已正确定义
|
||||
```
|
||||
|
||||
失败情况:
|
||||
```
|
||||
❌ 测试失败: 缺少必需的请求头 X-Data-Domain
|
||||
❌ 测试失败: 请求头 tenant-id 存在但未标记为必需
|
||||
```
|
||||
|
||||
### 注意事项
|
||||
|
||||
1. 此测试用例接受请求头名称的不同变体(如`X-Tenant-ID`和`tenant-id`)
|
||||
2. 如果API规范设计时有意不包含某些请求头,可能需要修改测试用例的`required_headers`配置
|
||||
Binary file not shown.
@@ -1,78 +0,0 @@
|
||||
# from typing import Dict, Any, Optional, List
|
||||
# from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity, ValidationResult, APIRequestContext, APIResponseContext
|
||||
|
||||
# class BasicAPISanityCheckCase(BaseAPITestCase):
|
||||
# id = "TC-FRAMEWORK-SANITY-001"
|
||||
# name = "Basic API Sanity Check"
|
||||
# description = ("Performs a basic API call with default generated data and expects a generally successful "
|
||||
# "response (e.g., 200, 201, 204). If a response schema is defined for success, "
|
||||
# "it also validates the response body against it. "
|
||||
# "If this test case fails, subsequent test cases for this endpoint may be skipped.")
|
||||
# severity = TestSeverity.CRITICAL
|
||||
# tags = ["sanity", "framework-setup"]
|
||||
|
||||
# # This flag indicates to the orchestrator that if this test fails,
|
||||
# # subsequent tests for THIS ENDPOINT should be skipped.
|
||||
# is_critical_setup_test: bool = True
|
||||
# execution_order = 1 # Ensures this runs first for an endpoint
|
||||
|
||||
# # Expected successful HTTP status codes
|
||||
# EXPECTED_SUCCESS_STATUS_CODES: List[int] = [200, 201, 202, 204]
|
||||
|
||||
# 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)
|
||||
# self.target_success_schema: Optional[Dict[str, Any]] = None
|
||||
|
||||
# # Try to find a schema for a successful response (e.g., 200 or 201)
|
||||
# responses_spec = self.endpoint_spec.get("responses", {})
|
||||
# if isinstance(responses_spec, dict):
|
||||
# for status_code_str in map(str, self.EXPECTED_SUCCESS_STATUS_CODES):
|
||||
# if status_code_str in responses_spec:
|
||||
# response_def = responses_spec[status_code_str]
|
||||
# if isinstance(response_def, dict):
|
||||
# content = response_def.get("content", {})
|
||||
# for ct in ["application/json", "application/*+json", "*/*"]:
|
||||
# if ct in content:
|
||||
# media_type_obj = content[ct]
|
||||
# if isinstance(media_type_obj, dict) and isinstance(media_type_obj.get("schema"), dict):
|
||||
# self.target_success_schema = media_type_obj["schema"]
|
||||
# self.logger.info(f"[{self.id}] Found success response schema for status {status_code_str} under content type {ct}.")
|
||||
# break # Found a schema for this content type
|
||||
# if self.target_success_schema:
|
||||
# break # Found a schema for this status code
|
||||
# if not self.target_success_schema:
|
||||
# self.logger.info(f"[{self.id}] No specific success response JSON schema found to validate against for this endpoint.")
|
||||
|
||||
# # No need to override generate_* methods, as we want the default behavior.
|
||||
|
||||
# def validate_response(self, response_context: APIResponseContext, request_context: APIRequestContext) -> List[ValidationResult]:
|
||||
# results = []
|
||||
# status_code = response_context.status_code
|
||||
|
||||
# if status_code in self.EXPECTED_SUCCESS_STATUS_CODES:
|
||||
# msg = f"Basic sanity check: Received expected success status code {status_code}."
|
||||
# results.append(self.passed(msg))
|
||||
|
||||
# # If we have a schema for successful responses, validate the body
|
||||
# if self.target_success_schema:
|
||||
# if response_context.json_content is not None:
|
||||
# results.extend(self.validate_data_against_schema(
|
||||
# data_to_validate=response_context.json_content,
|
||||
# schema_definition=self.target_success_schema,
|
||||
# context_message_prefix="Successful response body"
|
||||
# ))
|
||||
# elif response_context.text_content and not response_context.text_content.strip() and status_code == 204:
|
||||
# # HTTP 204 No Content, body is expected to be empty, so schema validation is not applicable.
|
||||
# results.append(self.passed("Response is 204 No Content, body is correctly empty."))
|
||||
# elif status_code != 204 : # For 200, 201, 202, if schema is present, content is expected
|
||||
# results.append(self.failed(
|
||||
# message="Basic sanity check: Response body is empty or not JSON, but a success schema was defined.",
|
||||
# details={"status_code": status_code, "content_type": response_context.headers.get("Content-Type")}
|
||||
# ))
|
||||
# else:
|
||||
# results.append(self.failed(
|
||||
# message=f"Basic sanity check: Expected a success status code (one of {self.EXPECTED_SUCCESS_STATUS_CODES}), but received {status_code}.",
|
||||
# details={"status_code": status_code, "response_body": response_context.json_content if response_context.json_content else response_context.text_content}
|
||||
# ))
|
||||
|
||||
# return results
|
||||
@@ -0,0 +1,127 @@
|
||||
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity, ValidationResult, APIRequestContext, APIResponseContext
|
||||
import logging
|
||||
import json
|
||||
from typing import Dict, Any, Optional, List
|
||||
|
||||
class RequiredHeadersSchemaCheck(BaseAPITestCase):
|
||||
"""验证API规范中是否包含必需的请求头"""
|
||||
|
||||
# 1. 元数据
|
||||
id = "TC-HEADER-001"
|
||||
name = "必需请求头Schema验证"
|
||||
description = "验证API规范中是否包含必需的请求头(X-Tenant-ID、X-Data-Domain和Authorization)"
|
||||
severity = TestSeverity.CRITICAL
|
||||
tags = ["headers", "schema", "compliance"]
|
||||
execution_order = 0 # 优先执行
|
||||
# is_critical_setup_test = 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=json_schema_validator, llm_service=llm_service)
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
self.logger.info(f"测试用例 {self.id} ({self.name}) 已针对端点 '{self.endpoint_spec.get('method')} {self.endpoint_spec.get('path')}' 初始化。")
|
||||
|
||||
# 定义必需的请求头和可能的命名变体
|
||||
# self.required_headers = {
|
||||
# 'X-Tenant-ID': ['X-Tenant-ID', 'tenant-id', 'X-TENANT-ID', 'TENANT-ID'],
|
||||
# 'X-Data-Domain': ['X-Data-Domain', 'data-domain', 'X-DATA-DOMAIN', 'DATA-DOMAIN'],
|
||||
# 'Authorization': ['Authorization', 'authorization', 'AUTHORIZATION']
|
||||
# }
|
||||
self.required_headers = {
|
||||
'X-Tenant-ID': ['X-Tenant-ID'],
|
||||
'X-Data-Domain': ['X-Data-Domain'],
|
||||
'Authorization': ['Authorization']
|
||||
}
|
||||
|
||||
def pre_request(self, request_context: APIRequestContext) -> APIRequestContext:
|
||||
"""这个测试用例不需要发送实际请求"""
|
||||
# 设置一个标志,表示不需要发送请求
|
||||
self._skip_request = True
|
||||
return request_context
|
||||
|
||||
def validate_response(self, response_context: APIResponseContext, request_context: APIRequestContext) -> List[ValidationResult]:
|
||||
"""验证API规范中是否包含所有必需的请求头"""
|
||||
results = []
|
||||
|
||||
# 从parameters数组中获取header类型的参数
|
||||
parameters = self.endpoint_spec.get('parameters', [])
|
||||
header_params = [p for p in parameters if p.get('in') == 'header']
|
||||
|
||||
# 记录调试信息
|
||||
self.logger.info(f"API端点: {self.endpoint_spec.get('method')} {self.endpoint_spec.get('path')}")
|
||||
self.logger.info(f"发现的header参数: {json.dumps(header_params, ensure_ascii=False)}")
|
||||
|
||||
# 记录找到的请求头,方便调试
|
||||
found_headers = {}
|
||||
|
||||
# 检查每个必需的请求头
|
||||
for header_key, possible_names in self.required_headers.items():
|
||||
header_found = False
|
||||
required_found = False
|
||||
found_name = None
|
||||
|
||||
# 检查是否存在任何变体的请求头名称
|
||||
for name in possible_names:
|
||||
for header in header_params:
|
||||
header_name = header.get('name', '')
|
||||
if header_name.lower() == name.lower():
|
||||
header_found = True
|
||||
found_name = header_name
|
||||
# 检查是否被标记为必需
|
||||
if header.get('required') is True:
|
||||
required_found = True
|
||||
break
|
||||
if header_found:
|
||||
break
|
||||
|
||||
found_headers[header_key] = {
|
||||
'found': header_found,
|
||||
'required': required_found,
|
||||
'name': found_name
|
||||
}
|
||||
|
||||
# 记录检查结果
|
||||
self.logger.info(f"检查请求头 {header_key}: 找到={header_found}, 必需={required_found}, 名称={found_name}")
|
||||
|
||||
# 根据检查结果添加验证结果
|
||||
if not header_found:
|
||||
results.append(
|
||||
ValidationResult(
|
||||
passed=False,
|
||||
message=f"缺少必需的请求头 {header_key}",
|
||||
details={
|
||||
'header': header_key,
|
||||
'possible_names': possible_names,
|
||||
'endpoint': f"{self.endpoint_spec.get('method')} {self.endpoint_spec.get('path')}"
|
||||
}
|
||||
)
|
||||
)
|
||||
self.logger.warning(f"规范验证失败: 缺少必需的请求头 {header_key}")
|
||||
elif not required_found:
|
||||
results.append(
|
||||
ValidationResult(
|
||||
passed=False,
|
||||
message=f"请求头 {found_name} 存在但未标记为必需",
|
||||
details={
|
||||
'header': header_key,
|
||||
'found_name': found_name,
|
||||
'endpoint': f"{self.endpoint_spec.get('method')} {self.endpoint_spec.get('path')}"
|
||||
}
|
||||
)
|
||||
)
|
||||
self.logger.warning(f"规范验证失败: 请求头 {found_name} 存在但未标记为必需")
|
||||
|
||||
# 如果没有失败的验证结果,添加一个成功的结果
|
||||
if not [r for r in results if not r.passed]:
|
||||
results.append(
|
||||
ValidationResult(
|
||||
passed=True,
|
||||
message=f"所有必需的请求头都已正确定义",
|
||||
details={
|
||||
'found_headers': found_headers,
|
||||
'endpoint': f"{self.endpoint_spec.get('method')} {self.endpoint_spec.get('path')}"
|
||||
}
|
||||
)
|
||||
)
|
||||
self.logger.info(f"规范验证通过: 所有必需的请求头都已正确定义")
|
||||
|
||||
return results
|
||||
Reference in New Issue
Block a user