443 lines
16 KiB
Python
443 lines
16 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
API 调用器和 JSON Schema 验证器的集成测试
|
|
|
|
本测试模块验证 API 调用器获取的数据是否能够被 JSON Schema 验证器正确验证,
|
|
测试两个组件能否协同工作。
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import unittest
|
|
import json
|
|
from unittest import mock
|
|
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
|
|
|
|
class MockResponse:
|
|
"""Mock 类用于模拟 requests 的响应"""
|
|
def __init__(self, json_data, status_code, headers=None, content=None, elapsed_seconds=0.1):
|
|
self.json_data = json_data
|
|
self.status_code = status_code
|
|
self.headers = headers or {"Content-Type": "application/json"}
|
|
self.content = content or json.dumps(json_data).encode('utf-8')
|
|
self.elapsed = mock.Mock()
|
|
self.elapsed.total_seconds.return_value = elapsed_seconds
|
|
|
|
def json(self):
|
|
return self.json_data
|
|
|
|
def raise_for_status(self):
|
|
if self.status_code >= 400:
|
|
from requests.exceptions import HTTPError
|
|
raise HTTPError(f"HTTP Error: {self.status_code}")
|
|
|
|
class TestAPISchemaIntegration(unittest.TestCase):
|
|
"""API 调用器和 JSON Schema 验证器的集成测试类"""
|
|
|
|
def setUp(self):
|
|
"""测试前的设置"""
|
|
# 创建 API 调用器实例
|
|
self.api_caller = APICaller(
|
|
default_timeout=30,
|
|
default_headers={"X-Test": "test-value"}
|
|
)
|
|
|
|
# 创建 JSON Schema 验证器实例
|
|
self.schema_validator = JSONSchemaValidator()
|
|
|
|
# 井数据的 JSON Schema
|
|
self.well_schema = {
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"type": "object",
|
|
"required": ["wellName", "wellID", "status", "coordinates"],
|
|
"properties": {
|
|
"wellName": {
|
|
"type": "string",
|
|
"description": "Well name"
|
|
},
|
|
"wellID": {
|
|
"type": "string",
|
|
"pattern": "^W[0-9]{10}$",
|
|
"description": "Well unique identifier, must be W followed by 10 digits"
|
|
},
|
|
"status": {
|
|
"type": "string",
|
|
"enum": ["active", "inactive", "abandoned", "suspended"],
|
|
"description": "Current well status"
|
|
},
|
|
"coordinates": {
|
|
"type": "object",
|
|
"required": ["longitude", "latitude"],
|
|
"properties": {
|
|
"longitude": {
|
|
"type": "number",
|
|
"minimum": -180,
|
|
"maximum": 180,
|
|
"description": "Longitude coordinate"
|
|
},
|
|
"latitude": {
|
|
"type": "number",
|
|
"minimum": -90,
|
|
"maximum": 90,
|
|
"description": "Latitude coordinate"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# 地震数据的 JSON Schema
|
|
self.seismic_schema = {
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"type": "object",
|
|
"required": ["projectId", "surveyId", "seismicName", "dimensions"],
|
|
"properties": {
|
|
"projectId": {
|
|
"type": "string",
|
|
"description": "Project identifier"
|
|
},
|
|
"surveyId": {
|
|
"type": "string",
|
|
"description": "Survey identifier"
|
|
},
|
|
"seismicName": {
|
|
"type": "string",
|
|
"description": "Seismic volume name"
|
|
},
|
|
"dsType": {
|
|
"type": "integer",
|
|
"enum": [1, 2],
|
|
"description": "Dataset type: 1 for base seismic, 2 for attribute body"
|
|
},
|
|
"dimensions": {
|
|
"type": "array",
|
|
"minItems": 1,
|
|
"items": {
|
|
"type": "object",
|
|
"required": ["dimensionNo", "dimensionName", "serviceMin", "serviceMax"],
|
|
"properties": {
|
|
"dimensionNo": {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"description": "Dimension number"
|
|
},
|
|
"dimensionName": {
|
|
"type": "string",
|
|
"description": "Dimension name"
|
|
},
|
|
"serviceMin": {
|
|
"type": "integer",
|
|
"description": "Minimum value"
|
|
},
|
|
"serviceMax": {
|
|
"type": "integer",
|
|
"description": "Maximum value"
|
|
},
|
|
"serviceSpan": {
|
|
"type": "integer",
|
|
"description": "Sample interval"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"allOf": [
|
|
{
|
|
"if": {
|
|
"properties": { "dsType": { "enum": [2] } },
|
|
"required": ["dsType"]
|
|
},
|
|
"then": {
|
|
"required": ["baseSeismicId"],
|
|
"properties": {
|
|
"baseSeismicId": {
|
|
"type": "string",
|
|
"description": "Base seismic identifier required for attribute bodies"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
# 创建 Schema 规则
|
|
self.well_schema_rule = JSONSchemaDefinition(
|
|
id="well-data-schema",
|
|
name="Well Data Schema",
|
|
description="Defines JSON structure for well data",
|
|
category=RuleCategory.JSON_SCHEMA,
|
|
version="1.0.0",
|
|
target_type=TargetType.DATA_OBJECT,
|
|
target_identifier="Well",
|
|
schema_content=self.well_schema
|
|
)
|
|
|
|
self.seismic_schema_rule = JSONSchemaDefinition(
|
|
id="seismic-data-schema",
|
|
name="Seismic Data Schema",
|
|
description="Defines JSON structure for seismic data",
|
|
category=RuleCategory.JSON_SCHEMA,
|
|
version="1.0.0",
|
|
target_type=TargetType.DATA_OBJECT,
|
|
target_identifier="Seismic",
|
|
schema_content=self.seismic_schema
|
|
)
|
|
|
|
@mock.patch('requests.request')
|
|
def test_valid_well_data_integration(self, mock_request):
|
|
"""测试有效井数据的 API 调用和 Schema 验证集成"""
|
|
# 设置 mock 返回值
|
|
valid_well_data = {
|
|
"wellName": "Test Well-01",
|
|
"wellID": "W0123456789",
|
|
"status": "active",
|
|
"coordinates": {
|
|
"longitude": 116.3833,
|
|
"latitude": 39.9167
|
|
},
|
|
"depth": 3500,
|
|
"operator": "TestCorp"
|
|
}
|
|
|
|
mock_response = MockResponse(
|
|
json_data=valid_well_data,
|
|
status_code=200
|
|
)
|
|
mock_request.return_value = mock_response
|
|
|
|
# 创建 API 请求
|
|
request = APIRequest(
|
|
method="GET",
|
|
url="https://api.example.com/wells/W0123456789",
|
|
headers={"Accept": "application/json"}
|
|
)
|
|
|
|
# 调用 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.well_schema)
|
|
|
|
# 断言
|
|
self.assertTrue(validation_result.is_valid)
|
|
self.assertEqual(len(validation_result.errors), 0)
|
|
|
|
# 使用规则对象进行验证
|
|
rule_validation_result = self.schema_validator.validate_with_rule(response.json_content, self.well_schema_rule)
|
|
self.assertTrue(rule_validation_result.is_valid)
|
|
|
|
@mock.patch('requests.request')
|
|
def test_invalid_well_data_integration(self, mock_request):
|
|
"""测试无效井数据的 API 调用和 Schema 验证集成"""
|
|
# 设置 mock 返回值 - 缺少必填字段 status
|
|
invalid_well_data = {
|
|
"wellName": "Test Well-02",
|
|
"wellID": "W0123456789",
|
|
# 缺少 status
|
|
"coordinates": {
|
|
"longitude": 116.3833,
|
|
"latitude": 39.9167
|
|
}
|
|
}
|
|
|
|
mock_response = MockResponse(
|
|
json_data=invalid_well_data,
|
|
status_code=200
|
|
)
|
|
mock_request.return_value = mock_response
|
|
|
|
# 创建 API 请求
|
|
request = APIRequest(
|
|
method="GET",
|
|
url="https://api.example.com/wells/W0123456789",
|
|
headers={"Accept": "application/json"}
|
|
)
|
|
|
|
# 调用 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.well_schema)
|
|
|
|
# 断言
|
|
self.assertFalse(validation_result.is_valid)
|
|
self.assertTrue(any("status" in error for error in validation_result.errors))
|
|
|
|
# 使用规则对象进行验证
|
|
rule_validation_result = self.schema_validator.validate_with_rule(response.json_content, self.well_schema_rule)
|
|
self.assertFalse(rule_validation_result.is_valid)
|
|
|
|
@mock.patch('requests.request')
|
|
def test_valid_seismic_data_integration(self, mock_request):
|
|
"""测试有效地震数据的 API 调用和 Schema 验证集成"""
|
|
# 设置 mock 返回值
|
|
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
|
|
}
|
|
],
|
|
"sampleRate": 2.0
|
|
}
|
|
|
|
mock_response = MockResponse(
|
|
json_data=valid_seismic_data,
|
|
status_code=200
|
|
)
|
|
mock_request.return_value = mock_response
|
|
|
|
# 创建 API 请求
|
|
request = APIRequest(
|
|
method="GET",
|
|
url="https://api.example.com/seismic/20230117135924_2",
|
|
headers={"Accept": "application/json"}
|
|
)
|
|
|
|
# 调用 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.seismic_schema)
|
|
|
|
# 断言
|
|
self.assertTrue(validation_result.is_valid)
|
|
self.assertEqual(len(validation_result.errors), 0)
|
|
|
|
# 使用规则对象进行验证
|
|
rule_validation_result = self.schema_validator.validate_with_rule(response.json_content, self.seismic_schema_rule)
|
|
self.assertTrue(rule_validation_result.is_valid)
|
|
|
|
@mock.patch('requests.request')
|
|
def test_invalid_seismic_data_integration(self, mock_request):
|
|
"""测试无效地震数据的 API 调用和 Schema 验证集成"""
|
|
# 设置 mock 返回值 - 属性体缺少 baseSeismicId
|
|
invalid_seismic_data = {
|
|
"projectId": "testPrj1",
|
|
"surveyId": "20230117135924_2",
|
|
"seismicName": "西部地震体-02",
|
|
"dsType": 2, # dsType 为 2 时需要 baseSeismicId
|
|
# 缺少 baseSeismicId
|
|
"dimensions": [
|
|
{
|
|
"dimensionNo": 1,
|
|
"dimensionName": "inline",
|
|
"serviceMin": 100,
|
|
"serviceMax": 500,
|
|
"serviceSpan": 1
|
|
}
|
|
]
|
|
}
|
|
|
|
mock_response = MockResponse(
|
|
json_data=invalid_seismic_data,
|
|
status_code=200
|
|
)
|
|
mock_request.return_value = mock_response
|
|
|
|
# 创建 API 请求
|
|
request = APIRequest(
|
|
method="GET",
|
|
url="https://api.example.com/seismic/20230117135924_2",
|
|
headers={"Accept": "application/json"}
|
|
)
|
|
|
|
# 调用 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.seismic_schema)
|
|
|
|
# 断言
|
|
self.assertFalse(validation_result.is_valid)
|
|
# 验证错误信息包含 baseSeismicId
|
|
self.assertTrue(any("baseSeismicId" in error for error in validation_result.errors))
|
|
|
|
# 使用规则对象进行验证
|
|
rule_validation_result = self.schema_validator.validate_with_rule(response.json_content, self.seismic_schema_rule)
|
|
self.assertFalse(rule_validation_result.is_valid)
|
|
|
|
@mock.patch('requests.request')
|
|
def test_api_error_with_validation_integration(self, mock_request):
|
|
"""测试 API 调用错误时的集成流程"""
|
|
# 设置 mock 抛出异常
|
|
from requests.exceptions import HTTPError
|
|
mock_response = mock.Mock()
|
|
mock_response.status_code = 404
|
|
mock_response.headers = {"Content-Type": "application/json"}
|
|
mock_response.content = b"Not Found"
|
|
|
|
http_error = HTTPError("404 Client Error")
|
|
http_error.response = mock_response
|
|
mock_request.side_effect = http_error
|
|
|
|
# 创建 API 请求
|
|
request = APIRequest(
|
|
method="GET",
|
|
url="https://api.example.com/wells/nonexistent",
|
|
headers={"Accept": "application/json"}
|
|
)
|
|
|
|
# 调用 API
|
|
response = self.api_caller.call_api(request)
|
|
|
|
# 验证 API 调用失败
|
|
self.assertEqual(response.status_code, 404)
|
|
self.assertIsNone(response.json_content)
|
|
|
|
# 尝试验证空数据
|
|
validation_result = self.schema_validator.validate(response.json_content, self.well_schema)
|
|
|
|
# 断言
|
|
self.assertFalse(validation_result.is_valid)
|
|
# 数据为空或无效时应该有相应的错误消息
|
|
self.assertTrue(len(validation_result.errors) > 0)
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main() |