""" JSON 安全解析功能组件的单元测试 """ import pytest import json from src.components.functions.json_utils import ( JsonSafeParseFunction, JSONParseError, safe_json_parse, batch_json_parse, validate_json_structure ) class TestJsonSafeParseFunction: """测试 JsonSafeParseFunction 类""" def setup_method(self): """设置测试方法""" self.parser = JsonSafeParseFunction() def test_execute_basic(self): """测试基本 JSON 解析""" # 测试简单对象 json_str = '{"name": "Alice", "age": 30}' result = self.parser.execute(json_str) assert result == {"name": "Alice", "age": 30} # 测试数组 json_str = '[1, 2, 3, "test"]' result = self.parser.execute(json_str) assert result == [1, 2, 3, "test"] def test_execute_wrapped_json(self): """测试包装的 JSON 解析""" # 测试 ```json``` 包装 json_str = '```json\n{"key": "value"}\n```' result = self.parser.execute(json_str) assert result == {"key": "value"} # 测试 ``` 包装 json_str = '```\n{"key": "value"}\n```' result = self.parser.execute(json_str) assert result == {"key": "value"} # 测试带额外空白的包装 json_str = ' ```json \n {"key": "value"} \n ``` ' result = self.parser.execute(json_str) assert result == {"key": "value"} def test_execute_with_context(self): """测试带上下文的解析""" json_str = '{"name": "Alice", "age": 30}' context = {"options": {"strict": False}} result = self.parser.execute(json_str, context) assert result == {"name": "Alice", "age": 30} def test_pure_function_property(self): """测试纯函数特性""" json_str = '{"test": "data"}' # 多次调用应返回相同结果 result1 = self.parser.execute(json_str) result2 = self.parser.execute(json_str) assert result1 == result2 # 不同实例应返回相同结果 parser2 = JsonSafeParseFunction() result3 = parser2.execute(json_str) assert result1 == result3 def test_invalid_input_type(self): """测试无效输入类型""" with pytest.raises(JSONParseError) as exc_info: self.parser.execute(123) # 非字符串输入 assert "Expected string input" in str(exc_info.value) def test_invalid_json(self): """测试无效 JSON""" with pytest.raises(JSONParseError) as exc_info: self.parser.execute('{"invalid": json}') # 无效 JSON assert "JSON decode error" in str(exc_info.value) def test_empty_input(self): """测试空输入""" with pytest.raises(JSONParseError) as exc_info: self.parser.execute('') assert "Empty or invalid JSON content" in str(exc_info.value) def test_extract_json_from_wrapped_string(self): """测试 JSON 提取方法""" # 测试各种格式 test_cases = [ ('```json\n{"key": "value"}\n```', '{"key": "value"}'), ('```\n[1, 2, 3]\n```', '[1, 2, 3]'), ('{"key": "value"}', '{"key": "value"}'), ('[1, 2, 3]', '[1, 2, 3]'), (' ```JSON \n {"test": true} \n ``` ', '{"test": true}'), ] for input_str, expected in test_cases: result = self.parser._extract_json_from_wrapped_string(input_str) assert result == expected def test_complex_nested_json(self): """测试复杂嵌套 JSON""" complex_json = { "users": [ {"name": "Alice", "age": 30, "active": True}, {"name": "Bob", "age": 25, "active": False} ], "metadata": { "total": 2, "timestamp": "2024-01-01T00:00:00Z" } } json_str = f'```json\n{json.dumps(complex_json, indent=2)}\n```' result = self.parser.execute(json_str) assert result == complex_json class TestSafeJsonParse: """测试 safe_json_parse 便捷函数""" def test_basic_parsing(self): """测试基本解析功能""" result = safe_json_parse('{"name": "test"}') assert result == {"name": "test"} def test_wrapped_parsing(self): """测试包装格式解析""" result = safe_json_parse('```json\n{"wrapped": true}\n```') assert result == {"wrapped": True} def test_error_handling(self): """测试错误处理""" with pytest.raises(JSONParseError): safe_json_parse('invalid json') class TestBatchJsonParse: """测试批量 JSON 解析功能""" def test_batch_parsing_success(self): """测试成功的批量解析""" json_strings = [ '{"name": "Alice"}', '```json\n{"name": "Bob"}\n```', '[1, 2, 3]' ] results = batch_json_parse(json_strings) expected = [ {"name": "Alice"}, {"name": "Bob"}, [1, 2, 3] ] assert results == expected def test_batch_parsing_error(self): """测试批量解析中的错误""" json_strings = [ '{"name": "Alice"}', 'invalid json', # 这个会导致错误 '[1, 2, 3]' ] with pytest.raises(JSONParseError) as exc_info: batch_json_parse(json_strings) assert "Failed to parse JSON at index 1" in str(exc_info.value) def test_invalid_input_type(self): """测试无效输入类型""" with pytest.raises(JSONParseError) as exc_info: batch_json_parse("not a list") assert "Expected list input" in str(exc_info.value) class TestValidateJsonStructure: """测试 JSON 结构验证功能""" def test_validate_string_input(self): """测试字符串输入验证""" result = validate_json_structure('{"name": "Alice", "age": 30}') assert result['valid'] is True assert result['data'] == {"name": "Alice", "age": 30} assert len(result['errors']) == 0 def test_validate_dict_input(self): """测试字典输入验证""" data = {"name": "Alice", "age": 30} result = validate_json_structure(data) assert result['valid'] is True assert result['data'] == data assert len(result['errors']) == 0 def test_validate_with_required_keys(self): """测试必需字段验证""" context = { 'validation_rules': { 'required_keys': ['name', 'age', 'email'] } } # 缺少字段的情况 result = validate_json_structure('{"name": "Alice", "age": 30}', context) assert result['valid'] is False assert any("Missing required keys" in error for error in result['errors']) # 包含所有字段的情况 result = validate_json_structure( '{"name": "Alice", "age": 30, "email": "alice@test.com"}', context ) assert result['valid'] is True def test_validate_expected_type(self): """测试期望类型验证""" # 期望对象但得到数组 context = {'validation_rules': {'expected_type': 'object'}} result = validate_json_structure('[1, 2, 3]', context) assert result['valid'] is False assert any("Expected JSON object" in error for error in result['errors']) # 期望数组但得到对象 context = {'validation_rules': {'expected_type': 'array'}} result = validate_json_structure('{"key": "value"}', context) assert result['valid'] is False assert any("Expected JSON array" in error for error in result['errors']) def test_validate_invalid_json(self): """测试无效 JSON 验证""" result = validate_json_structure('invalid json') assert result['valid'] is False assert result['data'] is None assert len(result['errors']) > 0 def test_validate_non_json_types(self): """测试非 JSON 类型验证""" # 测试无法解析为 JSON 的字符串 result = validate_json_structure("just a string") assert result['valid'] is False assert any("JSON decode error" in error for error in result['errors']) # 测试解析成功但不是 JSON 对象或数组的情况(如字符串、数字等) result = validate_json_structure('"just a string"') # 这是有效的 JSON 字符串 assert result['valid'] is False assert any("not a JSON object or array" in error for error in result['errors']) class TestPipelineComposition: """测试管道组合能力""" def test_json_parse_in_pipeline(self): """测试 JSON 解析在管道中的使用""" from src.components.functions import create_simple_function, create_pipeline # 创建一个简单的数据转换函数 def extract_name(data, context=None): if isinstance(data, dict) and 'name' in data: return data['name'] raise ValueError("No name field found") # 创建管道 parse_func = JsonSafeParseFunction() extract_func = create_simple_function("extract_name", "提取姓名", extract_name) pipeline = create_pipeline("parse_and_extract", "解析并提取", [parse_func, extract_func]) # 测试管道执行 json_str = '```json\n{"name": "Alice", "age": 30}\n```' result = pipeline(json_str) assert result == "Alice" def test_error_propagation_in_pipeline(self): """测试管道中的错误传播""" from src.components.functions import create_simple_function, create_pipeline def dummy_transform(data, context=None): return data parse_func = JsonSafeParseFunction() transform_func = create_simple_function("dummy", "虚拟转换", dummy_transform) pipeline = create_pipeline("parse_pipeline", "解析管道", [parse_func, transform_func]) # 测试无效 JSON 在管道中的错误传播 with pytest.raises(JSONParseError): pipeline('invalid json') class TestContextHandling: """测试上下文处理""" def test_context_options(self): """测试上下文选项""" parser = JsonSafeParseFunction() # 测试严格模式 context_strict = {'options': {'strict': True}} result = parser.execute('{"name": "Alice"}', context_strict) assert result == {"name": "Alice"} # 测试非严格模式 context_non_strict = {'options': {'strict': False}} result = parser.execute('{"name": "Alice"}', context_non_strict) assert result == {"name": "Alice"} def test_context_preservation(self): """测试上下文保持""" # 验证函数不会修改传入的上下文 original_context = {'options': {'strict': True}, 'other': 'data'} context_copy = original_context.copy() safe_json_parse('{"test": true}', context_copy) assert context_copy == original_context if __name__ == "__main__": pytest.main([__file__])