diff --git a/README.md b/README.md index 6c6803d..aaf51b1 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ It is meant to be language agnostic and should require only a JSON parser. The c This suite covers the following OpenAPI Schema Object dialects: +- **OAS 3.0**: Highly customized subset/superset of JSON Schema Draft 4 - **OAS 3.1**: [`https://spec.openapis.org/oas/3.1/dialect/2024-11-10`](https://spec.openapis.org/oas/3.1/dialect/2024-11-10) (also compatible with [`https://spec.openapis.org/oas/3.1/dialect/base`](https://spec.openapis.org/oas/3.1/dialect/base)) - **OAS 3.2**: [`https://spec.openapis.org/oas/3.2/schema/2025-11-23`](https://spec.openapis.org/oas/3.2/schema/2025-11-23) @@ -16,6 +17,7 @@ This suite covers the following OpenAPI Schema Object dialects: The tests in this suite are contained in the `tests` directory at the root of this repository. Inside that directory is a subdirectory for each supported version of the OpenAPI specification: +- `tests/oas30/` — Tests for the OpenAPI 3.0 Schema Object dialect - `tests/oas31/` — Tests for the OpenAPI 3.1 Schema Object dialect - `tests/oas32/` — Tests for the OpenAPI 3.2 Schema Object dialect diff --git a/tests/oas30/allOf.json b/tests/oas30/allOf.json new file mode 100644 index 0000000..6ddf66b --- /dev/null +++ b/tests/oas30/allOf.json @@ -0,0 +1,160 @@ +[ + { + "description": "allOf", + "schema": { + "allOf": [ + { + "type": "string" + }, + { + "minLength": 3 + } + ] + }, + "tests": [ + { + "description": "string with sufficient length is valid", + "data": "foobar", + "valid": true + }, + { + "description": "short string is not valid", + "data": "ab", + "valid": false + }, + { + "description": "integer is not valid", + "data": 1, + "valid": false + } + ] + }, + { + "description": "allOf with object schemas", + "schema": { + "allOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + { + "type": "object", + "properties": { + "age": { + "type": "integer" + } + }, + "required": [ + "age" + ] + } + ] + }, + "tests": [ + { + "description": "object with both required properties is valid", + "data": { + "name": "Alice", + "age": 30 + }, + "valid": true + }, + { + "description": "object missing age is not valid", + "data": { + "name": "Alice" + }, + "valid": false + }, + { + "description": "object missing name is not valid", + "data": { + "age": 30 + }, + "valid": false + }, + { + "description": "empty object is not valid", + "data": {}, + "valid": false + } + ] + }, + { + "description": "allOf with base schema", + "schema": { + "type": "object", + "allOf": [ + { + "properties": { + "bar": { + "type": "integer" + } + } + } + ], + "properties": { + "foo": { + "type": "string" + } + } + }, + "tests": [ + { + "description": "valid with both properties", + "data": { + "foo": "quux", + "bar": 1 + }, + "valid": true + }, + { + "description": "not valid, wrong type for bar", + "data": { + "foo": "quux", + "bar": "not-integer" + }, + "valid": false + } + ] + }, + { + "description": "allOf with boolean schemas", + "schema": { + "allOf": [ + true, + true + ] + }, + "tests": [ + { + "description": "any value is valid when all subschemas are true", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "allOf with one false schema always fails", + "schema": { + "allOf": [ + true, + false + ] + }, + "tests": [ + { + "description": "any value is invalid when any subschema is false", + "data": "foo", + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/anyOf.json b/tests/oas30/anyOf.json new file mode 100644 index 0000000..3c7e8dc --- /dev/null +++ b/tests/oas30/anyOf.json @@ -0,0 +1,123 @@ +[ + { + "description": "anyOf", + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "an integer is valid", + "data": 1, + "valid": true + }, + { + "description": "a number greater than 2 is valid", + "data": 2.5, + "valid": true + }, + { + "description": "an integer and greater than 2 is valid", + "data": 3, + "valid": true + }, + { + "description": "a float less than 2 is not valid", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "anyOf with base schema", + "schema": { + "type": "string", + "anyOf": [ + { + "maxLength": 2 + }, + { + "minLength": 4 + } + ] + }, + "tests": [ + { + "description": "short string is valid", + "data": "hi", + "valid": true + }, + { + "description": "long string is valid", + "data": "hello", + "valid": true + }, + { + "description": "string of length 3 is not valid", + "data": "foo", + "valid": false + }, + { + "description": "integer is not valid (fails base schema)", + "data": 1, + "valid": false + } + ] + }, + { + "description": "anyOf with object schemas", + "schema": { + "anyOf": [ + { + "type": "object", + "required": [ + "name" + ] + }, + { + "type": "object", + "required": [ + "id" + ] + } + ] + }, + "tests": [ + { + "description": "object with name is valid", + "data": { + "name": "Alice" + }, + "valid": true + }, + { + "description": "object with id is valid", + "data": { + "id": 1 + }, + "valid": true + }, + { + "description": "object with both is valid", + "data": { + "name": "Alice", + "id": 1 + }, + "valid": true + }, + { + "description": "object with neither is not valid", + "data": { + "other": true + }, + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/deprecated.json b/tests/oas30/deprecated.json new file mode 100644 index 0000000..2b19540 --- /dev/null +++ b/tests/oas30/deprecated.json @@ -0,0 +1,21 @@ +[ + { + "description": "deprecated property", + "schema": { + "type": "string", + "deprecated": true + }, + "tests": [ + { + "description": "deprecated is merely an annotation, no validation effect by default", + "data": "foo", + "valid": true + }, + { + "description": "invalid types still fail", + "data": 123, + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/discriminator.json b/tests/oas30/discriminator.json new file mode 100644 index 0000000..4a674e3 --- /dev/null +++ b/tests/oas30/discriminator.json @@ -0,0 +1,183 @@ +[ + { + "description": "discriminator as annotation", + "comment": "The discriminator keyword in OAS 3.0 is an annotation that assists with schema selection in polymorphic scenarios. Validation is still performed by oneOf/anyOf/allOf.", + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "petType": { + "enum": [ + "cat" + ] + }, + "meow": { + "type": "string" + } + }, + "required": [ + "petType", + "meow" + ] + }, + { + "type": "object", + "properties": { + "petType": { + "enum": [ + "dog" + ] + }, + "bark": { + "type": "string" + } + }, + "required": [ + "petType", + "bark" + ] + } + ], + "discriminator": { + "propertyName": "petType" + } + }, + "tests": [ + { + "description": "a cat object is valid", + "data": { + "petType": "cat", + "meow": "purr" + }, + "valid": true + }, + { + "description": "a dog object is valid", + "data": { + "petType": "dog", + "bark": "woof" + }, + "valid": true + }, + { + "description": "unknown petType is not valid", + "data": { + "petType": "bird", + "chirp": "tweet" + }, + "valid": false + }, + { + "description": "missing discriminator property is not valid", + "data": { + "meow": "purr" + }, + "valid": false + } + ] + }, + { + "description": "discriminator with mapping", + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "vehicleType": { + "enum": [ + "car" + ] + }, + "doors": { + "type": "integer" + } + }, + "required": [ + "vehicleType", + "doors" + ] + }, + { + "type": "object", + "properties": { + "vehicleType": { + "enum": [ + "truck" + ] + }, + "payload": { + "type": "number" + } + }, + "required": [ + "vehicleType", + "payload" + ] + } + ], + "discriminator": { + "propertyName": "vehicleType", + "mapping": { + "car": "#/components/schemas/Car", + "truck": "#/components/schemas/Truck" + } + } + }, + "tests": [ + { + "description": "a car object is valid", + "data": { + "vehicleType": "car", + "doors": 4 + }, + "valid": true + }, + { + "description": "a truck object is valid", + "data": { + "vehicleType": "truck", + "payload": 5.5 + }, + "valid": true + }, + { + "description": "unknown vehicle type is not valid", + "data": { + "vehicleType": "bus", + "seats": 50 + }, + "valid": false + } + ] + }, + { + "description": "discriminator keyword does not affect basic type validation", + "comment": "Validation is still governed by the JSON Schema keywords; discriminator is just an annotation", + "schema": { + "type": "object", + "discriminator": { + "propertyName": "kind" + } + }, + "tests": [ + { + "description": "an object without the discriminator property is still valid per type constraint", + "data": { + "name": "foo" + }, + "valid": true + }, + { + "description": "null is not valid because type is object", + "data": null, + "valid": false + }, + { + "description": "a string is not valid because type is object", + "data": "foo", + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/enum.json b/tests/oas30/enum.json new file mode 100644 index 0000000..a40344c --- /dev/null +++ b/tests/oas30/enum.json @@ -0,0 +1,105 @@ +[ + { + "description": "enum validation", + "schema": { + "enum": [ + "foo", + "bar" + ] + }, + "tests": [ + { + "description": "'foo' is valid", + "data": "foo", + "valid": true + }, + { + "description": "'bar' is valid", + "data": "bar", + "valid": true + }, + { + "description": "'baz' is not valid", + "data": "baz", + "valid": false + }, + { + "description": "an integer is not valid", + "data": 1, + "valid": false + } + ] + }, + { + "description": "enum with mixed types", + "schema": { + "enum": [ + 1, + 2, + 3, + "foo", + null + ] + }, + "tests": [ + { + "description": "1 is valid", + "data": 1, + "valid": true + }, + { + "description": "'foo' is valid", + "data": "foo", + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "5 is not valid", + "data": 5, + "valid": false + }, + { + "description": "'bar' is not valid", + "data": "bar", + "valid": false + } + ] + }, + { + "description": "enum with type constraint", + "schema": { + "type": "string", + "enum": [ + "active", + "inactive", + "pending" + ] + }, + "tests": [ + { + "description": "'active' is valid", + "data": "active", + "valid": true + }, + { + "description": "'inactive' is valid", + "data": "inactive", + "valid": true + }, + { + "description": "'pending' is valid", + "data": "pending", + "valid": true + }, + { + "description": "'unknown' is not valid", + "data": "unknown", + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/example.json b/tests/oas30/example.json new file mode 100644 index 0000000..d1eeeb1 --- /dev/null +++ b/tests/oas30/example.json @@ -0,0 +1,21 @@ +[ + { + "description": "example property", + "schema": { + "type": "string", + "example": "example-value" + }, + "tests": [ + { + "description": "example is an annotation and doesn't affect validation", + "data": "foo", + "valid": true + }, + { + "description": "wrong type fails validation independently of example", + "data": 123, + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/externalDocs.json b/tests/oas30/externalDocs.json new file mode 100644 index 0000000..e0e2a90 --- /dev/null +++ b/tests/oas30/externalDocs.json @@ -0,0 +1,24 @@ +[ + { + "description": "externalDocs property", + "schema": { + "type": "string", + "externalDocs": { + "description": "More info", + "url": "https://example.com" + } + }, + "tests": [ + { + "description": "externalDocs is merely an annotation, no validation effect by default", + "data": "foo", + "valid": true + }, + { + "description": "invalid types still fail", + "data": 123, + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/items.json b/tests/oas30/items.json new file mode 100644 index 0000000..3cadcbe --- /dev/null +++ b/tests/oas30/items.json @@ -0,0 +1,186 @@ +[ + { + "description": "items validation", + "schema": { + "type": "array", + "items": { + "type": "integer" + } + }, + "tests": [ + { + "description": "array of integers is valid", + "data": [ + 1, + 2, + 3 + ], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + }, + { + "description": "array with a string is not valid", + "data": [ + 1, + "foo", + 3 + ], + "valid": false + }, + { + "description": "array with null is not valid", + "data": [ + 1, + null, + 3 + ], + "valid": false + } + ] + }, + { + "description": "minItems", + "schema": { + "type": "array", + "minItems": 2 + }, + "tests": [ + { + "description": "array with 2 items is valid", + "data": [ + 1, + 2 + ], + "valid": true + }, + { + "description": "array with 3 items is valid", + "data": [ + 1, + 2, + 3 + ], + "valid": true + }, + { + "description": "array with 1 item is not valid", + "data": [ + 1 + ], + "valid": false + }, + { + "description": "empty array is not valid", + "data": [], + "valid": false + } + ] + }, + { + "description": "maxItems", + "schema": { + "type": "array", + "maxItems": 2 + }, + "tests": [ + { + "description": "empty array is valid", + "data": [], + "valid": true + }, + { + "description": "array with 1 item is valid", + "data": [ + 1 + ], + "valid": true + }, + { + "description": "array with 2 items is valid", + "data": [ + 1, + 2 + ], + "valid": true + }, + { + "description": "array with 3 items is not valid", + "data": [ + 1, + 2, + 3 + ], + "valid": false + } + ] + }, + { + "description": "uniqueItems", + "schema": { + "type": "array", + "uniqueItems": true + }, + "tests": [ + { + "description": "array with unique items is valid", + "data": [ + 1, + 2, + 3 + ], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + }, + { + "description": "array with duplicate items is not valid", + "data": [ + 1, + 2, + 1 + ], + "valid": false + }, + { + "description": "array with duplicate strings is not valid", + "data": [ + "foo", + "bar", + "foo" + ], + "valid": false + }, + { + "description": "array with duplicate objects is not valid", + "data": [ + { + "a": 1 + }, + { + "a": 1 + } + ], + "valid": false + }, + { + "description": "array with non-duplicate objects is valid", + "data": [ + { + "a": 1 + }, + { + "a": 2 + } + ], + "valid": true + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/not.json b/tests/oas30/not.json new file mode 100644 index 0000000..b45334b --- /dev/null +++ b/tests/oas30/not.json @@ -0,0 +1,150 @@ +[ + { + "description": "not", + "schema": { + "not": { + "type": "integer" + } + }, + "tests": [ + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "a float is valid", + "data": 1.1, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "an integer is not valid", + "data": 1, + "valid": false + } + ] + }, + { + "description": "not with required", + "schema": { + "not": { + "required": [ + "foo", + "bar" + ] + } + }, + "tests": [ + { + "description": "object with only foo is valid", + "data": { + "foo": 1 + }, + "valid": true + }, + { + "description": "object with only bar is valid", + "data": { + "bar": 2 + }, + "valid": true + }, + { + "description": "object with both foo and bar is not valid", + "data": { + "foo": 1, + "bar": 2 + }, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "not with true schema always fails", + "schema": { + "not": true + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + } + ] + }, + { + "description": "not with false schema always succeeds", + "schema": { + "not": false + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + } + ] + }, + { + "description": "not multiple types", + "schema": { + "not": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "boolean" + } + ] + } + }, + "tests": [ + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "a float is valid", + "data": 1.1, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "an integer is not valid", + "data": 1, + "valid": false + }, + { + "description": "a boolean is not valid", + "data": true, + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/nullable.json b/tests/oas30/nullable.json new file mode 100644 index 0000000..dc34e6e --- /dev/null +++ b/tests/oas30/nullable.json @@ -0,0 +1,70 @@ +[ + { + "description": "nullable string using nullable keyword", + "comment": "In OAS 3.0, nullable types use the 'nullable' keyword", + "schema": { + "type": "string", + "nullable": true + }, + "tests": [ + { + "description": "a string is valid", + "data": "hello", + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "an integer is not valid", + "data": 1, + "valid": false + } + ] + }, + { + "description": "nullable integer using nullable keyword", + "schema": { + "type": "integer", + "nullable": true + }, + "tests": [ + { + "description": "an integer is valid", + "data": 42, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "a string is not valid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "non-nullable string type does not accept null", + "schema": { + "type": "string", + "nullable": false + }, + "tests": [ + { + "description": "a string is valid", + "data": "hello", + "valid": true + }, + { + "description": "null is not valid", + "data": null, + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/numeric.json b/tests/oas30/numeric.json new file mode 100644 index 0000000..29c7fe4 --- /dev/null +++ b/tests/oas30/numeric.json @@ -0,0 +1,219 @@ +[ + { + "description": "minimum validation", + "schema": { + "minimum": 1.1 + }, + "tests": [ + { + "description": "a value equal to minimum is valid", + "data": 1.1, + "valid": true + }, + { + "description": "a value greater than minimum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "a value less than minimum is not valid", + "data": 0.6, + "valid": false + } + ] + }, + { + "description": "maximum validation", + "schema": { + "maximum": 3.0 + }, + "tests": [ + { + "description": "a value less than maximum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "a value equal to maximum is valid", + "data": 3.0, + "valid": true + }, + { + "description": "a value greater than maximum is not valid", + "data": 3.5, + "valid": false + } + ] + }, + { + "description": "exclusiveMinimum validation (OAS 3.0 / Draft 4 syntax)", + "comment": "In OAS 3.1 (JSON Schema 2020-12), exclusiveMinimum is a number, not a boolean", + "schema": { + "exclusiveMinimum": true, + "minimum": 1.1 + }, + "tests": [ + { + "description": "a value greater than exclusiveMinimum is valid", + "data": 1.2, + "valid": true + }, + { + "description": "a value equal to exclusiveMinimum is not valid", + "data": 1.1, + "valid": false + }, + { + "description": "a value less than exclusiveMinimum is not valid", + "data": 0.6, + "valid": false + } + ] + }, + { + "description": "exclusiveMaximum validation (OAS 3.0 / Draft 4 syntax)", + "comment": "In OAS 3.1 (JSON Schema 2020-12), exclusiveMaximum is a number, not a boolean", + "schema": { + "exclusiveMaximum": true, + "maximum": 3.0 + }, + "tests": [ + { + "description": "a value less than exclusiveMaximum is valid", + "data": 2.2, + "valid": true + }, + { + "description": "a value equal to exclusiveMaximum is not valid", + "data": 3.0, + "valid": false + }, + { + "description": "a value greater than exclusiveMaximum is not valid", + "data": 3.5, + "valid": false + } + ] + }, + { + "description": "multipleOf validation", + "schema": { + "multipleOf": 2 + }, + "tests": [ + { + "description": "an integer multiple of 2 is valid", + "data": 10, + "valid": true + }, + { + "description": "0 is a multiple of 2", + "data": 0, + "valid": true + }, + { + "description": "an integer not multiple of 2 is not valid", + "data": 7, + "valid": false + } + ] + }, + { + "description": "multipleOf with float divisor", + "schema": { + "multipleOf": 1.5 + }, + "tests": [ + { + "description": "0 is valid", + "data": 0, + "valid": true + }, + { + "description": "1.5 is valid", + "data": 1.5, + "valid": true + }, + { + "description": "3 is valid", + "data": 3, + "valid": true + }, + { + "description": "35 is not valid", + "data": 35, + "valid": false + } + ] + }, + { + "description": "minimum and maximum", + "schema": { + "minimum": 0, + "maximum": 100 + }, + "tests": [ + { + "description": "a value at minimum is valid", + "data": 0, + "valid": true + }, + { + "description": "a value at maximum is valid", + "data": 100, + "valid": true + }, + { + "description": "a value in range is valid", + "data": 50, + "valid": true + }, + { + "description": "a value below minimum is not valid", + "data": -1, + "valid": false + }, + { + "description": "a value above maximum is not valid", + "data": 101, + "valid": false + } + ] + }, + { + "description": "exclusiveMinimum and exclusiveMaximum (OAS 3.0 / Draft 4 syntax)", + "schema": { + "exclusiveMinimum": true, + "exclusiveMaximum": true, + "minimum": 0, + "maximum": 100 + }, + "tests": [ + { + "description": "a value in range is valid", + "data": 50, + "valid": true + }, + { + "description": "a value at exclusiveMinimum is not valid", + "data": 0, + "valid": false + }, + { + "description": "a value at exclusiveMaximum is not valid", + "data": 100, + "valid": false + }, + { + "description": "a value below exclusiveMinimum is not valid", + "data": -1, + "valid": false + }, + { + "description": "a value above exclusiveMaximum is not valid", + "data": 101, + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/oneOf.json b/tests/oas30/oneOf.json new file mode 100644 index 0000000..1b4df16 --- /dev/null +++ b/tests/oas30/oneOf.json @@ -0,0 +1,182 @@ +[ + { + "description": "oneOf", + "schema": { + "oneOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "an integer less than 2 matches first subschema only", + "data": 1, + "valid": true + }, + { + "description": "a float greater than 2 matches second subschema only", + "data": 2.5, + "valid": true + }, + { + "description": "an integer greater than 2 matches both subschemas and is not valid", + "data": 3, + "valid": false + }, + { + "description": "a float less than 2 matches neither subschema", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "oneOf with mutually exclusive types", + "schema": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "tests": [ + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "an integer is valid", + "data": 1, + "valid": true + }, + { + "description": "a float is not valid", + "data": 1.5, + "valid": false + }, + { + "description": "null is not valid", + "data": null, + "valid": false + } + ] + }, + { + "description": "oneOf with base schema", + "schema": { + "type": "string", + "oneOf": [ + { + "minLength": 2 + }, + { + "maxLength": 4 + } + ] + }, + "tests": [ + { + "description": "short string matching only maxLength is valid", + "data": "a", + "valid": true + }, + { + "description": "long string matching only minLength is valid", + "data": "hello", + "valid": true + }, + { + "description": "string of length 2-4 matches both and is not valid", + "data": "foo", + "valid": false + }, + { + "description": "integer is not valid (fails base schema)", + "data": 1, + "valid": false + } + ] + }, + { + "description": "oneOf with discriminator-like pattern", + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "enum": [ + "cat" + ] + }, + "meow": { + "type": "string" + } + }, + "required": [ + "type", + "meow" + ] + }, + { + "type": "object", + "properties": { + "type": { + "enum": [ + "dog" + ] + }, + "bark": { + "type": "string" + } + }, + "required": [ + "type", + "bark" + ] + } + ] + }, + "tests": [ + { + "description": "cat schema is valid", + "data": { + "type": "cat", + "meow": "purr" + }, + "valid": true + }, + { + "description": "dog schema is valid", + "data": { + "type": "dog", + "bark": "woof" + }, + "valid": true + }, + { + "description": "unknown type is not valid", + "data": { + "type": "bird", + "chirp": "tweet" + }, + "valid": false + }, + { + "description": "missing required field is not valid", + "data": { + "type": "cat" + }, + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/optional/format/format-assertion.json b/tests/oas30/optional/format/format-assertion.json new file mode 100644 index 0000000..118f9be --- /dev/null +++ b/tests/oas30/optional/format/format-assertion.json @@ -0,0 +1,128 @@ +[ + { + "description": "format email validation when assertions are enabled", + "comment": "These tests apply when a validator is configured to treat format as assertions, not just annotations", + "schema": { + "type": "string", + "format": "email" + }, + "tests": [ + { + "description": "a valid email is valid", + "data": "foo@example.com", + "valid": true + }, + { + "description": "an invalid email is not valid", + "data": "not-an-email", + "valid": false + }, + { + "description": "an empty string is not a valid email", + "data": "", + "valid": false + } + ] + }, + { + "description": "format date-time with assertion", + "schema": { + "type": "string", + "format": "date-time" + }, + "tests": [ + { + "description": "a valid date-time is valid", + "data": "1963-06-19T08:30:06.283185307Z", + "valid": true + }, + { + "description": "a valid date-time with offset is valid", + "data": "2023-01-01T00:00:00+05:30", + "valid": true + }, + { + "description": "an invalid date-time string is not valid", + "data": "not-a-date-time", + "valid": false + }, + { + "description": "a date without time is not a valid date-time", + "data": "1963-06-19", + "valid": false + } + ] + }, + { + "description": "format date with assertion", + "schema": { + "type": "string", + "format": "date" + }, + "tests": [ + { + "description": "a valid date string is valid", + "data": "1963-06-19", + "valid": true + }, + { + "description": "an invalid date string is not valid", + "data": "not-a-date", + "valid": false + }, + { + "description": "a date-time is not a valid date", + "data": "1963-06-19T08:30:06Z", + "valid": false + } + ] + }, + { + "description": "format uri with assertion", + "schema": { + "type": "string", + "format": "uri" + }, + "tests": [ + { + "description": "a valid URI is valid", + "data": "http://example.com/path?query=1#fragment", + "valid": true + }, + { + "description": "a relative URI is not a valid URI", + "data": "//example.com", + "valid": false + }, + { + "description": "an invalid URI is not valid", + "data": "not a uri", + "valid": false + } + ] + }, + { + "description": "format uuid with assertion", + "schema": { + "type": "string", + "format": "uuid" + }, + "tests": [ + { + "description": "a valid UUID is valid", + "data": "2eb8aa08-aa98-11ea-b4aa-73b441d16380", + "valid": true + }, + { + "description": "an uppercase UUID is valid", + "data": "2EB8AA08-AA98-11EA-B4AA-73B441D16380", + "valid": true + }, + { + "description": "an invalid UUID is not valid", + "data": "not-a-uuid", + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/properties.json b/tests/oas30/properties.json new file mode 100644 index 0000000..7c53955 --- /dev/null +++ b/tests/oas30/properties.json @@ -0,0 +1,247 @@ +[ + { + "description": "properties validation", + "schema": { + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "integer" + } + } + }, + "tests": [ + { + "description": "both properties are valid", + "data": { + "name": "Alice", + "age": 30 + }, + "valid": true + }, + { + "description": "one property is valid", + "data": { + "name": "Alice" + }, + "valid": true + }, + { + "description": "extra properties are valid when additionalProperties not restricted", + "data": { + "name": "Alice", + "age": 30, + "extra": true + }, + "valid": true + }, + { + "description": "wrong type for a property is not valid", + "data": { + "name": 123 + }, + "valid": false + }, + { + "description": "wrong type for age is not valid", + "data": { + "name": "Alice", + "age": "thirty" + }, + "valid": false + } + ] + }, + { + "description": "required properties", + "schema": { + "type": "object", + "required": [ + "name", + "age" + ], + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "integer" + } + } + }, + "tests": [ + { + "description": "all required properties present is valid", + "data": { + "name": "Alice", + "age": 30 + }, + "valid": true + }, + { + "description": "missing required property is not valid", + "data": { + "name": "Alice" + }, + "valid": false + }, + { + "description": "missing both required properties is not valid", + "data": {}, + "valid": false + }, + { + "description": "extra properties with required present is valid", + "data": { + "name": "Alice", + "age": 30, + "email": "alice@example.com" + }, + "valid": true + } + ] + }, + { + "description": "additionalProperties as false", + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": { + "name": "Alice" + }, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "an additional property is not valid", + "data": { + "name": "Alice", + "extra": "value" + }, + "valid": false + } + ] + }, + { + "description": "additionalProperties as a schema", + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "additionalProperties": { + "type": "string" + } + }, + "tests": [ + { + "description": "additional string property is valid", + "data": { + "name": "Alice", + "extra": "value" + }, + "valid": true + }, + { + "description": "additional integer property is not valid", + "data": { + "name": "Alice", + "extra": 123 + }, + "valid": false + } + ] + }, + { + "description": "minProperties", + "schema": { + "type": "object", + "minProperties": 2 + }, + "tests": [ + { + "description": "object with 2 properties is valid", + "data": { + "a": 1, + "b": 2 + }, + "valid": true + }, + { + "description": "object with 3 properties is valid", + "data": { + "a": 1, + "b": 2, + "c": 3 + }, + "valid": true + }, + { + "description": "object with 1 property is not valid", + "data": { + "a": 1 + }, + "valid": false + }, + { + "description": "empty object is not valid", + "data": {}, + "valid": false + } + ] + }, + { + "description": "maxProperties", + "schema": { + "type": "object", + "maxProperties": 2 + }, + "tests": [ + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "object with 1 property is valid", + "data": { + "a": 1 + }, + "valid": true + }, + { + "description": "object with 2 properties is valid", + "data": { + "a": 1, + "b": 2 + }, + "valid": true + }, + { + "description": "object with 3 properties is not valid", + "data": { + "a": 1, + "b": 2, + "c": 3 + }, + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/readOnly.json b/tests/oas30/readOnly.json new file mode 100644 index 0000000..13e985d --- /dev/null +++ b/tests/oas30/readOnly.json @@ -0,0 +1,34 @@ +[ + { + "description": "readOnly property", + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "readOnly": true + }, + "name": { + "type": "string" + } + } + }, + "tests": [ + { + "description": "readOnly is merely an annotation, no validation effect by default", + "data": { + "id": 123, + "name": "foo" + }, + "valid": true + }, + { + "description": "valid without readOnly property", + "data": { + "name": "foo" + }, + "valid": true + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/ref.json b/tests/oas30/ref.json new file mode 100644 index 0000000..49b052d --- /dev/null +++ b/tests/oas30/ref.json @@ -0,0 +1,270 @@ +[ + { + "description": "$ref to $defs", + "schema": { + "$ref": "#/definitions/MyString", + "definitions": { + "MyString": { + "type": "string" + } + } + }, + "tests": [ + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "an integer is not valid", + "data": 1, + "valid": false + } + ] + }, + { + "description": "property using $ref", + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "address": { + "$ref": "#/definitions/Address" + } + }, + "required": [ + "name" + ], + "definitions": { + "Address": { + "type": "object", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + } + }, + "required": [ + "street", + "city" + ] + } + } + }, + "tests": [ + { + "description": "valid object with ref property", + "data": { + "name": "Alice", + "address": { + "street": "123 Main St", + "city": "Springfield" + } + }, + "valid": true + }, + { + "description": "valid object without optional ref property", + "data": { + "name": "Alice" + }, + "valid": true + }, + { + "description": "invalid ref property is not valid", + "data": { + "name": "Alice", + "address": { + "street": "123 Main St" + } + }, + "valid": false + }, + { + "description": "wrong type for ref property is not valid", + "data": { + "name": "Alice", + "address": "not-an-object" + }, + "valid": false + } + ] + }, + { + "description": "recursive $ref using $defs", + "schema": { + "$ref": "#/definitions/Node", + "definitions": { + "Node": { + "type": "object", + "properties": { + "value": { + "type": "integer" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/Node" + } + } + }, + "required": [ + "value" + ] + } + } + }, + "tests": [ + { + "description": "simple node is valid", + "data": { + "value": 1 + }, + "valid": true + }, + { + "description": "node with children is valid", + "data": { + "value": 1, + "children": [ + { + "value": 2 + }, + { + "value": 3 + } + ] + }, + "valid": true + }, + { + "description": "nested children are valid", + "data": { + "value": 1, + "children": [ + { + "value": 2, + "children": [ + { + "value": 3 + } + ] + } + ] + }, + "valid": true + }, + { + "description": "node missing required value is not valid", + "data": { + "children": [] + }, + "valid": false + }, + { + "description": "child node missing required value is not valid", + "data": { + "value": 1, + "children": [ + { + "name": "no-value" + } + ] + }, + "valid": false + } + ] + }, + { + "description": "$ref in allOf", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/BaseObject" + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + } + ], + "definitions": { + "BaseObject": { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ] + } + } + }, + "tests": [ + { + "description": "object with both id and name is valid", + "data": { + "id": 1, + "name": "Alice" + }, + "valid": true + }, + { + "description": "object without id is not valid", + "data": { + "name": "Alice" + }, + "valid": false + }, + { + "description": "object without name is not valid", + "data": { + "id": 1 + }, + "valid": false + } + ] + }, + { + "description": "$ref sibling keywords are ignored", + "comment": "In JSON Schema Draft 4 and OAS 3.0, $ref precludes sibling keywords, so they should be ignored.", + "schema": { + "$ref": "#/definitions/StringType", + "minLength": 3, + "definitions": { + "StringType": { + "type": "string" + } + } + }, + "tests": [ + { + "description": "a string with sufficient length is valid", + "data": "foobar", + "valid": true + }, + { + "description": "a short string is valid because minLength sibling is ignored", + "data": "ab", + "valid": true + }, + { + "description": "an integer is not valid", + "data": 1, + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/string.json b/tests/oas30/string.json new file mode 100644 index 0000000..68f11b5 --- /dev/null +++ b/tests/oas30/string.json @@ -0,0 +1,158 @@ +[ + { + "description": "minLength validation", + "schema": { + "minLength": 2 + }, + "tests": [ + { + "description": "a string with length equal to minLength is valid", + "data": "fo", + "valid": true + }, + { + "description": "a longer string is valid", + "data": "foobar", + "valid": true + }, + { + "description": "a shorter string is not valid", + "data": "f", + "valid": false + }, + { + "description": "an empty string is not valid", + "data": "", + "valid": false + } + ] + }, + { + "description": "maxLength validation", + "schema": { + "maxLength": 2 + }, + "tests": [ + { + "description": "an empty string is valid", + "data": "", + "valid": true + }, + { + "description": "a string with length equal to maxLength is valid", + "data": "fo", + "valid": true + }, + { + "description": "a longer string is not valid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "pattern validation", + "schema": { + "pattern": "^[a-z]+$" + }, + "tests": [ + { + "description": "a lowercase string is valid", + "data": "foo", + "valid": true + }, + { + "description": "a string with uppercase is not valid", + "data": "Foo", + "valid": false + }, + { + "description": "a string with digits is not valid", + "data": "foo1", + "valid": false + }, + { + "description": "an empty string is not valid", + "data": "", + "valid": false + } + ] + }, + { + "description": "pattern with partial match", + "comment": "pattern matches if the pattern is found anywhere in the string", + "schema": { + "pattern": "\\d+" + }, + "tests": [ + { + "description": "a string containing digits is valid", + "data": "abc123", + "valid": true + }, + { + "description": "a string of only digits is valid", + "data": "123", + "valid": true + }, + { + "description": "a string without digits is not valid", + "data": "abc", + "valid": false + } + ] + }, + { + "description": "minLength and maxLength", + "schema": { + "minLength": 2, + "maxLength": 5 + }, + "tests": [ + { + "description": "string of length 2 is valid", + "data": "ab", + "valid": true + }, + { + "description": "string of length 5 is valid", + "data": "hello", + "valid": true + }, + { + "description": "string of length 3 is valid", + "data": "foo", + "valid": true + }, + { + "description": "string of length 1 is not valid", + "data": "a", + "valid": false + }, + { + "description": "string of length 6 is not valid", + "data": "toolong", + "valid": false + } + ] + }, + { + "description": "minLength with unicode characters", + "comment": "minLength counts Unicode code points, not bytes", + "schema": { + "minLength": 2 + }, + "tests": [ + { + "description": "two unicode code points is valid", + "data": "AB", + "valid": true + }, + { + "description": "one unicode code point is not valid", + "data": "A", + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/type.json b/tests/oas30/type.json new file mode 100644 index 0000000..954deb0 --- /dev/null +++ b/tests/oas30/type.json @@ -0,0 +1,281 @@ +[ + { + "description": "integer type matches integers", + "schema": { + "type": "integer" + }, + "tests": [ + { + "description": "an integer is valid", + "data": 1, + "valid": true + }, + { + "description": "a float with zero fractional part is an integer", + "data": 1.0, + "valid": true + }, + { + "description": "a float is not an integer", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an integer", + "data": "foo", + "valid": false + }, + { + "description": "an object is not an integer", + "data": {}, + "valid": false + }, + { + "description": "an array is not an integer", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an integer", + "data": true, + "valid": false + }, + { + "description": "null is not an integer", + "data": null, + "valid": false + } + ] + }, + { + "description": "number type matches numbers", + "schema": { + "type": "number" + }, + "tests": [ + { + "description": "an integer is a number", + "data": 1, + "valid": true + }, + { + "description": "a float is a number", + "data": 1.1, + "valid": true + }, + { + "description": "a string is not a number", + "data": "foo", + "valid": false + }, + { + "description": "an object is not a number", + "data": {}, + "valid": false + }, + { + "description": "an array is not a number", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a number", + "data": true, + "valid": false + }, + { + "description": "null is not a number", + "data": null, + "valid": false + } + ] + }, + { + "description": "string type matches strings", + "schema": { + "type": "string" + }, + "tests": [ + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "an empty string is a string", + "data": "", + "valid": true + }, + { + "description": "an integer is not a string", + "data": 1, + "valid": false + }, + { + "description": "a float is not a string", + "data": 1.1, + "valid": false + }, + { + "description": "an object is not a string", + "data": {}, + "valid": false + }, + { + "description": "an array is not a string", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a string", + "data": true, + "valid": false + }, + { + "description": "null is not a string", + "data": null, + "valid": false + } + ] + }, + { + "description": "boolean type matches booleans", + "schema": { + "type": "boolean" + }, + "tests": [ + { + "description": "true is valid", + "data": true, + "valid": true + }, + { + "description": "false is valid", + "data": false, + "valid": true + }, + { + "description": "an integer is not a boolean", + "data": 1, + "valid": false + }, + { + "description": "zero is not a boolean", + "data": 0, + "valid": false + }, + { + "description": "a string is not a boolean", + "data": "foo", + "valid": false + }, + { + "description": "an object is not a boolean", + "data": {}, + "valid": false + }, + { + "description": "an array is not a boolean", + "data": [], + "valid": false + }, + { + "description": "null is not a boolean", + "data": null, + "valid": false + } + ] + }, + { + "description": "object type matches objects", + "schema": { + "type": "object" + }, + "tests": [ + { + "description": "an object is valid", + "data": {}, + "valid": true + }, + { + "description": "an object with properties is valid", + "data": { + "key": "value" + }, + "valid": true + }, + { + "description": "an integer is not an object", + "data": 1, + "valid": false + }, + { + "description": "a string is not an object", + "data": "foo", + "valid": false + }, + { + "description": "an array is not an object", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an object", + "data": true, + "valid": false + }, + { + "description": "null is not an object", + "data": null, + "valid": false + } + ] + }, + { + "description": "array type matches arrays", + "schema": { + "type": "array" + }, + "tests": [ + { + "description": "an array is valid", + "data": [], + "valid": true + }, + { + "description": "an array with items is valid", + "data": [ + 1, + 2, + 3 + ], + "valid": true + }, + { + "description": "an integer is not an array", + "data": 1, + "valid": false + }, + { + "description": "a string is not an array", + "data": "foo", + "valid": false + }, + { + "description": "an object is not an array", + "data": {}, + "valid": false + }, + { + "description": "a boolean is not an array", + "data": true, + "valid": false + }, + { + "description": "null is not an array", + "data": null, + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/writeOnly.json b/tests/oas30/writeOnly.json new file mode 100644 index 0000000..66c0c69 --- /dev/null +++ b/tests/oas30/writeOnly.json @@ -0,0 +1,34 @@ +[ + { + "description": "writeOnly property", + "schema": { + "type": "object", + "properties": { + "password": { + "type": "string", + "writeOnly": true + }, + "name": { + "type": "string" + } + } + }, + "tests": [ + { + "description": "writeOnly is merely an annotation, no validation effect by default", + "data": { + "password": "secret", + "name": "foo" + }, + "valid": true + }, + { + "description": "valid without writeOnly property", + "data": { + "name": "foo" + }, + "valid": true + } + ] + } +] \ No newline at end of file diff --git a/tests/oas30/xml.json b/tests/oas30/xml.json new file mode 100644 index 0000000..90277a1 --- /dev/null +++ b/tests/oas30/xml.json @@ -0,0 +1,28 @@ +[ + { + "description": "xml object structural validation", + "comment": "The xml keyword itself doesn't typically validate the instance JSON payload directly, but validators sometimes check the schema validity. Here we provide a valid xml schema object.", + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "xml": { + "name": "User", + "namespace": "http://example.com/schema", + "prefix": "ex", + "attribute": false, + "wrapped": true + } + } + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"name": "Alice"}, + "valid": true + } + ] + } +] \ No newline at end of file