#!/usr/bin/env python # -*- coding: utf-8 -*- """ 地震体模拟API集成测试 本测试模块验证与地震体模拟API服务器的集成测试, 确保API调用器和JSON Schema验证器能够正确处理地震体数据API。 """ import sys import os import unittest import json import logging import time import subprocess from pathlib import Path import threading # 添加项目根目录到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 # 配置日志 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 TestSeismicAPIIntegration(unittest.TestCase): """地震体模拟API集成测试类""" @classmethod def setUpClass(cls): """在所有测试前启动模拟API服务器""" # 尝试启动模拟服务器 cls.server_process = None cls.server_running = False try: # 检查服务器是否已经在运行 import requests try: response = requests.get(f"{MOCK_API_SERVER}/", timeout=0.5) cls.server_running = True logger.info("模拟API服务器已经在运行") except: # 服务器没有运行,需要启动 logger.info("正在启动模拟API服务器...") # 启动模拟服务器的线程 def run_server(): try: project_root = Path(__file__).resolve().parents[1] process = subprocess.Popen( [sys.executable, '-m', 'tests.run_mock_seismic_api'], cwd=str(project_root), stdout=subprocess.PIPE, stderr=subprocess.PIPE ) cls.server_process = process logger.info("模拟API服务器启动成功!") except Exception as e: logger.error(f"启动模拟API服务器失败: {str(e)}") # 在后台线程中启动服务器 server_thread = threading.Thread(target=run_server) server_thread.daemon = True server_thread.start() # 等待服务器启动 max_retries = 10 retry_count = 0 while retry_count < max_retries: try: time.sleep(1) # 等待一秒 response = requests.get(f"{MOCK_API_SERVER}/", timeout=0.5) cls.server_running = True logger.info("模拟API服务器已启动并可用") break except: retry_count += 1 logger.info(f"等待模拟API服务器启动... ({retry_count}/{max_retries})") if not cls.server_running: logger.warning("无法确认模拟API服务器是否成功启动") except Exception as e: logger.error(f"设置模拟API服务器时出错: {str(e)}") @classmethod def tearDownClass(cls): """在所有测试结束后关闭模拟API服务器""" if cls.server_process: try: cls.server_process.terminate() cls.server_process.wait(timeout=5) logger.info("模拟API服务器已关闭") except Exception as e: logger.error(f"关闭模拟API服务器时出错: {str(e)}") def setUp(self): """测试前的设置""" # 如果服务器未运行,跳过测试 if not self.__class__.server_running: self.skipTest("模拟API服务器未运行") # 创建API调用器 self.api_caller = APICaller( default_timeout=10, default_headers={"Content-Type": "application/json"} ) # 创建JSON Schema验证器 self.schema_validator = JSONSchemaValidator() # 地震体数据的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": "属性体必须的基础地震体标识符" } } } } ] } # API响应的JSON Schema self.api_response_schema = { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "code": {"type": ["string", "integer"]}, "flag": {"type": "boolean"}, "msg": {"type": "string"}, "result": {} # 允许任何类型的结果 } } # 测试数据集 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 } ] } self.attribute_seismic_data = { "projectId": "testPrj1", "surveyId": "20230117135924_2", "seismicName": "振幅属性体", "dsType": 2, "baseSeismicId": "20221113181927_1", # 假设这是有效的基础地震体ID "dimensions": [ { "dimensionNo": 1, "dimensionName": "inline", "serviceMin": 100, "serviceMax": 500, "serviceSpan": 1 }, { "dimensionNo": 2, "dimensionName": "xline", "serviceMin": 200, "serviceMax": 600, "serviceSpan": 1 } ] } self.invalid_seismic_data = { "projectId": "testPrj1", "seismicName": "缺少必填字段", "dsType": 1, # 缺少 surveyId "dimensions": [ { "dimensionNo": 1, "dimensionName": "inline", "serviceMin": 100, "serviceMax": 500, "serviceSpan": 1 } ] } # 存储测试过程中创建的资源ID self.created_resources = [] def test_server_connection(self): """测试与模拟服务器的连接""" import requests try: response = requests.get(f"{MOCK_API_SERVER}/", timeout=2) # 即使状态码是404,但只要能连接到服务器就算成功 logger.info(f"服务器连接测试 - 状态码: {response.status_code}") self.assertTrue(True, "成功连接到模拟API服务器") except Exception as e: self.fail(f"连接到模拟API服务器失败: {str(e)}") def test_add_valid_seismic_file(self): """测试添加有效的地震体文件""" # 先验证数据结构符合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}") # 创建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) # 验证响应状态码 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 ) self.assertTrue(response_validation.is_valid, f"响应验证失败: {response_validation.errors}") # 验证业务逻辑 self.assertTrue(response.json_content.get("flag", False)) self.assertEqual(response.json_content.get("code"), "0") # 保存创建的资源ID result_id = response.json_content.get("result") if result_id: self.created_resources.append(result_id) logger.info(f"创建地震体成功,ID: {result_id}") def test_add_attribute_seismic_file(self): """测试添加属性体文件""" # 先验证数据结构符合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}") # 创建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.assertIsNotNone(response.json_content) # 验证响应符合Schema response_validation = self.schema_validator.validate( response.json_content, self.api_response_schema ) self.assertTrue(response_validation.is_valid, f"响应验证失败: {response_validation.errors}") # 验证业务逻辑 self.assertTrue(response.json_content.get("flag", False)) self.assertEqual(response.json_content.get("code"), "0") # 保存创建的资源ID result_id = response.json_content.get("result") if result_id: self.created_resources.append(result_id) logger.info(f"创建属性体成功,ID: {result_id}") def test_add_invalid_seismic_file(self): """测试添加无效的地震体文件""" # 先验证数据结构不符合Schema validation_result = self.schema_validator.validate( self.invalid_seismic_data, self.seismic_data_schema ) self.assertFalse(validation_result.is_valid, "无效数据居然通过了Schema验证") # 创建API请求 request = APIRequest( method="POST", url=f"{MOCK_API_SERVER}/api/gsc/appmodel/api/v1/seismic/file/add", body=self.invalid_seismic_data ) # 调用API response = self.api_caller.call_api(request) # 验证响应状态码 self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.json_content) # 注意:模拟服务器可能不严格执行验证,此处仅验证API调用能够成功完成 logger.info(f"添加无效地震体返回: {response.json_content}") def test_query_trace_count(self): """测试查询地震体道数""" # 使用预定义的样例ID sample_id = "20221113181927_1" # 创建API请求 request = APIRequest( method="POST", url=f"{MOCK_API_SERVER}/api/gsc/appmodel/api/v1/seismic/traces/count", body={"seismicId": sample_id} ) # 调用API response = self.api_caller.call_api(request) # 验证响应状态码 self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.json_content) # 模拟服务器可能返回不同格式的响应,此处仅检查API调用成功 logger.info(f"查询道数响应: {response.json_content}") # 验证结果包含信息(可能是数字或其他) self.assertIn("result", response.json_content) result = response.json_content.get("result") logger.info(f"地震体 {sample_id} 的道数: {result}") def test_coordinate_conversion(self): """测试坐标转换""" # 使用预定义的样例ID和坐标点 sample_id = "20221113181927_1" test_points = [ [606406.1281141682, 6082083.338731234], [609767.8725899048, 6080336.549935018], [615271.9052119441, 6082017.422172886] ] # 创建API请求 request = APIRequest( method="POST", url=f"{MOCK_API_SERVER}/api/gsc/appmodel/api/v1/seismic/coordinate/geodetic/toline", body={ "seismicId": sample_id, "points": test_points } ) # 调用API response = self.api_caller.call_api(request) # 验证响应状态码 self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.json_content) # 模拟服务器可能有不同的响应格式,记录响应信息 logger.info(f"坐标转换响应: {response.json_content}") # 验证结果包含信息 self.assertIn("result", response.json_content) result = response.json_content.get("result", []) # 记录转换结果 logger.info(f"坐标转换结果: {result}") def test_export_task(self): """测试导出地震体任务""" # 使用预定义的样例ID sample_id = "20221113181927_1" # 创建API请求 request = APIRequest( method="POST", url=f"{MOCK_API_SERVER}/api/gsc/appmodel/api/v1/seismic/export/submit", body={ "seismicId": sample_id, "saveDir": f"/export/{sample_id}.sgy" } ) # 调用API response = self.api_caller.call_api(request) # 验证响应状态码 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 ) self.assertTrue(response_validation.is_valid, f"响应验证失败: {response_validation.errors}") # 验证业务逻辑 self.assertTrue(response.json_content.get("flag", False)) self.assertEqual(response.json_content.get("code"), "0") # 验证结果包含任务ID result = response.json_content.get("result", {}) task_id = None if isinstance(result, dict) and "taskId" in result: task_id = result.get("taskId") else: task_id = result # 某些实现可能直接返回任务ID self.assertIsNotNone(task_id, "任务ID不应为空") logger.info(f"导出任务ID: {task_id}") # 查询任务进度 if task_id: # 等待一小段时间以确保任务已经开始处理 time.sleep(0.5) # 创建API请求 progress_request = APIRequest( method="GET", url=f"{MOCK_API_SERVER}/api/gsc/appmodel/api/v1/seismic/export/progress?taskId={task_id}" ) # 调用API progress_response = self.api_caller.call_api(progress_request) # 验证响应状态码 self.assertEqual(progress_response.status_code, 200) self.assertIsNotNone(progress_response.json_content) # 验证业务逻辑 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')}%") def test_h400_keywords(self): """测试查询地震体卷头关键字""" # 使用预定义的样例ID sample_id = "20221113181927_1" # 创建API请求 request = APIRequest( method="POST", url=f"{MOCK_API_SERVER}/api/gsc/appmodel/api/v1/seismic/h400/keyword/list", body={"seismicId": sample_id} ) # 调用API response = self.api_caller.call_api(request) # 验证响应状态码 self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.json_content) # 记录响应信息 logger.info(f"卷头关键字响应: {response.json_content}") # 验证结果包含关键字列表 self.assertIn("result", response.json_content) keywords = response.json_content.get("result", []) logger.info(f"地震体 {sample_id} 的卷头关键字数量: {len(keywords)}") if isinstance(keywords, list) and keywords: logger.info(f"第一个关键字: {keywords[0]}") if __name__ == "__main__": unittest.main()