compliance/tests/test_seismic_schema_validation.py
2025-05-16 15:18:02 +08:00

517 lines
20 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.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
地震体数据Schema验证和API集成测试
测试地震体数据的Schema验证和API调用的集成
确保Schema验证器和API调用器能够协同工作正确处理地震体数据。
"""
import sys
import os
import unittest
import json
import logging
from pathlib import Path
from unittest import mock
# 添加项目根目录到Python路径
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from ddms_compliance_suite.api_caller.caller import APICaller, APIRequest, APIResponse
from ddms_compliance_suite.json_schema_validator.validator import JSONSchemaValidator
from ddms_compliance_suite.rule_repository.repository import RuleRepository
from ddms_compliance_suite.models.config_models import RuleRepositoryConfig
from ddms_compliance_suite.models.rule_models import TargetType
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 模拟API服务器
MOCK_API_SERVER = "http://localhost:5001"
class MockResponse:
"""模拟API响应对象"""
def __init__(self, status_code, data=None, content=None, headers=None, elapsed_time=0.1):
self.status_code = status_code
self.data = data
self.content = content if content is not None else (json.dumps(data).encode() if data else b'')
self.headers = headers or {"Content-Type": "application/json"}
self.elapsed = mock.Mock()
self.elapsed.total_seconds.return_value = elapsed_time
def json(self):
"""获取JSON内容"""
if isinstance(self.data, dict):
return self.data
raise ValueError("Response does not contain valid JSON")
class TestSeismicSchemaValidation(unittest.TestCase):
"""地震体数据Schema验证和API集成测试类"""
def setUp(self):
"""测试前的设置"""
# 创建规则仓库
config = RuleRepositoryConfig(
storage={"type": "filesystem", "path": "./rules"},
preload_rules=True
)
self.rule_repository = RuleRepository(config)
# 创建JSON Schema验证器
self.schema_validator = JSONSchemaValidator()
# 创建API调用器
self.api_caller = APICaller(
default_timeout=10,
default_headers={"Content-Type": "application/json"}
)
# 从规则仓库加载Schema
self.seismic_data_schema = self.rule_repository.get_schema_for_target(
TargetType.DATA_OBJECT, "Seismic"
)
if not self.seismic_data_schema:
self.fail("无法加载地震体数据Schema请确保rules/json_schemas/seismic-data-schema目录中存在有效的Schema文件")
# 从规则仓库加载API响应Schema
self.api_response_schema = self.rule_repository.get_schema_for_target(
TargetType.API_RESPONSE, "SeismicAPIResponse"
)
if not self.api_response_schema:
logger.warning("无法加载API响应Schema将使用默认定义")
# 默认的API响应Schema
self.api_response_schema = {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["code", "flag", "msg", "result"],
"properties": {
"code": {"type": ["string", "integer"]},
"flag": {"type": "boolean"},
"msg": {"type": "string"},
"result": {"type": ["string", "null", "object"]}
}
}
# 测试数据集
self.valid_seismic_data = {
"projectId": "testPrj1",
"surveyId": "20230117135924_2",
"seismicName": "西部地震体-01",
"dsType": 1,
"dimensions": [
{
"dimensionNo": 1,
"dimensionName": "inline",
"serviceMin": 100,
"serviceMax": 500,
"serviceSpan": 1
},
{
"dimensionNo": 2,
"dimensionName": "xline",
"serviceMin": 200,
"serviceMax": 600,
"serviceSpan": 1
},
{
"dimensionNo": 3,
"dimensionName": "slice",
"serviceMin": 3500,
"serviceMax": 3600,
"serviceSpan": 4
}
],
"sampleRate": 2.0,
"dataRange": {
"min": -10000,
"max": 10000
},
"coordinates": [
[116.3833, 39.9167],
[116.4024, 39.9008],
[116.4100, 39.9200]
]
}
self.attribute_seismic_data = {
"projectId": "testPrj1",
"surveyId": "20230117135924_2",
"seismicName": "振幅属性体",
"dsType": 2,
"baseSeismicId": "20221113181927_1",
"dimensions": [
{
"dimensionNo": 1,
"dimensionName": "inline",
"serviceMin": 100,
"serviceMax": 500,
"serviceSpan": 1
},
{
"dimensionNo": 2,
"dimensionName": "xline",
"serviceMin": 200,
"serviceMax": 600,
"serviceSpan": 1
}
],
"sampleRate": 2.0,
"dataRange": {
"min": 0,
"max": 255
}
}
self.invalid_dimension_data = {
"projectId": "testPrj1",
"surveyId": "20230117135924_2",
"seismicName": "错误维度地震体",
"dsType": 1,
"dimensions": [
{
"dimensionNo": 1,
"dimensionName": "inline",
"serviceMin": 500,
"serviceMax": 100, # 最小值大于最大值,应该报错
"serviceSpan": 1
}
]
}
self.duplicate_dimension_data = {
"projectId": "testPrj1",
"surveyId": "20230117135924_2",
"seismicName": "重复维度地震体",
"dsType": 1,
"dimensions": [
{
"dimensionNo": 1,
"dimensionName": "inline",
"serviceMin": 100,
"serviceMax": 500,
"serviceSpan": 1
},
{
"dimensionNo": 1, # 重复的维度编号
"dimensionName": "xline",
"serviceMin": 200,
"serviceMax": 600,
"serviceSpan": 1
}
]
}
self.missing_required_data = {
"projectId": "testPrj1",
"seismicName": "缺少必填字段",
"dsType": 1, # 缺少 surveyId
"dimensions": [
{
"dimensionNo": 1,
"dimensionName": "inline",
"serviceMin": 100,
"serviceMax": 500,
"serviceSpan": 1
}
]
}
self.missing_baseid_data = {
"projectId": "testPrj1",
"surveyId": "20230117135924_2",
"seismicName": "缺少基础ID的属性体",
"dsType": 2, # 属性体类型,但缺少 baseSeismicId
"dimensions": [
{
"dimensionNo": 1,
"dimensionName": "inline",
"serviceMin": 100,
"serviceMax": 500,
"serviceSpan": 1
}
]
}
# 模拟API响应
self.mock_success_response = {
"code": "0",
"flag": True,
"msg": "操作成功",
"result": "20230601123456_1"
}
self.mock_error_response = {
"code": "1",
"flag": False,
"msg": "操作失败:缺少必填字段",
"result": None
}
def test_schema_loaded_from_file(self):
"""测试从文件加载Schema"""
self.assertIsNotNone(self.seismic_data_schema, "地震体数据Schema未加载")
self.assertIsInstance(self.seismic_data_schema, dict, "地震体数据Schema不是字典类型")
self.assertIn("properties", self.seismic_data_schema, "地震体数据Schema缺少properties字段")
self.assertIn("dimensions", self.seismic_data_schema["properties"], "地震体数据Schema缺少dimensions属性")
logger.info("成功从文件加载地震体数据Schema")
self.assertIsNotNone(self.api_response_schema, "API响应Schema未加载")
self.assertIsInstance(self.api_response_schema, dict, "API响应Schema不是字典类型")
self.assertIn("properties", self.api_response_schema, "API响应Schema缺少properties字段")
self.assertIn("code", self.api_response_schema["properties"], "API响应Schema缺少code属性")
logger.info("成功加载API响应Schema")
@mock.patch('ddms_compliance_suite.api_caller.caller.requests.request')
def test_valid_seismic_data_validation(self, mock_request):
"""测试验证有效的地震体数据"""
# 使用Schema验证器验证数据
validation_result = self.schema_validator.validate(
self.valid_seismic_data,
self.seismic_data_schema
)
# 断言验证结果
self.assertTrue(validation_result.is_valid, f"验证失败: {validation_result.errors}")
self.assertEqual(len(validation_result.errors), 0)
self.assertEqual(len(validation_result.warnings), 0)
# 配置模拟请求
mock_response = MockResponse(200, self.mock_success_response)
mock_request.return_value = mock_response
# 创建API请求
request = APIRequest(
method="POST",
url=f"{MOCK_API_SERVER}/api/gsc/appmodel/api/v1/seismic/file/add",
body=self.valid_seismic_data
)
# 调用API
response = self.api_caller.call_api(request)
# 验证模拟方法被调用
mock_request.assert_called_once()
# 验证响应
self.assertEqual(response.status_code, 200)
self.assertIsNotNone(response.json_content)
# 验证响应符合Schema
response_validation = self.schema_validator.validate(
response.json_content,
self.api_response_schema
)
# 断言API响应验证
self.assertTrue(response_validation.is_valid, f"API响应验证失败: {response_validation.errors}")
self.assertEqual(response.json_content["code"], "0")
self.assertTrue(response.json_content["flag"])
@mock.patch('ddms_compliance_suite.api_caller.caller.requests.request')
def test_attribute_seismic_data_validation(self, mock_request):
"""测试验证属性体数据"""
# 使用Schema验证器验证数据
validation_result = self.schema_validator.validate(
self.attribute_seismic_data,
self.seismic_data_schema
)
# 断言验证结果
self.assertTrue(validation_result.is_valid, f"验证失败: {validation_result.errors}")
self.assertEqual(len(validation_result.errors), 0)
# 配置模拟请求
mock_response = MockResponse(200, self.mock_success_response)
mock_request.return_value = mock_response
# 创建API请求
request = APIRequest(
method="POST",
url=f"{MOCK_API_SERVER}/api/gsc/appmodel/api/v1/seismic/file/add",
body=self.attribute_seismic_data
)
# 调用API
response = self.api_caller.call_api(request)
# 验证响应
self.assertEqual(response.status_code, 200)
self.assertTrue(response.json_content["flag"])
def test_invalid_dimension_validation(self):
"""测试维度参数无效的验证"""
# 使用Schema验证器验证数据
validation_result = self.schema_validator.validate(
self.invalid_dimension_data,
self.seismic_data_schema
)
# 基本Schema验证不会检查 serviceMin < serviceMax这需要额外验证逻辑
# 因此这个测试仅检查基本结构符合Schema
self.assertTrue(validation_result.is_valid)
# 所以在这里我们可以补充一个额外的业务逻辑验证
for dimension in self.invalid_dimension_data["dimensions"]:
if dimension["serviceMin"] > dimension["serviceMax"]:
# 在实际代码中,这应该添加到验证结果的错误列表中
logger.error(f"维度 {dimension['dimensionName']} 的最小值({dimension['serviceMin']})大于最大值({dimension['serviceMax']})")
validation_result.is_valid = False
validation_result.errors.append(f"维度{dimension['dimensionNo']}的最小值不能大于最大值")
# 断言最终的验证结果
self.assertFalse(validation_result.is_valid)
self.assertGreater(len(validation_result.errors), 0)
def test_duplicate_dimension_validation(self):
"""测试重复维度编号的验证"""
# 使用Schema验证器验证数据
validation_result = self.schema_validator.validate(
self.duplicate_dimension_data,
self.seismic_data_schema
)
# 基本Schema验证不会检查重复的dimensionNo这需要额外验证逻辑
self.assertTrue(validation_result.is_valid)
# 实现额外的检查
dimension_nos = [dim["dimensionNo"] for dim in self.duplicate_dimension_data["dimensions"]]
if len(dimension_nos) != len(set(dimension_nos)):
# 在实际代码中,这应该添加到验证结果的错误列表中
logger.error(f"存在重复的维度编号: {dimension_nos}")
validation_result.is_valid = False
validation_result.errors.append("维度编号不能重复")
# 断言最终的验证结果
self.assertFalse(validation_result.is_valid)
self.assertGreater(len(validation_result.errors), 0)
def test_missing_required_field_validation(self):
"""测试缺少必填字段的验证"""
# 使用Schema验证器验证数据
validation_result = self.schema_validator.validate(
self.missing_required_data,
self.seismic_data_schema
)
# 断言验证结果
self.assertFalse(validation_result.is_valid)
self.assertGreater(len(validation_result.errors), 0)
# 检查错误消息是否包含缺少的字段
has_missing_field_error = any("surveyId" in error for error in validation_result.errors)
self.assertTrue(has_missing_field_error, f"错误消息中没有包含缺少的surveyId字段: {validation_result.errors}")
def test_missing_baseid_for_attribute_validation(self):
"""测试缺少基础地震体ID的属性体验证"""
# 使用Schema验证器验证数据
validation_result = self.schema_validator.validate(
self.missing_baseid_data,
self.seismic_data_schema
)
# 断言验证结果
self.assertFalse(validation_result.is_valid)
self.assertGreater(len(validation_result.errors), 0)
# 检查错误消息是否包含缺少的基础地震体ID
has_missing_baseid_error = any("baseSeismicId" in error for error in validation_result.errors)
self.assertTrue(has_missing_baseid_error, f"错误消息中没有包含缺少的baseSeismicId: {validation_result.errors}")
@mock.patch('ddms_compliance_suite.api_caller.caller.requests.request')
def test_api_error_response_validation(self, mock_request):
"""测试API错误响应的验证"""
# 配置模拟请求返回错误响应
mock_response = MockResponse(200, self.mock_error_response)
mock_request.return_value = mock_response
# 创建API请求 - 使用缺少必填字段的数据
request = APIRequest(
method="POST",
url=f"{MOCK_API_SERVER}/api/gsc/appmodel/api/v1/seismic/file/add",
body=self.missing_required_data
)
# 调用API
response = self.api_caller.call_api(request)
# 验证模拟方法被调用
mock_request.assert_called_once()
# 验证响应
self.assertEqual(response.status_code, 200)
self.assertIsNotNone(response.json_content)
# 验证响应符合Schema但标志为失败
response_validation = self.schema_validator.validate(
response.json_content,
self.api_response_schema
)
# 断言API响应验证
self.assertTrue(response_validation.is_valid, f"API响应验证失败: {response_validation.errors}")
self.assertEqual(response.json_content["code"], "1")
self.assertFalse(response.json_content["flag"])
@mock.patch('ddms_compliance_suite.api_caller.caller.requests.request')
def test_schema_and_api_integration(self, mock_request):
"""测试Schema验证和API调用的完整集成"""
test_cases = [
(self.valid_seismic_data, True, self.mock_success_response),
(self.attribute_seismic_data, True, self.mock_success_response),
(self.missing_required_data, False, self.mock_error_response),
(self.missing_baseid_data, False, self.mock_error_response)
]
for seismic_data, expected_valid, api_response in test_cases:
# 1. 验证Schema
validation_result = self.schema_validator.validate(
seismic_data,
self.seismic_data_schema
)
# 断言Schema验证结果符合预期
self.assertEqual(validation_result.is_valid, expected_valid,
f"Schema验证结果与预期不符: {seismic_data.get('seismicName', 'Unknown')}")
# 2. 如果Schema验证通过调用API
if validation_result.is_valid:
# 配置模拟请求
mock_response = MockResponse(200, api_response)
mock_request.return_value = mock_response
# 创建API请求
request = APIRequest(
method="POST",
url=f"{MOCK_API_SERVER}/api/gsc/appmodel/api/v1/seismic/file/add",
body=seismic_data
)
# 调用API
response = self.api_caller.call_api(request)
# 验证响应
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json_content["flag"], api_response["flag"])
# 验证响应符合Schema
response_validation = self.schema_validator.validate(
response.json_content,
self.api_response_schema
)
# 断言API响应验证
self.assertTrue(response_validation.is_valid)
if __name__ == "__main__":
unittest.main()