#!/usr/bin/env python # -*- coding: utf-8 -*- """ 实时地震体 API 集成测试 本测试模块验证连接到真实运行的地震体 API 服务(端口5001), 使用 API 调用器和 JSON Schema 验证器对其响应进行验证。 """ import sys import os import unittest import json import logging import requests from pathlib import Path # 添加项目根目录到 Python 路径 sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from ddms_compliance_suite.api_caller.caller import APICaller, APIRequest from ddms_compliance_suite.json_schema_validator.validator import JSONSchemaValidator from ddms_compliance_suite.models.rule_models import JSONSchemaDefinition, RuleCategory, TargetType # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) # API 服务器地址 LIVE_API_SERVER = "http://localhost:5001" class TestLiveSeismicAPI(unittest.TestCase): """实时地震体 API 集成测试类""" @classmethod def setUpClass(cls): """测试类开始前检查API服务器是否可用""" cls.server_available = False try: # 使用简单的健康检查请求来测试服务器是否运行 response = requests.get(f"{LIVE_API_SERVER}/", timeout=2) # 模拟服务器可能返回404,但只要能连接就说明服务器在运行 cls.server_available = True logger.info("API服务器可用,将执行集成测试") except (requests.ConnectionError, requests.Timeout): logger.warning(f"无法连接到API服务器 {LIVE_API_SERVER},集成测试将被跳过") cls.server_available = False def setUp(self): """测试前的设置""" # 如果服务器不可用,跳过所有测试 if not self.__class__.server_available: self.skipTest(f"API服务器 {LIVE_API_SERVER} 不可用") # 创建 API 调用器实例 self.api_caller = APICaller( default_timeout=30, default_headers={"Content-Type": "application/json"} ) # 创建 JSON Schema 验证器实例 self.schema_validator = JSONSchemaValidator() # 用于添加新地震体和验证响应的 JSON Schema self.add_seismic_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"]} # 允许字符串、空或对象类型 } } # 地震体数据的 JSON Schema self.seismic_data_schema = { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "required": ["projectId", "surveyId", "seismicName", "dsType", "dimensions"], "properties": { "projectId": { "type": "string", "description": "项目ID" }, "surveyId": { "type": "string", "description": "测量ID" }, "seismicName": { "type": "string", "description": "地震体名称" }, "dsType": { "type": "integer", "enum": [1, 2], "description": "数据集类型: 1 为基础地震体, 2 为属性体" }, "dimensions": { "type": "array", "minItems": 1, "items": { "type": "object", "required": ["dimensionNo", "dimensionName", "serviceMin", "serviceMax"], "properties": { "dimensionNo": { "type": "integer", "minimum": 1, "description": "维度编号" }, "dimensionName": { "type": "string", "description": "维度名称" }, "serviceMin": { "type": "integer", "description": "最小值" }, "serviceMax": { "type": "integer", "description": "最大值" }, "serviceSpan": { "type": "integer", "description": "采样间隔" } } } } }, "allOf": [ { "if": { "properties": { "dsType": { "enum": [2] } }, "required": ["dsType"] }, "then": { "required": ["baseSeismicId"], "properties": { "baseSeismicId": { "type": "string", "description": "属性体必须的基础地震体标识符" } } } } ] } # 创建一个新的地震体ID存储 self.created_seismic_ids = [] def test_0_health_check(self): """测试服务器健康状态""" # 此测试主要用于确认服务器是否正常运行 # 即使返回404,只要能连接就视为服务器可用 try: response = requests.get(f"{LIVE_API_SERVER}/health", timeout=2) logger.info(f"服务器健康检查响应: {response.status_code}") self.assertTrue(True, "服务器可用") except (requests.ConnectionError, requests.Timeout) as e: self.fail(f"无法连接到API服务器: {str(e)}") def test_1_add_valid_seismic_file(self): """测试添加有效的地震体文件""" # 创建有效的地震体数据 valid_seismic_data = { "projectId": "testPrj1", "surveyId": "20230117135924_2", "seismicName": "有效地震体", "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 } ] } # 验证请求数据满足架构 request_validation = self.schema_validator.validate( valid_seismic_data, self.seismic_data_schema ) self.assertTrue(request_validation.is_valid, f"请求数据不符合架构: {request_validation.errors}") # 创建 API 请求 request = APIRequest( method="POST", url=f"{LIVE_API_SERVER}/api/gsc/appmodel/api/v1/seismic/file/add", body=valid_seismic_data ) try: # 调用 API response = self.api_caller.call_api(request) # 验证 API 调用结果 self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.json_content) # 验证响应符合 Schema validation_result = self.schema_validator.validate( response.json_content, self.add_seismic_schema ) # 断言 self.assertTrue(validation_result.is_valid, f"响应数据不符合架构: {validation_result.errors}") self.assertTrue(response.json_content.get("flag", False)) # 保存创建的地震体ID,用于后续测试 seismic_id = response.json_content.get("result") if seismic_id: self.created_seismic_ids.append(seismic_id) logger.info(f"创建了地震体ID: {seismic_id}") except Exception as e: logger.error(f"测试添加有效地震体文件失败: {str(e)}") raise def test_2_add_attribute_body(self): """测试添加属性体""" # 确保有至少一个地震体ID可用于测试 # 获取已知存在的样例地震体 sample_seismic_id = "20221113181927_1" # 创建属性体数据 attribute_seismic_data = { "projectId": "testPrj1", "surveyId": "20230117135924_2", "seismicName": "测试属性体", "dsType": 2, # dsType 为 2 表示属性体 "baseSeismicId": sample_seismic_id, # 引用已知存在的基础地震体ID "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 } ] } # 验证请求数据满足架构 request_validation = self.schema_validator.validate( attribute_seismic_data, self.seismic_data_schema ) self.assertTrue(request_validation.is_valid, f"请求数据不符合架构: {request_validation.errors}") # 创建 API 请求 request = APIRequest( method="POST", url=f"{LIVE_API_SERVER}/api/gsc/appmodel/api/v1/seismic/file/add", body=attribute_seismic_data ) try: # 调用 API response = self.api_caller.call_api(request) # 验证 API 调用结果 self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.json_content) # 验证响应符合 Schema validation_result = self.schema_validator.validate( response.json_content, self.add_seismic_schema ) # 断言 self.assertTrue(validation_result.is_valid, f"响应数据不符合架构: {validation_result.errors}") self.assertTrue(response.json_content.get("flag", False)) # 保存创建的地震体ID,用于后续测试 seismic_id = response.json_content.get("result") if seismic_id: self.created_seismic_ids.append(seismic_id) logger.info(f"创建了属性体ID: {seismic_id}") except Exception as e: logger.error(f"测试添加属性体失败: {str(e)}") raise def test_3_trace_count(self): """测试查询地震体总道数""" # 使用已知存在的样例地震体ID sample_seismic_id = "20221113181927_1" # 创建查询道数的请求 request = APIRequest( method="POST", url=f"{LIVE_API_SERVER}/api/gsc/appmodel/api/v1/seismic/traces/count", body={"seismicId": sample_seismic_id} ) try: # 调用 API response = self.api_caller.call_api(request) # 验证 API 调用结果 self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.json_content) # 验证响应格式 self.assertIn("result", response.json_content) self.assertIn("code", response.json_content) self.assertIn("msg", response.json_content) # 即使道数为0也是有效响应 logger.info(f"地震体 {sample_seismic_id} 的道数: {response.json_content.get('result')}") except Exception as e: logger.error(f"测试查询地震体总道数失败: {str(e)}") raise def test_4_export_task(self): """测试导出地震体任务提交""" # 使用已知存在的样例地震体ID sample_seismic_id = "20221113181927_1" # 创建提交导出任务的请求 request = APIRequest( method="POST", url=f"{LIVE_API_SERVER}/api/gsc/appmodel/api/v1/seismic/export/submit", body={ "seismicId": sample_seismic_id, "saveDir": f"/export/{sample_seismic_id}.sgy" } ) try: # 调用 API response = self.api_caller.call_api(request) # 验证 API 调用结果 self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.json_content) # 验证响应 self.assertEqual(response.json_content.get("code"), "0") self.assertTrue(response.json_content.get("flag", False)) self.assertIn("result", response.json_content) # 获取任务ID task_id = None result = response.json_content.get("result") if isinstance(result, dict) and "taskId" in result: task_id = result.get("taskId") self.assertIsNotNone(task_id, "未能获取导出任务ID") logger.info(f"创建了导出任务ID: {task_id}") # 检查导出任务进度 if task_id: # 创建查询进度的请求 progress_request = APIRequest( method="GET", url=f"{LIVE_API_SERVER}/api/gsc/appmodel/api/v1/seismic/export/progress?taskId={task_id}" ) # 调用 API progress_response = self.api_caller.call_api(progress_request) # 验证 API 调用结果 self.assertEqual(progress_response.status_code, 200) self.assertIsNotNone(progress_response.json_content) # 验证响应 self.assertEqual(progress_response.json_content.get("code"), "0") self.assertTrue(progress_response.json_content.get("flag", False)) progress_result = progress_response.json_content.get("result", {}) self.assertIn("status", progress_result) self.assertIn("progress", progress_result) logger.info(f"导出任务 {task_id} 状态: {progress_result.get('status')}, 进度: {progress_result.get('progress')}%") except Exception as e: logger.error(f"测试导出地震体任务提交失败: {str(e)}") raise def test_5_h3200_header(self): """测试查询地震体3200头""" # 使用已知存在的样例地震体ID sample_seismic_id = "20221113181927_1" # 创建查询3200头的请求 request = APIRequest( method="POST", url=f"{LIVE_API_SERVER}/api/gsc/appmodel/api/v1/seismic/head/h3200", body={"seismicId": sample_seismic_id} ) try: # 调用 API response = self.api_caller.call_api(request) # 验证 API 调用结果 - 成功返回二进制数据 self.assertEqual(response.status_code, 200) # 不是JSON响应,应该是二进制数据 self.assertIsNotNone(response.content) logger.info(f"地震体 {sample_seismic_id} 的3200头数据长度: {len(response.content)}") except Exception as e: logger.error(f"测试查询地震体3200头失败: {str(e)}") raise def test_6_h400_keywords(self): """测试查询地震体卷头关键字信息""" # 使用已知存在的样例地震体ID sample_seismic_id = "20221113181927_1" # 创建查询卷头关键字的请求 request = APIRequest( method="POST", url=f"{LIVE_API_SERVER}/api/gsc/appmodel/api/v1/seismic/h400/keyword/list", body={"seismicId": sample_seismic_id} ) try: # 调用 API response = self.api_caller.call_api(request) # 验证 API 调用结果 self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.json_content) # 验证响应 self.assertTrue(response.json_content.get("flag", False)) # 验证响应中包含关键字列表 self.assertIn("result", response.json_content) logger.info(f"地震体 {sample_seismic_id} 的卷头关键字数量: {len(response.json_content.get('result', []))}") except Exception as e: logger.error(f"测试查询地震体卷头关键字信息失败: {str(e)}") raise def test_7_coordinate_conversion(self): """测试查询地震体点线坐标""" # 使用已知存在的样例地震体ID sample_seismic_id = "20221113181927_1" # 测试点坐标 test_points = [ [606406.1281141682, 6082083.338731234], # 模拟服务器样例中的一个坐标 [609767.8725899048, 6080336.549935018], [615271.9052119441, 6082017.422172886] ] # 创建坐标转换请求 request = APIRequest( method="POST", url=f"{LIVE_API_SERVER}/api/gsc/appmodel/api/v1/seismic/coordinate/geodetic/toline", body={ "seismicId": sample_seismic_id, "points": test_points } ) try: # 调用 API response = self.api_caller.call_api(request) # 验证 API 调用结果 self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.json_content) # 验证响应 self.assertEqual(response.json_content.get("code"), 0) self.assertIn("result", response.json_content) # 验证返回的结果是否与测试点一一对应 result = response.json_content.get("result", []) self.assertEqual(len(result), len(test_points)) # 记录转换结果 for i, (point, line_point) in enumerate(zip(test_points, result)): logger.info(f"坐标点 {i+1}: {point} → 线点: {line_point}") except Exception as e: logger.error(f"测试查询地震体点线坐标失败: {str(e)}") raise if __name__ == "__main__": unittest.main()