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

521 lines
20 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 服务端口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()