#!/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()