#!/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()