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