""" Tests for ToolCallOutput schema validation with complex types. """ import pytest from arcade_core.errors import ErrorKind from arcade_core.schema import ToolCallError, ToolCallLog, ToolCallOutput from pydantic import ValidationError class TestToolCallOutputValidation: """Test ToolCallOutput validation with various data types.""" def test_basic_types(self): """Test that basic types are validated correctly.""" # String output = ToolCallOutput(value="test string") assert output.value == "test string" # Integer output = ToolCallOutput(value=42) assert output.value == 42 # Float output = ToolCallOutput(value=3.14) assert output.value == 3.14 # Boolean output = ToolCallOutput(value=True) assert output.value is True # None output = ToolCallOutput(value=None) assert output.value is None def test_dict_types(self): """Test that dict types are validated correctly.""" # Simple dict output = ToolCallOutput(value={"key": "value"}) assert output.value == {"key": "value"} # Nested dict output = ToolCallOutput(value={"outer": {"inner": "value"}}) assert output.value == {"outer": {"inner": "value"}} # Empty dict output = ToolCallOutput(value={}) assert output.value == {} # Dict with mixed types output = ToolCallOutput( value={ "string": "text", "number": 123, "float": 45.6, "bool": True, "null": None, "list": [1, 2, 3], "dict": {"nested": "value"}, } ) assert output.value["string"] == "text" assert output.value["number"] == 123 assert output.value["list"] == [1, 2, 3] def test_list_types(self): """Test that list types are validated correctly.""" # List of strings (original type) output = ToolCallOutput(value=["a", "b", "c"]) assert output.value == ["a", "b", "c"] # List of integers output = ToolCallOutput(value=[1, 2, 3]) assert output.value == [1, 2, 3] # List of dicts (TypedDict at runtime) output = ToolCallOutput(value=[{"id": 1, "name": "first"}, {"id": 2, "name": "second"}]) assert output.value == [{"id": 1, "name": "first"}, {"id": 2, "name": "second"}] # Mixed type list output = ToolCallOutput(value=[1, "two", 3.0, True, None, {"key": "value"}]) assert len(output.value) == 6 assert output.value[5] == {"key": "value"} # Empty list output = ToolCallOutput(value=[]) assert output.value == [] # Nested lists output = ToolCallOutput(value=[[1, 2], [3, 4], [5, 6]]) assert output.value == [[1, 2], [3, 4], [5, 6]] def test_complex_nested_structures(self): """Test complex nested structures that might come from TypedDict.""" # Simulate a complex API response structure complex_data = { "status": "success", "data": { "users": [ { "id": 1, "name": "Alice", "roles": ["admin", "user"], "metadata": {"last_login": "2024-01-01", "active": True}, }, { "id": 2, "name": "Bob", "roles": ["user"], "metadata": {"last_login": "2024-01-02", "active": False}, }, ], "total": 2, "page_info": {"page": 1, "per_page": 10, "has_next": False}, }, "errors": [], } output = ToolCallOutput(value=complex_data) assert output.value == complex_data assert output.value["data"]["users"][0]["name"] == "Alice" assert output.value["data"]["page_info"]["has_next"] is False def test_error_and_logs_with_value(self): """Test that error and logs can coexist with different value types.""" # With dict value and logs output = ToolCallOutput( value={"result": "success"}, logs=[ ToolCallLog(message="Processing started", level="info"), ToolCallLog(message="Deprecation warning", level="warning", subtype="deprecation"), ], ) assert output.value == {"result": "success"} assert len(output.logs) == 2 # With list value and error output = ToolCallOutput( error=ToolCallError( message="Partial failure", developer_message="Some items failed to process", can_retry=True, kind=ErrorKind.TOOL_RUNTIME_RETRY, ) ) assert output.error.message == "Partial failure" assert output.value is None def test_unsupported_types_still_fail(self): """Test that truly unsupported types still fail validation.""" # Custom object (not dict, list, or basic type) class CustomClass: def __init__(self): self.data = "test" # This should fail because CustomClass instance is not a supported type # Note: This test is about Pydantic validation, not the output factory # The output factory would catch this earlier with pytest.raises(ValidationError): # Directly creating with an unsupported type should fail ToolCallOutput(value=CustomClass()) def test_very_large_structures(self): """Test that large structures are handled properly.""" # Large list of dicts large_list = [{"id": i, "value": f"item_{i}"} for i in range(1000)] output = ToolCallOutput(value=large_list) assert len(output.value) == 1000 assert output.value[500]["id"] == 500 # Deeply nested structure deep_dict = {"level1": {"level2": {"level3": {"level4": {"level5": "deep_value"}}}}} output = ToolCallOutput(value=deep_dict) assert output.value["level1"]["level2"]["level3"]["level4"]["level5"] == "deep_value" def test_json_serializable(self): """Test that all supported types are JSON serializable.""" import json test_cases = [ {"type": "string"}, ["list", "of", "strings"], [{"id": 1}, {"id": 2}], {"nested": {"data": [1, 2, 3]}}, 123, 45.6, True, None, ] for test_value in test_cases: output = ToolCallOutput(value=test_value) # This should not raise an exception json_str = json.dumps(output.model_dump()) # And we should be able to parse it back parsed = json.loads(json_str) assert parsed["value"] == test_value