diff --git a/.github/workflows/annotation-tests.yml b/.github/workflows/annotation-tests.yml new file mode 100644 index 00000000..16de7b16 --- /dev/null +++ b/.github/workflows/annotation-tests.yml @@ -0,0 +1,21 @@ +name: Validate annotation tests + +on: + pull_request: + paths: + - "annotations/**" + +jobs: + annotate: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Deno + uses: denoland/setup-deno@v2 + with: + deno-version: "2.x" + + - name: Validate annotation tests + run: deno --node-modules-dir=auto --allow-read --no-prompt bin/annotation-tests.ts diff --git a/annotations/README.md b/annotations/README.md new file mode 100644 index 00000000..826f98cc --- /dev/null +++ b/annotations/README.md @@ -0,0 +1,104 @@ +# Annotation Tests + +The annotations Test Suite tests which annotations should appear (or not appear) +on which values of an instance. These tests are agnostic of any output format. + +## Supported Dialects + +Although the concept of annotations didn't appear in the spec until 2019-09, the +concept is compatible with every version of JSON Schema. Test Cases in this Test +Suite are designed to be compatible with as many releases of JSON Schema as +possible. They do not include `$schema` or `$id`/`id` keywords so that +implementations can run the same Test Suite for each dialect they support. + +Since this Test Suite can be used for a variety of dialects, there are a couple +of options that can be used by Test Runners to filter out Test Cases that don't +apply to the dialect under test. + +## Test Case Components + +### description + +A short description of what behavior the Test Case is covering. + +### compatibility + +The `compatibility` option allows you to set which dialects the Test Case is +compatible with. Test Runners can use this value to filter out Test Cases that +don't apply the to dialect currently under test. Dialects are indicated by the +number corresponding to their release. Date-based releases use just the year. + +If this option isn't present, it means the Test Case is compatible with any +dialect. + +If this option is present with a number, the number indicates the minimum +release the Test Case is compatible with. This example indicates that the Test +Case is compatible with draft-07 and up. + +**Example**: `"compatibility": "7"` + +You can use a `<=` operator to indicate that the Test Case is compatible with +releases less then or equal to the given release. This example indicates that +the Test Case is compatible with 2019-09 and under. + +**Example**: `"compatibility": "<=2019"` + +You can use comma-separated values to indicate multiple constraints if needed. +This example indicates that the Test Case is compatible with releases between +draft-06 and 2019-09. + +**Example**: `"compatibility": "6,<=2019"` + +For convenience, you can use the `=` operator to indicate a Test Case is only +compatible with a single release. This example indicates that the Test Case is +compatible only with 2020-12. + +**Example**: `"compatibility": "=2020"` + +### schema + +The schema that will serve as the subject for the tests. Whenever possible, this +schema shouldn't include `$schema` or `id`/`$id` because Test Cases should be +designed to work with as many releases as possible. + +### externalSchemas + +`externalSchemas` allows you to define additional schemas that `schema` makes +references to. The value is an object where the keys are retrieval URIs and +values are schemas. Most external schemas aren't self identifying (using +`id`/`$id`) and rely on the retrieval URI for identification. This is done to +increase the number of dialects that the test is compatible with. Because `id` +changed to `$id` in draft-06, if you use `$id`, the test becomes incompatible +with draft-03/4 and in most cases, that's not necessary. + +### tests + +A collection of Tests to run to verify the Test Case. + +## Test Components + +### instance + +The JSON instance to be annotated. + +### assertions + +`assertions` are a collection of assertions that must be true for the test to pass. + +## Assertions Components + +### location + +The instance location. + +### keyword + +The annotating keyword. + +### expected + +An array of annotations on the `keyword` - instance `location` pair. `expected` +is an array because there's always a chance that an annotation is applied +multiple times to any given instance location. The `expected` array should be +sorted such that the most recently encountered value for an annotation during +evaluation comes before previously encountered values. diff --git a/annotations/assertion.schema.json b/annotations/assertion.schema.json new file mode 100644 index 00000000..2272f304 --- /dev/null +++ b/annotations/assertion.schema.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + + "type": "object", + "properties": { + "location": { + "markdownDescription": "The instance location.", + "type": "string", + "format": "json-pointer" + }, + "keyword": { + "markdownDescription": "The annotation keyword.", + "type": "string" + }, + "expected": { + "markdownDescription": "An array of annotations on the `keyword` - instance `location` pair.", + "type": "array" + } + }, + "required": ["location", "keyword", "expected"] +} diff --git a/annotations/test-case.schema.json b/annotations/test-case.schema.json new file mode 100644 index 00000000..6df5f109 --- /dev/null +++ b/annotations/test-case.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + + "type": "object", + "properties": { + "description": { + "markdownDescription": "A short description of what behavior the Test Case is covering.", + "type": "string" + }, + "compatibility": { + "markdownDescription": "Set which dialects the Test Case is compatible with. Examples:\n- `\"7\"` -- draft-07 and above\n- `\"<=2019\"` -- 2019-09 and previous\n- `\"6,<=2019\"` -- Between draft-06 and 2019-09\n- `\"=2020\"` -- 2020-12 only", + "type": "string", + "pattern": "^(<=|=)?([123467]|2019|2020)(,(<=|=)?([123467]|2019|2020))*$" + }, + "schema": { + "markdownDescription": "This schema shouldn't include `$schema` or `id`/`$id` unless necesary for the test because Test Cases should be designed to work with as many releases as possible.", + "type": ["boolean", "object"] + }, + "externalSchemas": { + "markdownDescription": "The keys are retrieval URIs and values are schemas.", + "type": "object", + "patternProperties": { + "": { + "type": ["boolean", "object"] + } + }, + "propertyNames": { + "format": "uri" + } + }, + "tests": { + "markdownDescription": "A collection of Tests to run to verify the Test Case.", + "type": "array", + "items": { "$ref": "./test.schema.json" } + } + }, + "required": ["description", "schema", "tests"] +} diff --git a/annotations/test-suite.schema.json b/annotations/test-suite.schema.json new file mode 100644 index 00000000..c8b17f0d --- /dev/null +++ b/annotations/test-suite.schema.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "suite": { + "type": "array", + "items": { "$ref": "./test-case.schema.json" } + } + }, + "required": ["description", "suite"] +} diff --git a/annotations/test.schema.json b/annotations/test.schema.json new file mode 100644 index 00000000..5ab48933 --- /dev/null +++ b/annotations/test.schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + + "type": "object", + "properties": { + "instance": { + "markdownDescription": "The JSON instance to be annotated." + }, + "assertions": { + "markdownDescription": "An array of annotations on the `keyword` - instance `location` pair.", + "type": "array", + "items": { "$ref": "./assertion.schema.json" } + } + }, + "required": ["instance", "assertions"] +} diff --git a/annotations/tests/applicators.json b/annotations/tests/applicators.json new file mode 100644 index 00000000..801e6cee --- /dev/null +++ b/annotations/tests/applicators.json @@ -0,0 +1,459 @@ +{ + "$schema": "../test-suite.schema.json", + "description": "The applicator vocabulary", + "suite": [ + { + "description": "`properties`, `patternProperties`, and `additionalProperties`", + "compatibility": "3", + "schema": { + "properties": { + "foo": { + "title": "Foo" + } + }, + "patternProperties": { + "^a": { + "title": "Bar" + } + }, + "additionalProperties": { + "title": "Baz" + } + }, + "tests": [ + { + "instance": {}, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": [] + }, + { + "location": "#/apple", + "keyword": "title", + "expected": [] + }, + { + "location": "#/bar", + "keyword": "title", + "expected": [] + } + ] + }, + { + "instance": { + "foo": {}, + "apple": {}, + "baz": {} + }, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": [ + "Foo" + ] + }, + { + "location": "/apple", + "keyword": "title", + "expected": [ + "Bar" + ] + }, + { + "location": "/baz", + "keyword": "title", + "expected": [ + "Baz" + ] + } + ] + } + ] + }, + { + "description": "`propertyNames`", + "compatibility": "6", + "schema": { + "propertyNames": { + "const": "foo", + "title": "Foo" + } + }, + "tests": [ + { + "instance": { + "foo": 42 + }, + "assertions": [ + { + "location": "", + "keyword": "propertyNames", + "expected": [] + }, + { + "location": "/foo", + "keyword": "title", + "expected": [] + } + ] + } + ] + }, + { + "description": "`prefixItems` and `items`", + "compatibility": "2020", + "schema": { + "prefixItems": [ + { + "title": "Foo" + } + ], + "items": { + "title": "Bar" + } + }, + "tests": [ + { + "instance": [ + "foo", + "bar" + ], + "assertions": [ + { + "location": "/0", + "keyword": "title", + "expected": [ + "Foo" + ] + }, + { + "location": "/1", + "keyword": "title", + "expected": [ + "Bar" + ] + }, + { + "location": "/2", + "keyword": "title", + "expected": [] + } + ] + } + ] + }, + { + "description": "`contains`", + "compatibility": "6", + "schema": { + "contains": { + "type": "number", + "title": "Foo" + } + }, + "tests": [ + { + "instance": [ + "foo", + 42, + true + ], + "assertions": [ + { + "location": "/0", + "keyword": "title", + "expected": [] + }, + { + "location": "/1", + "keyword": "title", + "expected": [ + "Foo" + ] + }, + { + "location": "/2", + "keyword": "title", + "expected": [] + }, + { + "location": "/3", + "keyword": "title", + "expected": [] + } + ] + } + ] + }, + { + "description": "`allOf`", + "compatibility": "4", + "schema": { + "allOf": [ + { + "title": "Foo" + }, + { + "title": "Bar" + } + ] + }, + "tests": [ + { + "instance": "foo", + "assertions": [ + { + "location": "", + "keyword": "allOf", + "expected": [] + }, + { + "location": "", + "keyword": "title", + "expected": [ + "Bar", + "Foo" + ] + } + ] + } + ] + }, + { + "description": "`anyOf`", + "compatibility": "4", + "schema": { + "anyOf": [ + { + "type": "integer", + "title": "Foo" + }, + { + "type": "number", + "title": "Bar" + } + ] + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "anyOf", + "expected": [] + }, + { + "location": "", + "keyword": "title", + "expected": [ + "Bar", + "Foo" + ] + } + ] + }, + { + "instance": 4.2, + "assertions": [ + { + "location": "", + "keyword": "title", + "expected": [ + "Bar" + ] + } + ] + } + ] + }, + { + "description": "`oneOf`", + "compatibility": "4", + "schema": { + "oneOf": [ + { + "type": "string", + "title": "Foo" + }, + { + "type": "number", + "title": "Bar" + } + ] + }, + "tests": [ + { + "instance": "foo", + "assertions": [ + { + "location": "", + "keyword": "oneOf", + "expected": [] + }, + { + "location": "", + "keyword": "title", + "expected": [ + "Foo" + ] + } + ] + }, + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "title", + "expected": [ + "Bar" + ] + } + ] + } + ] + }, + { + "description": "`not`", + "compatibility": "4", + "schema": { + "title": "Foo", + "not": { + "not": { + "title": "Bar" + } + } + }, + "tests": [ + { + "instance": {}, + "assertions": [ + { + "location": "", + "keyword": "not", + "expected": [] + }, + { + "location": "", + "keyword": "title", + "expected": [ + "Foo" + ] + } + ] + } + ] + }, + { + "description": "`dependentSchemas`", + "compatibility": "2019", + "schema": { + "dependentSchemas": { + "foo": { + "title": "Foo" + } + } + }, + "tests": [ + { + "instance": { + "foo": 42 + }, + "assertions": [ + { + "keyword": "dependentSchemas", + "location": "", + "expected": [] + }, + { + "keyword": "title", + "location": "", + "expected": [ + "Foo" + ] + } + ] + }, + { + "instance": { + "foo": 42 + }, + "assertions": [ + { + "keyword": "title", + "location": "/foo", + "expected": [] + } + ] + } + ] + }, + { + "description": "`if`, `then`, and `else`", + "compatibility": "7", + "schema": { + "if": { + "title": "If", + "type": "string" + }, + "then": { + "title": "Then" + }, + "else": { + "title": "Else" + } + }, + "tests": [ + { + "instance": "foo", + "assertions": [ + { + "location": "", + "keyword": "if", + "expected": [] + }, + { + "location": "", + "keyword": "then", + "expected": [] + }, + { + "location": "", + "keyword": "title", + "expected": [ + "Then", + "If" + ] + } + ] + }, + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "if", + "expected": [] + }, + { + "location": "", + "keyword": "else", + "expected": [] + }, + { + "location": "", + "keyword": "title", + "expected": [ + "Else" + ] + } + ] + } + ] + } + ] +} diff --git a/annotations/tests/content.json b/annotations/tests/content.json new file mode 100644 index 00000000..fe16e98b --- /dev/null +++ b/annotations/tests/content.json @@ -0,0 +1,64 @@ +{ + "$schema": "../test-suite.schema.json", + "description": "The content vocabulary", + "suite": [ + { + "description": "`contentMediaType` is an annotation", + "compatibility": "7", + "schema": { + "contentMediaType": "application/json" + }, + "tests": [ + { + "instance": "{ \"foo\": \"bar\" }", + "assertions": [ + { + "location": "", + "keyword": "contentMediaType", + "expected": ["application/json"] + } + ] + } + ] + }, + { + "description": "`contentEncoding` is an annotation", + "compatibility": "7", + "schema": { + "contentEncoding": "base64" + }, + "tests": [ + { + "instance": "SGVsbG8gZnJvbSBKU09OIFNjaGVtYQ==", + "assertions": [ + { + "location": "", + "keyword": "contentEncoding", + "expected": ["base64"] + } + ] + } + ] + }, + { + "description": "`contentSchema` is an annotation", + "compatibility": "2019", + "schema": { + "$id": "https://annotations.json-schema.org/test/contentSchema-is-an-annotation", + "contentSchema": { "type": "number" } + }, + "tests": [ + { + "instance": "42", + "assertions": [ + { + "location": "", + "keyword": "contentSchema", + "expected": ["https://annotations.json-schema.org/test/contentSchema-is-an-annotation#/contentSchema"] + } + ] + } + ] + } + ] +} diff --git a/annotations/tests/core.json b/annotations/tests/core.json new file mode 100644 index 00000000..0c6f3cea --- /dev/null +++ b/annotations/tests/core.json @@ -0,0 +1,38 @@ +{ + "$schema": "../test-suite.schema.json", + "description": "The core vocabulary", + "suite": [ + { + "description": "`$ref` and `$defs`", + "compatibility": "2019", + "schema": { + "$ref": "#/$defs/foo", + "$defs": { + "foo": { "title": "Foo" } + } + }, + "tests": [ + { + "instance": "foo", + "assertions": [ + { + "location": "", + "keyword": "$ref", + "expected": [] + }, + { + "location": "", + "keyword": "$defs", + "expected": [] + }, + { + "location": "", + "keyword": "title", + "expected": ["Foo"] + } + ] + } + ] + } + ] +} diff --git a/annotations/tests/format.json b/annotations/tests/format.json new file mode 100644 index 00000000..a5ec6bbb --- /dev/null +++ b/annotations/tests/format.json @@ -0,0 +1,24 @@ +{ + "$schema": "../test-suite.schema.json", + "description": "The format vocabulary", + "suite": [ + { + "description": "`format` is an annotation", + "schema": { + "format": "email" + }, + "tests": [ + { + "instance": "foo@bar.com", + "assertions": [ + { + "location": "", + "keyword": "format", + "expected": ["email"] + } + ] + } + ] + } + ] +} diff --git a/annotations/tests/meta-data.json b/annotations/tests/meta-data.json new file mode 100644 index 00000000..6123d909 --- /dev/null +++ b/annotations/tests/meta-data.json @@ -0,0 +1,136 @@ +{ + "$schema": "../test-suite.schema.json", + "description": "The meta-data vocabulary", + "suite": [ + { + "description": "`title` is an annotation", + "schema": { + "title": "Foo" + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "title", + "expected": ["Foo"] + } + ] + } + ] + }, + { + "description": "`description` is an annotation", + "schema": { + "description": "Foo" + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "description", + "expected": ["Foo"] + } + ] + } + ] + }, + { + "description": "`default` is an annotation", + "schema": { + "default": "Foo" + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "default", + "expected": ["Foo"] + } + ] + } + ] + }, + { + "description": "`deprecated` is an annotation", + "compatibility": "2019", + "schema": { + "deprecated": true + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "deprecated", + "expected": [true] + } + ] + } + ] + }, + { + "description": "`readOnly` is an annotation", + "compatibility": "7", + "schema": { + "readOnly": true + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "readOnly", + "expected": [true] + } + ] + } + ] + }, + { + "description": "`writeOnly` is an annotation", + "compatibility": "7", + "schema": { + "writeOnly": true + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "writeOnly", + "expected": [true] + } + ] + } + ] + }, + { + "description": "`examples` is an annotation", + "compatibility": "6", + "schema": { + "examples": ["Foo", "Bar"] + }, + "tests": [ + { + "instance": "Foo", + "assertions": [ + { + "location": "", + "keyword": "examples", + "expected": [["Foo", "Bar"]] + } + ] + } + ] + } + ] +} diff --git a/annotations/tests/unevaluated.json b/annotations/tests/unevaluated.json new file mode 100644 index 00000000..9a72e6dd --- /dev/null +++ b/annotations/tests/unevaluated.json @@ -0,0 +1,579 @@ +{ + "$schema": "../test-suite.schema.json", + "description": "The unevaluated vocabulary", + "suite": [ + { + "description": "`unevaluatedProperties` alone", + "compatibility": "2019", + "schema": { + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "#/foo", + "keyword": "title", + "expected": ["Unevaluated"] + }, + { + "location": "#/bar", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `properties`", + "compatibility": "2019", + "schema": { + "properties": { + "foo": { "title": "Evaluated" } + }, + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "#/foo", + "keyword": "title", + "expected": ["Evaluated"] + }, + { + "location": "#/bar", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `patternProperties`", + "compatibility": "2019", + "schema": { + "patternProperties": { + "^a": { "title": "Evaluated" } + }, + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "apple": 42, "bar": 24 }, + "assertions": [ + { + "location": "#/apple", + "keyword": "title", + "expected": ["Evaluated"] + }, + { + "location": "#/bar", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `additionalProperties`", + "compatibility": "2019", + "schema": { + "additionalProperties": { "title": "Evaluated" }, + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "#/foo", + "keyword": "title", + "expected": ["Evaluated"] + }, + { + "location": "#/bar", + "keyword": "title", + "expected": ["Evaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `dependentSchemas`", + "compatibility": "2019", + "schema": { + "dependentSchemas": { + "foo": { + "properties": { + "bar": { "title": "Evaluated" } + } + } + }, + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "#/foo", + "keyword": "title", + "expected": ["Unevaluated"] + }, + { + "location": "#/bar", + "keyword": "title", + "expected": ["Evaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `if`, `then`, and `else`", + "compatibility": "2019", + "schema": { + "if": { + "properties": { + "foo": { + "type": "string", + "title": "If" + } + } + }, + "then": { + "properties": { + "foo": { "title": "Then" } + } + }, + "else": { + "properties": { + "foo": { "title": "Else" } + } + }, + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": "", "bar": 42 }, + "assertions": [ + { + "location": "#/foo", + "keyword": "title", + "expected": ["Then", "If"] + }, + { + "location": "#/bar", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + }, + { + "instance": { "foo": 42, "bar": "" }, + "assertions": [ + { + "location": "#/foo", + "keyword": "title", + "expected": ["Else"] + }, + { + "location": "#/bar", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `allOf`", + "compatibility": "2019", + "schema": { + "allOf": [ + { + "properties": { + "foo": { "title": "Evaluated" } + } + } + ], + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "#/foo", + "keyword": "title", + "expected": ["Evaluated"] + }, + { + "location": "#/bar", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `anyOf`", + "compatibility": "2019", + "schema": { + "anyOf": [ + { + "properties": { + "foo": { "title": "Evaluated" } + } + } + ], + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "#/foo", + "keyword": "title", + "expected": ["Evaluated"] + }, + { + "location": "#/bar", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `oneOf`", + "compatibility": "2019", + "schema": { + "oneOf": [ + { + "properties": { + "foo": { "title": "Evaluated" } + } + } + ], + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "#/foo", + "keyword": "title", + "expected": ["Evaluated"] + }, + { + "location": "#/bar", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `not`", + "compatibility": "2019", + "schema": { + "not": { + "not": { + "properties": { + "foo": { "title": "Evaluated" } + } + } + }, + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "#/foo", + "keyword": "title", + "expected": ["Unevaluated"] + }, + { + "location": "#/bar", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` alone", + "compatibility": "2019", + "schema": { + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": [42, 24], + "assertions": [ + { + "location": "#/0", + "keyword": "title", + "expected": ["Unevaluated"] + }, + { + "location": "#/1", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` with `prefixItems`", + "compatibility": "2020", + "schema": { + "prefixItems": [{ "title": "Evaluated" }], + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": [42, 24], + "assertions": [ + { + "location": "#/0", + "keyword": "title", + "expected": ["Evaluated"] + }, + { + "location": "#/1", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` with `contains`", + "compatibility": "2020", + "schema": { + "contains": { + "type": "string", + "title": "Evaluated" + }, + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": ["foo", 42], + "assertions": [ + { + "location": "#/0", + "keyword": "title", + "expected": ["Evaluated"] + }, + { + "location": "#/1", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` with `if`, `then`, and `else`", + "compatibility": "2019", + "schema": { + "if": { + "prefixItems": [ + { + "type": "string", + "title": "If" + } + ] + }, + "then": { + "prefixItems": [ + { "title": "Then" } + ] + }, + "else": { + "prefixItems": [ + { "title": "Else" } + ] + }, + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": ["", 42], + "assertions": [ + { + "location": "#/0", + "keyword": "title", + "expected": ["Then", "If"] + }, + { + "location": "#/1", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + }, + { + "instance": [42, ""], + "assertions": [ + { + "location": "#/0", + "keyword": "title", + "expected": ["Else"] + }, + { + "location": "#/1", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` with `allOf`", + "compatibility": "2019", + "schema": { + "allOf": [ + { + "prefixItems": [ + { "title": "Evaluated" } + ] + } + ], + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": [42, 24], + "assertions": [ + { + "location": "#/0", + "keyword": "title", + "expected": ["Evaluated"] + }, + { + "location": "#/1", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` with `anyOf`", + "compatibility": "2019", + "schema": { + "anyOf": [ + { + "prefixItems": [ + { "title": "Evaluated" } + ] + } + ], + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": [42, 24], + "assertions": [ + { + "location": "#/0", + "keyword": "title", + "expected": ["Evaluated"] + }, + { + "location": "#/1", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` with `oneOf`", + "compatibility": "2019", + "schema": { + "oneOf": [ + { + "prefixItems": [ + { "title": "Evaluated" } + ] + } + ], + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": [42, 24], + "assertions": [ + { + "location": "#/0", + "keyword": "title", + "expected": ["Evaluated"] + }, + { + "location": "#/1", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` with `not`", + "compatibility": "2019", + "schema": { + "not": { + "not": { + "prefixItems": [ + { "title": "Evaluated" } + ] + } + }, + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": [42, 24], + "assertions": [ + { + "location": "/0", + "keyword": "title", + "expected": ["Unevaluated"] + }, + { + "location": "/1", + "keyword": "title", + "expected": ["Unevaluated"] + } + ] + } + ] + } + ] +} diff --git a/annotations/tests/unknown.json b/annotations/tests/unknown.json new file mode 100644 index 00000000..85a92e00 --- /dev/null +++ b/annotations/tests/unknown.json @@ -0,0 +1,25 @@ +{ + "$schema": "../test-suite.schema.json", + "description": "Unknown keywords", + "suite": [ + { + "description": "`unknownKeyword` is an annotation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unknownKeyword": "Foo" + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "unknownKeyword", + "expected": ["Foo"] + } + ] + } + ] + } + ] +} diff --git a/annotations/tests/validation.json b/annotations/tests/validation.json new file mode 100644 index 00000000..18189b89 --- /dev/null +++ b/annotations/tests/validation.json @@ -0,0 +1,341 @@ +{ + "$schema": "../test-suite.schema.json", + "description": "The validation library", + "suite": [ + { + "description": "`const` doesn't produce annotations", + "compatibility": "6", + "schema": { + "const": "foo" + }, + "tests": [ + { + "instance": "foo", + "assertions": [ + { + "location": "", + "keyword": "const", + "expected": [] + } + ] + } + ] + }, + { + "description": "`dependentRequired` doesn't produce annotations", + "compatibility": "2019", + "schema": { + "dependentRequired": { + "foo": ["bar"] + } + }, + "tests": [ + { + "instance": { "foo": 1, "bar": 2 }, + "assertions": [ + { + "location": "", + "keyword": "dependentRequired", + "expected": [] + } + ] + } + ] + }, + { + "description": "`enum` doesn't produce annotations", + "schema": { + "enum": ["foo"] + }, + "tests": [ + { + "instance": "foo", + "assertions": [ + { + "location": "", + "keyword": "enum", + "expected": [] + } + ] + } + ] + }, + { + "description": "`exclusiveMaximum` doesn't produce annotations", + "compatibility": "3", + "schema": { + "exclusiveMaximum": 50 + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "exclusiveMaximum", + "expected": [] + } + ] + } + ] + }, + { + "description": "`exclusiveMinimum` doesn't produce annotations", + "compatibility": "3", + "schema": { + "exclusiveMinimum": 5 + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "exclusiveMinimum", + "expected": [] + } + ] + } + ] + }, + { + "description": "`maxItems` doesn't produce annotations", + "schema": { + "maxItems": 42 + }, + "tests": [ + { + "instance": ["foo"], + "assertions": [ + { + "location": "", + "keyword": "maxItems", + "expected": [] + } + ] + } + ] + }, + { + "description": "`maxLength` doesn't produce annotations", + "schema": { + "maxLength": 42 + }, + "tests": [ + { + "instance": "foo", + "assertions": [ + { + "location": "", + "keyword": "maxLength", + "expected": [] + } + ] + } + ] + }, + { + "description": "`maxProperties` doesn't produce annotations", + "compatibility": "4", + "schema": { + "maxProperties": 42 + }, + "tests": [ + { + "instance": { "foo": 42 }, + "assertions": [ + { + "location": "", + "keyword": "maxProperties", + "expected": [] + } + ] + } + ] + }, + { + "description": "`maximum` doesn't produce annotations", + "schema": { + "maximum": 50 + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "maximum", + "expected": [] + } + ] + } + ] + }, + { + "description": "`minItems` doesn't produce annotations", + "schema": { + "minItems": 2 + }, + "tests": [ + { + "instance": ["a", "b"], + "assertions": [ + { + "location": "", + "keyword": "minItems", + "expected": [] + } + ] + } + ] + }, + { + "description": "`minLength` doesn't produce annotations", + "schema": { + "minLength": 2 + }, + "tests": [ + { + "instance": "foo", + "assertions": [ + { + "location": "", + "keyword": "minLength", + "expected": [] + } + ] + } + ] + }, + { + "description": "`minProperties` doesn't produce annotations", + "compatibility": "4", + "schema": { + "minProperties": 2 + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "", + "keyword": "minProperties", + "expected": [] + } + ] + } + ] + }, + { + "description": "`minimum` doesn't produce annotations", + "schema": { + "minimum": 42 + }, + "tests": [ + { + "instance": 50, + "assertions": [ + { + "location": "", + "keyword": "minimum", + "expected": [] + } + ] + } + ] + }, + { + "description": "`multipleOf` doesn't produce annotations", + "compatibility": "4", + "schema": { + "multipleOf": 2 + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "multipleOf", + "expected": [] + } + ] + } + ] + }, + { + "description": "`pattern` doesn't produce annotations", + "schema": { + "pattern": ".*" + }, + "tests": [ + { + "instance": "foo", + "assertions": [ + { + "location": "", + "keyword": "pattern", + "expected": [] + } + ] + } + ] + }, + { + "description": "`required` doesn't produce annotations", + "compatibility": "3", + "schema": { + "required": ["foo"] + }, + "tests": [ + { + "instance": { "foo": 42 }, + "assertions": [ + { + "location": "", + "keyword": "required", + "expected": [] + } + ] + } + ] + }, + { + "description": "`type` doesn't produce annotations", + "schema": { + "type": "string" + }, + "tests": [ + { + "instance": "foo", + "assertions": [ + { + "location": "", + "keyword": "type", + "expected": [] + } + ] + } + ] + }, + { + "description": "`uniqueItems` doesn't produce annotations", + "compatibility": "2", + "schema": { + "uniqueItems": true + }, + "tests": [ + { + "instance": ["foo", "bar"], + "assertions": [ + { + "location": "", + "keyword": "uniqueItems", + "expected": [] + } + ] + } + ] + } + ] +} diff --git a/bin/annotation-tests.ts b/bin/annotation-tests.ts new file mode 100755 index 00000000..2d3d1932 --- /dev/null +++ b/bin/annotation-tests.ts @@ -0,0 +1,31 @@ +#!/usr/bin/env deno +import { validate } from "npm:@hyperjump/json-schema/draft-07"; +import { BASIC } from "npm:@hyperjump/json-schema/experimental"; + +const validateTestSuite = await validate("./annotations/test-suite.schema.json"); + +console.log("Validating annotation tests ..."); + +let isValid = true; +for await (const entry of Deno.readDir("./annotations/tests")) { + if (entry.isFile) { + const json = await Deno.readTextFile(`./annotations/tests/${entry.name}`); + const suite = JSON.parse(json); + + const output = validateTestSuite(suite, BASIC); + + if (output.valid) { + console.log(`\x1b[32m✔\x1b[0m ${entry.name}`); + } else { + isValid = false; + console.log(`\x1b[31m✖\x1b[0m ${entry.name}`); + console.log(output); + } + } +} + +console.log("Done."); + +if (!isValid) { + Deno.exit(1); +}