|
3 | 3 | import pytest |
4 | 4 | from pydantic import ValidationError |
5 | 5 |
|
6 | | -from cli.serve.models import StreamOptions |
| 6 | +from cli.serve.models import FunctionParameters, JsonSchemaFormat, StreamOptions |
7 | 7 |
|
8 | 8 |
|
9 | 9 | class TestStreamOptions: |
@@ -79,3 +79,78 @@ def test_model_dump_json_serialization(self): |
79 | 79 | json_str = options.model_dump_json() |
80 | 80 | assert "include_usage" in json_str |
81 | 81 | assert "true" in json_str.lower() |
| 82 | + |
| 83 | + |
| 84 | +class TestFunctionParameters: |
| 85 | + """Tests for the FunctionParameters RootModel validator.""" |
| 86 | + |
| 87 | + def test_valid_json_schema_accepted(self): |
| 88 | + """Test that a valid JSON Schema dict is accepted.""" |
| 89 | + schema = { |
| 90 | + "type": "object", |
| 91 | + "properties": {"location": {"type": "string"}}, |
| 92 | + "required": ["location"], |
| 93 | + } |
| 94 | + params = FunctionParameters(root=schema) |
| 95 | + assert params.root == schema |
| 96 | + |
| 97 | + def test_legacy_root_model_envelope_rejected(self): |
| 98 | + """Test that legacy {'RootModel': {...}} envelope is rejected.""" |
| 99 | + legacy_envelope = { |
| 100 | + "RootModel": { |
| 101 | + "type": "object", |
| 102 | + "properties": {"location": {"type": "string"}}, |
| 103 | + } |
| 104 | + } |
| 105 | + with pytest.raises(ValidationError) as exc_info: |
| 106 | + FunctionParameters(root=legacy_envelope) |
| 107 | + |
| 108 | + errors = exc_info.value.errors() |
| 109 | + assert len(errors) == 1 |
| 110 | + error_msg = str(exc_info.value) |
| 111 | + assert "Legacy {'RootModel': {...}} envelope is no longer accepted" in error_msg |
| 112 | + |
| 113 | + def test_root_model_with_additional_keys_accepted(self): |
| 114 | + """Test that a dict with 'RootModel' plus other keys is accepted.""" |
| 115 | + # This is a valid schema that happens to have a property named "RootModel" |
| 116 | + schema = { |
| 117 | + "type": "object", |
| 118 | + "properties": { |
| 119 | + "RootModel": {"type": "string"}, |
| 120 | + "other_field": {"type": "number"}, |
| 121 | + }, |
| 122 | + } |
| 123 | + params = FunctionParameters(root=schema) |
| 124 | + assert params.root == schema |
| 125 | + |
| 126 | + def test_empty_dict_accepted(self): |
| 127 | + """Test that an empty dict is accepted (though not a useful schema).""" |
| 128 | + params = FunctionParameters(root={}) |
| 129 | + assert params.root == {} |
| 130 | + |
| 131 | + |
| 132 | +class TestJsonSchemaFormat: |
| 133 | + """Test JsonSchemaFormat serialization uses 'schema' alias, not 'schema_'.""" |
| 134 | + |
| 135 | + def test_serialization_uses_schema_alias(self): |
| 136 | + """Verify schema_ serializes as 'schema' in dict and JSON output.""" |
| 137 | + schema_def = {"type": "object", "properties": {"foo": {"type": "string"}}} |
| 138 | + json_schema = JsonSchemaFormat(name="TestSchema", schema=schema_def) |
| 139 | + |
| 140 | + # Dict serialization |
| 141 | + dumped = json_schema.model_dump() |
| 142 | + assert "schema" in dumped and "schema_" not in dumped |
| 143 | + assert dumped["schema"] == schema_def |
| 144 | + |
| 145 | + # JSON serialization |
| 146 | + json_str = json_schema.model_dump_json() |
| 147 | + assert '"schema":' in json_str and '"schema_":' not in json_str |
| 148 | + |
| 149 | + # Input accepts both 'schema' (alias) and 'schema_' (field name) |
| 150 | + from_alias = JsonSchemaFormat(name="Test1", schema={"type": "string"}) |
| 151 | + # Use model_validate to test runtime populate_by_name behavior (bypasses type checker) |
| 152 | + from_field = JsonSchemaFormat.model_validate( |
| 153 | + {"name": "Test2", "schema_": {"type": "number"}} |
| 154 | + ) |
| 155 | + assert from_alias.schema_ == {"type": "string"} |
| 156 | + assert from_field.schema_ == {"type": "number"} |
0 commit comments