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

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()