mirror of
https://github.com/openai/codex.git
synced 2026-04-27 09:51:03 +03:00
Add regression tests for JsonSchema (#17052)
Tests added for existing JsonSchema in `codex-rs/tools/src/json_schema_tests.rs`: - `parse_tool_input_schema_coerces_boolean_schemas` - `parse_tool_input_schema_infers_object_shape_and_defaults_properties` - `parse_tool_input_schema_normalizes_integer_and_missing_array_items` - `parse_tool_input_schema_sanitizes_additional_properties_schema` - `parse_tool_input_schema_infers_object_shape_from_boolean_additional_properties_only` - `parse_tool_input_schema_infers_number_from_numeric_keywords` - `parse_tool_input_schema_infers_number_from_multiple_of` - `parse_tool_input_schema_infers_string_from_enum_const_and_format_keywords` - `parse_tool_input_schema_defaults_empty_schema_to_string` - `parse_tool_input_schema_infers_array_from_prefix_items` - `parse_tool_input_schema_preserves_boolean_additional_properties_on_inferred_object` - `parse_tool_input_schema_infers_object_shape_from_schema_additional_properties_only` Tests that we expect to fail on the baseline normalizer, but pass with the new JsonSchema: - `parse_tool_input_schema_preserves_nested_nullable_type_union` - `parse_tool_input_schema_preserves_nested_any_of_property`
This commit is contained in:
@@ -4,8 +4,18 @@ use super::parse_tool_input_schema;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
// Tests in this section exercise normalization transforms that mutate badly
|
||||
// formed JSON for consumption by the Responses API.
|
||||
|
||||
#[test]
|
||||
fn parse_tool_input_schema_coerces_boolean_schemas() {
|
||||
// Example schema shape:
|
||||
// true
|
||||
//
|
||||
// Expected normalization behavior:
|
||||
// - JSON Schema boolean forms are coerced to `{ "type": "string" }`
|
||||
// because the baseline enum model cannot represent boolean-schema
|
||||
// semantics directly.
|
||||
let schema = parse_tool_input_schema(&serde_json::json!(true)).expect("parse schema");
|
||||
|
||||
assert_eq!(schema, JsonSchema::String { description: None });
|
||||
@@ -13,6 +23,16 @@ fn parse_tool_input_schema_coerces_boolean_schemas() {
|
||||
|
||||
#[test]
|
||||
fn parse_tool_input_schema_infers_object_shape_and_defaults_properties() {
|
||||
// Example schema shape:
|
||||
// {
|
||||
// "properties": {
|
||||
// "query": { "description": "search query" }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Expected normalization behavior:
|
||||
// - `properties` implies an object schema when `type` is omitted.
|
||||
// - The child property keeps its description and defaults to a string type.
|
||||
let schema = parse_tool_input_schema(&serde_json::json!({
|
||||
"properties": {
|
||||
"query": {"description": "search query"}
|
||||
@@ -37,6 +57,19 @@ fn parse_tool_input_schema_infers_object_shape_and_defaults_properties() {
|
||||
|
||||
#[test]
|
||||
fn parse_tool_input_schema_normalizes_integer_and_missing_array_items() {
|
||||
// Example schema shape:
|
||||
// {
|
||||
// "type": "object",
|
||||
// "properties": {
|
||||
// "page": { "type": "integer" },
|
||||
// "tags": { "type": "array" }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Expected normalization behavior:
|
||||
// - `"integer"` is accepted by the baseline model through the legacy
|
||||
// number/integer alias.
|
||||
// - Arrays missing `items` receive a permissive string `items` schema.
|
||||
let schema = parse_tool_input_schema(&serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -50,7 +83,7 @@ fn parse_tool_input_schema_normalizes_integer_and_missing_array_items() {
|
||||
schema,
|
||||
JsonSchema::Object {
|
||||
properties: BTreeMap::from([
|
||||
("page".to_string(), JsonSchema::Number { description: None },),
|
||||
("page".to_string(), JsonSchema::Number { description: None }),
|
||||
(
|
||||
"tags".to_string(),
|
||||
JsonSchema::Array {
|
||||
@@ -67,6 +100,27 @@ fn parse_tool_input_schema_normalizes_integer_and_missing_array_items() {
|
||||
|
||||
#[test]
|
||||
fn parse_tool_input_schema_sanitizes_additional_properties_schema() {
|
||||
// Example schema shape:
|
||||
// {
|
||||
// "type": "object",
|
||||
// "additionalProperties": {
|
||||
// "required": ["value"],
|
||||
// "properties": {
|
||||
// "value": {
|
||||
// "anyOf": [
|
||||
// { "type": "string" },
|
||||
// { "type": "number" }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Expected normalization behavior:
|
||||
// - `additionalProperties` schema objects are recursively sanitized.
|
||||
// - The nested schema is normalized into the baseline object form.
|
||||
// - In the baseline model, the nested `anyOf` degrades to a plain string
|
||||
// field because richer combiners are not preserved.
|
||||
let schema = parse_tool_input_schema(&serde_json::json!({
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
@@ -96,3 +150,313 @@ fn parse_tool_input_schema_sanitizes_additional_properties_schema() {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_tool_input_schema_infers_object_shape_from_boolean_additional_properties_only() {
|
||||
// Example schema shape:
|
||||
// {
|
||||
// "additionalProperties": false
|
||||
// }
|
||||
//
|
||||
// Expected normalization behavior:
|
||||
// - `additionalProperties` implies an object schema when `type` is omitted.
|
||||
// - The boolean `additionalProperties` setting is preserved.
|
||||
let schema = parse_tool_input_schema(&serde_json::json!({
|
||||
"additionalProperties": false
|
||||
}))
|
||||
.expect("parse schema");
|
||||
|
||||
assert_eq!(
|
||||
schema,
|
||||
JsonSchema::Object {
|
||||
properties: BTreeMap::new(),
|
||||
required: None,
|
||||
additional_properties: Some(false.into()),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_tool_input_schema_infers_number_from_numeric_keywords() {
|
||||
// Example schema shape:
|
||||
// {
|
||||
// "minimum": 1
|
||||
// }
|
||||
//
|
||||
// Expected normalization behavior:
|
||||
// - Numeric constraint keywords imply a number schema when `type` is
|
||||
// omitted.
|
||||
let schema = parse_tool_input_schema(&serde_json::json!({
|
||||
"minimum": 1
|
||||
}))
|
||||
.expect("parse schema");
|
||||
|
||||
assert_eq!(schema, JsonSchema::Number { description: None });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_tool_input_schema_infers_number_from_multiple_of() {
|
||||
// Example schema shape:
|
||||
// {
|
||||
// "multipleOf": 5
|
||||
// }
|
||||
//
|
||||
// Expected normalization behavior:
|
||||
// - `multipleOf` follows the same numeric-keyword inference path as
|
||||
// `minimum` / `maximum`.
|
||||
let schema = parse_tool_input_schema(&serde_json::json!({
|
||||
"multipleOf": 5
|
||||
}))
|
||||
.expect("parse schema");
|
||||
|
||||
assert_eq!(schema, JsonSchema::Number { description: None });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_tool_input_schema_infers_string_from_enum_const_and_format_keywords() {
|
||||
// Example schema shapes:
|
||||
// { "enum": ["fast", "safe"] }
|
||||
// { "const": "file" }
|
||||
// { "format": "date-time" }
|
||||
//
|
||||
// Expected normalization behavior:
|
||||
// - Each of these keywords implies a string schema when `type` is omitted.
|
||||
let enum_schema = parse_tool_input_schema(&serde_json::json!({
|
||||
"enum": ["fast", "safe"]
|
||||
}))
|
||||
.expect("parse enum schema");
|
||||
let const_schema = parse_tool_input_schema(&serde_json::json!({
|
||||
"const": "file"
|
||||
}))
|
||||
.expect("parse const schema");
|
||||
let format_schema = parse_tool_input_schema(&serde_json::json!({
|
||||
"format": "date-time"
|
||||
}))
|
||||
.expect("parse format schema");
|
||||
|
||||
assert_eq!(enum_schema, JsonSchema::String { description: None });
|
||||
assert_eq!(const_schema, JsonSchema::String { description: None });
|
||||
assert_eq!(format_schema, JsonSchema::String { description: None });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_tool_input_schema_defaults_empty_schema_to_string() {
|
||||
// Example schema shape:
|
||||
// {}
|
||||
//
|
||||
// Expected normalization behavior:
|
||||
// - With no structural hints at all, the baseline normalizer falls back to
|
||||
// a permissive string schema.
|
||||
let schema = parse_tool_input_schema(&serde_json::json!({})).expect("parse schema");
|
||||
|
||||
assert_eq!(schema, JsonSchema::String { description: None });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_tool_input_schema_infers_array_from_prefix_items() {
|
||||
// Example schema shape:
|
||||
// {
|
||||
// "prefixItems": [
|
||||
// { "type": "string" }
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
// Expected normalization behavior:
|
||||
// - `prefixItems` implies an array schema when `type` is omitted.
|
||||
// - The baseline model still stores the normalized result as a regular
|
||||
// array schema with string items.
|
||||
let schema = parse_tool_input_schema(&serde_json::json!({
|
||||
"prefixItems": [
|
||||
{"type": "string"}
|
||||
]
|
||||
}))
|
||||
.expect("parse schema");
|
||||
|
||||
assert_eq!(
|
||||
schema,
|
||||
JsonSchema::Array {
|
||||
items: Box::new(JsonSchema::String { description: None }),
|
||||
description: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_tool_input_schema_preserves_boolean_additional_properties_on_inferred_object() {
|
||||
// Example schema shape:
|
||||
// {
|
||||
// "type": "object",
|
||||
// "properties": {
|
||||
// "metadata": {
|
||||
// "additionalProperties": true
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Expected normalization behavior:
|
||||
// - The nested `metadata` schema is inferred to be an object because it has
|
||||
// `additionalProperties`.
|
||||
// - `additionalProperties: true` is preserved rather than rewritten.
|
||||
let schema = parse_tool_input_schema(&serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"metadata": {
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}))
|
||||
.expect("parse schema");
|
||||
|
||||
assert_eq!(
|
||||
schema,
|
||||
JsonSchema::Object {
|
||||
properties: BTreeMap::from([(
|
||||
"metadata".to_string(),
|
||||
JsonSchema::Object {
|
||||
properties: BTreeMap::new(),
|
||||
required: None,
|
||||
additional_properties: Some(AdditionalProperties::Boolean(true)),
|
||||
},
|
||||
)]),
|
||||
required: None,
|
||||
additional_properties: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_tool_input_schema_infers_object_shape_from_schema_additional_properties_only() {
|
||||
// Example schema shape:
|
||||
// {
|
||||
// "additionalProperties": {
|
||||
// "type": "string"
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Expected normalization behavior:
|
||||
// - A schema-valued `additionalProperties` also implies an object schema
|
||||
// when `type` is omitted.
|
||||
// - The nested schema is preserved as the object's
|
||||
// `additionalProperties` definition.
|
||||
let schema = parse_tool_input_schema(&serde_json::json!({
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}))
|
||||
.expect("parse schema");
|
||||
|
||||
assert_eq!(
|
||||
schema,
|
||||
JsonSchema::Object {
|
||||
properties: BTreeMap::new(),
|
||||
required: None,
|
||||
additional_properties: Some(AdditionalProperties::Schema(Box::new(
|
||||
JsonSchema::String { description: None },
|
||||
))),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Schemas that should be preserved for Responses API compatibility rather than
|
||||
// being rewritten into a different shape. These currently fail on the baseline
|
||||
// normalizer and are the intended signal for the new JsonSchema work.
|
||||
|
||||
#[test]
|
||||
#[ignore = "Expected to pass after the new JsonSchema preserves nullable type unions"]
|
||||
fn parse_tool_input_schema_preserves_nested_nullable_type_union() {
|
||||
// Example schema shape:
|
||||
// {
|
||||
// "type": "object",
|
||||
// "properties": {
|
||||
// "nickname": {
|
||||
// "type": ["string", "null"],
|
||||
// "description": "Optional nickname"
|
||||
// }
|
||||
// },
|
||||
// "required": ["nickname"],
|
||||
// "additionalProperties": false
|
||||
// }
|
||||
//
|
||||
// Expected normalization behavior:
|
||||
// - The nested property keeps the explicit `["string", "null"]` union.
|
||||
// - The object-level `required` and `additionalProperties: false` stay intact.
|
||||
let schema = parse_tool_input_schema(&serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nickname": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Optional nickname"
|
||||
}
|
||||
},
|
||||
"required": ["nickname"],
|
||||
"additionalProperties": false
|
||||
}))
|
||||
.expect("parse schema");
|
||||
|
||||
assert_eq!(
|
||||
schema,
|
||||
JsonSchema::Object {
|
||||
properties: BTreeMap::from([(
|
||||
"nickname".to_string(),
|
||||
serde_json::from_value(serde_json::json!({
|
||||
"type": ["string", "null"],
|
||||
"description": "Optional nickname"
|
||||
}))
|
||||
.expect("nested nullable schema"),
|
||||
)]),
|
||||
required: Some(vec!["nickname".to_string()]),
|
||||
additional_properties: Some(false.into()),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Expected to pass after the new JsonSchema preserves nested anyOf schemas"]
|
||||
fn parse_tool_input_schema_preserves_nested_any_of_property() {
|
||||
// Example schema shape:
|
||||
// {
|
||||
// "type": "object",
|
||||
// "properties": {
|
||||
// "query": {
|
||||
// "anyOf": [
|
||||
// { "type": "string" },
|
||||
// { "type": "number" }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Expected normalization behavior:
|
||||
// - The nested `anyOf` is preserved rather than flattened into a single
|
||||
// fallback type.
|
||||
let schema = parse_tool_input_schema(&serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"anyOf": [
|
||||
{ "type": "string" },
|
||||
{ "type": "number" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}))
|
||||
.expect("parse schema");
|
||||
|
||||
assert_eq!(
|
||||
schema,
|
||||
JsonSchema::Object {
|
||||
properties: BTreeMap::from([(
|
||||
"query".to_string(),
|
||||
serde_json::from_value(serde_json::json!({
|
||||
"anyOf": [
|
||||
{ "type": "string" },
|
||||
{ "type": "number" }
|
||||
]
|
||||
}))
|
||||
.expect("nested anyOf schema"),
|
||||
)]),
|
||||
required: None,
|
||||
additional_properties: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user