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

566 lines
21 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 -*-
"""
地震体模拟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()