566 lines
21 KiB
Python
566 lines
21 KiB
Python
#!/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() |