From 1a9d15ccb7e653e875a088f4960efc4e2f836afd Mon Sep 17 00:00:00 2001 From: Niklas Kiefer Date: Fri, 4 Feb 2022 14:48:38 +0100 Subject: [PATCH] feat(cloud): add cloud JSON Schema Related to bpmn-io/bpmn-js-properties-panel#561 --- resources/cloud.json | 535 ++++++++++++++++++ src/cloud.json | 43 ++ src/defs/cloud-properties.json | 233 ++++++++ .../fixtures/cloud-optional-inputs-outputs.js | 30 + test/fixtures/cloud-optional-invalid-type.js | 70 +++ test/fixtures/cloud-rest-connector.js | 73 +++ test/fixtures/invalid-binding-type-cloud.js | 72 +++ test/fixtures/invalid-zeebe-input-type.js | 80 +++ test/fixtures/invalid-zeebe-output-type.js | 80 +++ ...invalid-zeebe-task-definition-type-type.js | 78 +++ .../invalid-zeebe-task-header-type.js | 80 +++ .../missing-binding-zeebe-header-key.js | 66 +++ .../missing-binding-zeebe-input-name.js | 66 +++ .../missing-binding-zeebe-output-source.js | 74 +++ test/spec/cloud.validationSpec.js | 185 ++++++ test/spec/schemaSpec.js | 15 + 16 files changed, 1780 insertions(+) create mode 100644 resources/cloud.json create mode 100644 src/cloud.json create mode 100644 src/defs/cloud-properties.json create mode 100644 test/fixtures/cloud-optional-inputs-outputs.js create mode 100644 test/fixtures/cloud-optional-invalid-type.js create mode 100644 test/fixtures/cloud-rest-connector.js create mode 100644 test/fixtures/invalid-binding-type-cloud.js create mode 100644 test/fixtures/invalid-zeebe-input-type.js create mode 100644 test/fixtures/invalid-zeebe-output-type.js create mode 100644 test/fixtures/invalid-zeebe-task-definition-type-type.js create mode 100644 test/fixtures/invalid-zeebe-task-header-type.js create mode 100644 test/fixtures/missing-binding-zeebe-header-key.js create mode 100644 test/fixtures/missing-binding-zeebe-input-name.js create mode 100644 test/fixtures/missing-binding-zeebe-output-source.js create mode 100644 test/spec/cloud.validationSpec.js diff --git a/resources/cloud.json b/resources/cloud.json new file mode 100644 index 0000000..ec20cb1 --- /dev/null +++ b/resources/cloud.json @@ -0,0 +1,535 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://camunda.org/schema/cloud-element-templates/1.0", + "title": "Cloud Element Template Schema", + "description": "A single element template configuration or an array of element template configurations", + "definitions": { + "properties": { + "allOf": [ + { + "type": "array", + "title": "element template properties", + "description": "The properties of the element template", + "default": [], + "items": { + "type": "object", + "title": "element template property", + "description": "A property defined for the element template", + "default": {}, + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "Dropdown" + } + }, + "required": [ + "type" + ] + }, + "then": { + "required": [ + "choices" + ], + "errorMessage": "must provide choices=[] with \"Dropdown\" type" + } + } + ], + "properties": { + "value": { + "$id": "#/properties/property/value", + "type": [ + "string", + "boolean" + ], + "title": "property value", + "description": "The value of the control field for the property" + }, + "description": { + "$id": "#/properties/property/description", + "type": "string", + "title": "property description", + "description": "The description of the control field" + }, + "label": { + "$id": "#/properties/property/label", + "type": "string", + "title": "property label", + "description": "The label of the control field for the property" + }, + "type": { + "$id": "#/properties/property/type", + "type": "string", + "title": "property type", + "description": "The type of the control field" + }, + "editable": { + "$id": "#/properties/property/editable", + "type": "boolean", + "title": "property editable", + "description": "Indicates whether the property is editable or not" + }, + "choices": { + "$id": "#/properties/property/choices", + "type": "array", + "title": "property choices", + "description": "The choices for dropdown properties", + "items": { + "$id": "#/properties/property/choices/item", + "type": "object", + "properties": { + "name": { + "$id": "#/properties/property/choices/item/name", + "type": "string", + "title": "choice name", + "description": "The name of the choice" + }, + "value": { + "$id": "#/properties/property/choices/item/value", + "type": "string", + "title": "choice value", + "description": "The value of the choice" + } + }, + "required": [ + "value", + "name" + ], + "errorMessage": "{ name, value } must be specified for \"Dropdown\" choices" + } + }, + "constraints": { + "$id": "#/properties/property/constraints", + "type": "object", + "title": "property constraints", + "description": "The validation constraints", + "properties": { + "notEmpty": { + "$id": "#/properties/property/constraints/notEmpty", + "type": "boolean", + "title": "property constraints not empty", + "description": "The control field must not be empty" + }, + "minLength": { + "$id": "#/properties/property/constraints/minLength", + "type": "number", + "title": "property constraints min length", + "description": "The minimal length for the control field value" + }, + "maxLength": { + "$id": "#/properties/property/constraints/maxLength", + "type": "number", + "title": "property constraints max length", + "description": "The maximal length for the control field value" + }, + "pattern": { + "$id": "#/properties/property/constraints/pattern", + "title": "property constraints pattern", + "description": "A regular expression pattern for the constraints", + "oneOf": [ + { + "type": "object", + "properties": { + "value": { + "$id": "#/properties/property/constraints/pattern/value", + "type": "string", + "title": "property constraints pattern value", + "description": "The regular expression of the pattern constraint" + }, + "message": { + "$id": "#/properties/property/constraints/pattern/message", + "type": "string", + "title": "property constraints pattern message", + "description": "The validation message of the pattern constraint" + } + } + }, + { + "type": "string" + } + ] + } + } + }, + "group": { + "$id": "#/properties/property/group", + "type": "string", + "title": "property group", + "description": "The custom group of the control field for the property" + } + } + } + }, + { + "$schema": "http://json-schema.org/draft-07/schema", + "type": "array", + "title": "element template properties", + "description": "The properties of the element template", + "default": [], + "items": { + "type": "object", + "title": "element template property", + "description": "A property defined for the element template", + "default": {}, + "required": [ + "binding" + ], + "errorMessage": { + "required": { + "binding": "missing binding for property \"${0#}\"" + } + }, + "allOf": [ + { + "if": { + "properties": { + "binding": { + "properties": { + "type": { + "const": "property" + } + }, + "required": [ + "type" + ] + } + }, + "required": [ + "binding" + ] + }, + "then": { + "properties": { + "type": { + "enum": [ + "String", + "Text", + "Hidden", + "Dropdown", + "Boolean" + ], + "errorMessage": "invalid property type ${0} for binding type \"property\"; must be any of { String, Text, Hidden, Dropdown, Boolean }" + } + } + } + }, + { + "if": { + "properties": { + "binding": { + "properties": { + "type": { + "enum": [ + "zeebe:input", + "zeebe:output", + "zeebe:taskHeader", + "zeebe:taskDefinition:type" + ] + } + }, + "required": [ + "type" + ] + } + }, + "required": [ + "binding" + ] + }, + "then": { + "properties": { + "type": { + "enum": [ + "String", + "Text", + "Hidden", + "Dropdown" + ], + "errorMessage": "invalid property type ${0} for binding type ${1/binding/type}; must be any of { String, Text, Hidden, Dropdown }" + } + } + } + }, + { + "if": { + "properties": { + "optional": { + "const": true + } + }, + "required": [ + "optional" + ] + }, + "then": { + "properties": { + "binding": { + "properties": { + "type": { + "enum": [ + "zeebe:input", + "zeebe:output" + ], + "errorMessage": "optional is not supported for binding type ${0}; must be any of { zeebe:input, zeebe:output }" + } + }, + "required": [ + "type" + ] + } + } + } + } + ], + "properties": { + "binding": { + "$id": "#/properties/property/binding", + "type": "object", + "title": "property binding", + "description": "A binding to a BPMN 2.0 property", + "required": [ + "type" + ], + "allOf": [ + { + "if": { + "properties": { + "type": { + "enum": [ + "property", + "zeebe:input" + ] + } + }, + "required": [ + "type" + ] + }, + "then": { + "required": [ + "name" + ], + "errorMessage": "property.binding ${0/type} requires name" + } + }, + { + "if": { + "properties": { + "type": { + "const": "zeebe:output" + } + }, + "required": [ + "type" + ] + }, + "then": { + "required": [ + "source" + ], + "errorMessage": "property.binding ${0/type} requires source" + } + }, + { + "if": { + "properties": { + "type": { + "const": "zeebe:taskHeader" + } + }, + "required": [ + "type" + ] + }, + "then": { + "required": [ + "key" + ], + "errorMessage": "property.binding ${0/type} requires key" + } + } + ], + "properties": { + "type": { + "$id": "#/properties/property/binding/type", + "type": "string", + "title": "property binding type", + "enum": [ + "property", + "zeebe:taskDefinition:type", + "zeebe:input", + "zeebe:output", + "zeebe:taskHeader" + ], + "errorMessage": "invalid property.binding type ${0}; must be any of { property, zeebe:taskDefinition:type, zeebe:input, zeebe:output, zeebe:taskHeader }", + "description": "The type of the property binding" + }, + "name": { + "$id": "#/properties/property/binding/name", + "type": "string", + "title": "property binding name", + "description": "The name of binding xml property" + }, + "source": { + "$id": "#/properties/property/binding/source", + "type": "string", + "title": "property binding source", + "description": "The source value of a property binding (zeebe:output)" + }, + "key": { + "$id": "#/properties/property/binding/key", + "type": "string", + "title": "property binding key", + "description": "The key value of a property binding (zeebe:taskHeader)" + } + } + }, + "optional": { + "$id": "#/optional", + "type": "boolean", + "title": "element template optional", + "description": "Indicates whether a property is optional" + } + } + } + } + ] + }, + "template": { + "type": "object", + "allOf": [ + { + "required": [ + "name", + "id", + "appliesTo", + "properties" + ], + "properties": { + "name": { + "$id": "#/name", + "type": "string", + "title": "element template name", + "description": "The name of the element template" + }, + "id": { + "$id": "#/id", + "type": "string", + "title": "element template id", + "description": "The identifier of the element template" + }, + "description": { + "$id": "#/description", + "type": "string", + "title": "element template description", + "description": "The description of the element template" + }, + "version": { + "$id": "#/version", + "type": "number", + "title": "element template version", + "description": "The version of the element template" + }, + "isDefault": { + "$id": "#/isDefault", + "type": "boolean", + "title": "element template is default", + "description": "Indicates whether the element template is a default template" + }, + "appliesTo": { + "$id": "#/appliesTo", + "type": "array", + "title": "element template applies to", + "description": "The definition for which element types the element template can be applied", + "default": [], + "items": { + "$id": "#/appliesTo/items", + "type": "string", + "pattern": "^(.*?:)", + "errorMessage": { + "pattern": "invalid item for \"appliesTo\", should contain namespaced property, example: \"bpmn:Task\"" + } + } + }, + "metadata": { + "$id": "#/metadata", + "type": "object", + "title": "element template metadata", + "description": "Some metadata for further configuration" + }, + "entriesVisible": { + "$id": "#/entriesVisible", + "type": "boolean", + "title": "element template entries visible", + "description": "Select whether non-template entries are visible in the properties panel" + }, + "groups": { + "$id": "#/groups", + "type": "array", + "title": "element template properties groups", + "description": "The custom defined groups of the element template", + "default": [], + "items": { + "$id": "#/groups/group", + "type": "object", + "title": "element template group", + "description": "A custom defined group for the element template", + "default": {}, + "required": [ + "id", + "label" + ], + "errorMessage": { + "required": { + "id": "missing id for group \"${0#}\"", + "label": "missing label for group \"${0#}\"" + } + }, + "properties": { + "id": { + "$id": "#/groups/group/id", + "type": "string", + "title": "group id", + "description": "The id of the custom group" + }, + "label": { + "$id": "#/groups/group/label", + "type": "string", + "title": "group label", + "description": "The label of the custom group" + } + } + } + } + }, + "errorMessage": { + "required": { + "name": "missing template name", + "id": "missing template id", + "appliesTo": "missing appliesTo=[]", + "properties": "missing properties=[]" + } + } + } + ], + "properties": { + "properties": { + "$ref": "#/definitions/properties", + "$id": "#/properties" + } + } + } + }, + "oneOf": [ + { + "$ref": "#/definitions/template" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/template" + } + } + ] +} \ No newline at end of file diff --git a/src/cloud.json b/src/cloud.json new file mode 100644 index 0000000..7464c34 --- /dev/null +++ b/src/cloud.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://camunda.org/schema/cloud-element-templates/1.0", + "title": "Cloud Element Template Schema", + "description": "A single element template configuration or an array of element template configurations", + "definitions": { + "properties": { + "allOf": [ + { + "$ref": "src/defs/base-properties.json" + }, + { + "$ref": "src/defs/cloud-properties.json" + } + ] + }, + "template": { + "type": "object", + "allOf": [ + { + "$ref": "src/defs/base.json" + } + ], + "properties": { + "properties": { + "$ref": "#/definitions/properties", + "$id": "#/properties" + } + } + } + }, + "oneOf": [ + { + "$ref": "#/definitions/template" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/template" + } + } + ] +} \ No newline at end of file diff --git a/src/defs/cloud-properties.json b/src/defs/cloud-properties.json new file mode 100644 index 0000000..c24d8c4 --- /dev/null +++ b/src/defs/cloud-properties.json @@ -0,0 +1,233 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "array", + "title": "element template properties", + "description": "The properties of the element template", + "default": [], + "items": { + "type": "object", + "title": "element template property", + "description": "A property defined for the element template", + "default": {}, + "required": [ + "binding" + ], + "errorMessage": { + "required": { + "binding": "missing binding for property \"${0#}\"" + } + }, + "allOf": [ + { + "if": { + "properties": { + "binding": { + "properties": { + "type": { + "const": "property" + } + }, + "required": [ + "type" + ] + } + }, + "required": [ + "binding" + ] + }, + "then": { + "properties": { + "type": { + "enum": [ + "String", + "Text", + "Hidden", + "Dropdown", + "Boolean" + ], + "errorMessage": "invalid property type ${0} for binding type \"property\"; must be any of { String, Text, Hidden, Dropdown, Boolean }" + } + } + } + }, + { + "if": { + "properties": { + "binding": { + "properties": { + "type": { + "enum": [ + "zeebe:input", + "zeebe:output", + "zeebe:taskHeader", + "zeebe:taskDefinition:type" + ] + } + }, + "required": [ + "type" + ] + } + }, + "required": [ + "binding" + ] + }, + "then": { + "properties": { + "type": { + "enum": [ + "String", + "Text", + "Hidden", + "Dropdown" + ], + "errorMessage": "invalid property type ${0} for binding type ${1/binding/type}; must be any of { String, Text, Hidden, Dropdown }" + } + } + } + }, + { + "if": { + "properties": { + "optional": { + "const": true + } + }, + "required": [ + "optional" + ] + }, + "then": { + "properties": { + "binding": { + "properties": { + "type": { + "enum": [ + "zeebe:input", + "zeebe:output" + ], + "errorMessage": "optional is not supported for binding type ${0}; must be any of { zeebe:input, zeebe:output }" + } + }, + "required": [ + "type" + ] + } + } + } + } + ], + "properties": { + "binding": { + "$id": "#/properties/property/binding", + "type": "object", + "title": "property binding", + "description": "A binding to a BPMN 2.0 property", + "required": [ + "type" + ], + "allOf": [ + { + "if": { + "properties": { + "type": { + "enum": [ + "property", + "zeebe:input" + ] + } + }, + "required": [ + "type" + ] + }, + "then": { + "required": [ + "name" + ], + "errorMessage": "property.binding ${0/type} requires name" + } + }, + { + "if": { + "properties": { + "type": { + "const": "zeebe:output" + } + }, + "required": [ + "type" + ] + }, + "then": { + "required": [ + "source" + ], + "errorMessage": "property.binding ${0/type} requires source" + } + }, + { + "if": { + "properties": { + "type": { + "const": "zeebe:taskHeader" + } + }, + "required": [ + "type" + ] + }, + "then": { + "required": [ + "key" + ], + "errorMessage": "property.binding ${0/type} requires key" + } + } + ], + "properties": { + "type": { + "$id": "#/properties/property/binding/type", + "type": "string", + "title": "property binding type", + "enum": [ + "property", + "zeebe:taskDefinition:type", + "zeebe:input", + "zeebe:output", + "zeebe:taskHeader" + ], + "errorMessage": "invalid property.binding type ${0}; must be any of { property, zeebe:taskDefinition:type, zeebe:input, zeebe:output, zeebe:taskHeader }", + "description": "The type of the property binding" + }, + "name": { + "$id": "#/properties/property/binding/name", + "type": "string", + "title": "property binding name", + "description": "The name of binding xml property" + }, + "source": { + "$id": "#/properties/property/binding/source", + "type": "string", + "title": "property binding source", + "description": "The source value of a property binding (zeebe:output)" + }, + "key": { + "$id": "#/properties/property/binding/key", + "type": "string", + "title": "property binding key", + "description": "The key value of a property binding (zeebe:taskHeader)" + } + } + }, + "optional": { + "$id": "#/optional", + "type": "boolean", + "title": "element template optional", + "description": "Indicates whether a property is optional" + } + } + } +} \ No newline at end of file diff --git a/test/fixtures/cloud-optional-inputs-outputs.js b/test/fixtures/cloud-optional-inputs-outputs.js new file mode 100644 index 0000000..a1d9051 --- /dev/null +++ b/test/fixtures/cloud-optional-inputs-outputs.js @@ -0,0 +1,30 @@ +export const template = { + 'name': 'REST Connector', + 'id': 'io.camunda.connectors.RestConnector-s1', + 'description': 'A generic REST service.', + 'appliesTo': [ + 'bpmn:ServiceTask' + ], + 'properties': [ + { + 'label': 'Request Body', + 'type': 'String', + 'optional': true, + 'binding': { + 'type': 'zeebe:input', + 'name': 'body' + } + }, + { + 'label': 'Result Variable', + 'type': 'String', + 'optional': true, + 'binding': { + 'type': 'zeebe:output', + 'source': '= body' + } + } + ] +}; + +export const errors = null; \ No newline at end of file diff --git a/test/fixtures/cloud-optional-invalid-type.js b/test/fixtures/cloud-optional-invalid-type.js new file mode 100644 index 0000000..693cb00 --- /dev/null +++ b/test/fixtures/cloud-optional-invalid-type.js @@ -0,0 +1,70 @@ +export const template = { + 'name': 'REST Connector', + 'id': 'io.camunda.connectors.RestConnector-s1', + 'description': 'A generic REST service.', + 'appliesTo': [ + 'bpmn:ServiceTask' + ], + 'properties': [ + { + 'label': 'Request Body', + 'type': 'String', + 'optional': true, + 'binding': { + 'type': 'zeebe:input', + 'name': 'body' + } + }, + { + 'label': 'Result Variable', + 'type': 'String', + 'optional': true, + 'binding': { + 'type': 'zeebe:taskHeader', + 'key': 'key' + } + } + ] +}; + +export const errors = [ + { + keyword: 'errorMessage', + dataPath: '/properties/1/binding/type', + schemaPath: '#/definitions/properties/allOf/1/items/allOf/2/then/properties/binding/properties/type/errorMessage', + params: { + errors: [ + { + keyword: 'enum', + dataPath: '/properties/1/binding/type', + schemaPath: '#/definitions/properties/allOf/1/items/allOf/2/then/properties/binding/properties/type/enum', + params: { allowedValues: [ 'zeebe:input', 'zeebe:output' ] }, + message: 'should be equal to one of the allowed values', + emUsed: true + } + ] + }, + message: 'optional is not supported for binding type "zeebe:taskHeader"; must be any of { zeebe:input, zeebe:output }' + }, + { + keyword: 'if', + dataPath: '/properties/1', + schemaPath: '#/definitions/properties/allOf/1/items/allOf/2/if', + params: { failingKeyword: 'then' }, + message: 'should match "then" schema' + }, + { + keyword: 'type', + dataPath: '', + schemaPath: '#/oneOf/1/type', + params: { type: 'array' }, + message: 'should be array' + }, + { + keyword: 'oneOf', + dataPath: '', + schemaPath: '#/oneOf', + params: { passingSchemas: null }, + message: 'should match exactly one schema in oneOf' + } +]; \ No newline at end of file diff --git a/test/fixtures/cloud-rest-connector.js b/test/fixtures/cloud-rest-connector.js new file mode 100644 index 0000000..04fdfa1 --- /dev/null +++ b/test/fixtures/cloud-rest-connector.js @@ -0,0 +1,73 @@ +export const template = { + 'name': 'REST Connector', + 'id': 'io.camunda.connectors.RestConnector-s1', + 'description': 'A generic REST service.', + 'appliesTo': [ + 'bpmn:ServiceTask' + ], + 'properties': [ + { + 'type': 'Hidden', + 'value': 'http', + 'binding': { + 'type': 'zeebe:taskDefinition:type' + } + }, + { + 'label': 'REST Endpoint URL', + 'description': 'Specify the url of the REST API to talk to.', + 'type': 'String', + 'binding': { + 'type': 'zeebe:taskHeader', + 'key': 'url' + }, + 'constraints': { + 'notEmpty': true, + 'pattern': { + 'value': '^https?://.*', + 'message': 'Must be http(s) URL.' + } + } + }, + { + 'label': 'REST Method', + 'description': 'Specify the HTTP method to use.', + 'type': 'Dropdown', + 'value': 'get', + 'choices': [ + { 'name': 'GET', 'value': 'get' }, + { 'name': 'POST', 'value': 'post' }, + { 'name': 'PATCH', 'value': 'patch' }, + { 'name': 'DELETE', 'value': 'delete' } + ], + 'binding': { + 'type': 'zeebe:taskHeader', + 'key': 'method' + } + }, + { + 'label': 'Request Body', + 'description': 'Data to send to the endpoint.', + 'value': '', + 'type': 'String', + 'optional': true, + 'binding': { + 'type': 'zeebe:input', + 'name': 'body' + } + }, + { + 'label': 'Result Variable', + 'description': 'Name of variable to store the response data in.', + 'value': 'response', + 'type': 'String', + 'optional': true, + 'binding': { + 'type': 'zeebe:output', + 'source': '= body' + } + } + ] +}; + +export const errors = null; \ No newline at end of file diff --git a/test/fixtures/invalid-binding-type-cloud.js b/test/fixtures/invalid-binding-type-cloud.js new file mode 100644 index 0000000..caa80da --- /dev/null +++ b/test/fixtures/invalid-binding-type-cloud.js @@ -0,0 +1,72 @@ +export const template = { + 'name': 'InvalidBindingType', + 'id': 'com.camunda.example.InvalidBindingType', + 'appliesTo': [ + 'bpmn:Task' + ], + 'properties': [ + { + 'label': 'Are you awesome?', + 'type': 'String', + 'binding': { + 'type': 'property', + 'name': 'foo' + } + }, + { + 'label': 'Are you awesome?', + 'type': 'String', + 'value': true, + 'binding': { + 'type': 'foo' + } + } + ] +}; + +export const errors = [ + { + keyword: 'errorMessage', + dataPath: '/properties/1/binding/type', + schemaPath: '#/definitions/properties/allOf/1/items/properties/binding/properties/type/errorMessage', + params: { + errors: [ + { + keyword: 'enum', + emUsed: true, + dataPath: '/properties/1/binding/type', + schemaPath: '#/definitions/properties/allOf/1/items/properties/binding/properties/type/enum', + params: { + allowedValues: [ + 'property', + 'zeebe:taskDefinition:type', + 'zeebe:input', + 'zeebe:output', + 'zeebe:taskHeader' + ] + }, + message: 'should be equal to one of the allowed values' + } + ] + }, + message: 'invalid property.binding type "foo"; must be any of { property, zeebe:taskDefinition:type, zeebe:input, zeebe:output, zeebe:taskHeader }' + }, + { + dataPath: '', + keyword: 'type', + message: 'should be array', + params: { + type: 'array', + }, + schemaPath: '#/oneOf/1/type', + }, + { + dataPath: '', + keyword: 'oneOf', + message: 'should match exactly one schema in oneOf', + params: { + passingSchemas: null + }, + schemaPath: '#/oneOf' + } +]; diff --git a/test/fixtures/invalid-zeebe-input-type.js b/test/fixtures/invalid-zeebe-input-type.js new file mode 100644 index 0000000..b1c5d5b --- /dev/null +++ b/test/fixtures/invalid-zeebe-input-type.js @@ -0,0 +1,80 @@ +export const template = { + 'name': 'InvalidZeebeInputType', + 'id': 'com.camunda.example.InvalidZeebeInputType', + 'appliesTo': [ + 'bpmn:Task' + ], + 'properties': [ + { + 'label': 'foo', + 'type': 'Text', + 'binding': { + 'type': 'zeebe:input', + 'name': 'foo' + } + }, + { + 'label': 'bar', + 'type': 'Boolean', + 'binding': { + 'type': 'zeebe:input', + 'name': 'bar' + } + } + ] +}; + +export const errors = [ + { + keyword: 'errorMessage', + dataPath: '/properties/1/type', + schemaPath: '#/definitions/properties/allOf/1/items/allOf/1/then/properties/type/errorMessage', + params: { + errors: [ + { + keyword: 'enum', + emUsed: true, + dataPath: '/properties/1/type', + schemaPath: '#/definitions/properties/allOf/1/items/allOf/1/then/properties/type/enum', + params: { + 'allowedValues': [ + 'String', + 'Text', + 'Hidden', + 'Dropdown' + ] + }, + message: 'should be equal to one of the allowed values' + } + ] + }, + message: 'invalid property type "Boolean" for binding type "zeebe:input"; must be any of { String, Text, Hidden, Dropdown }' + }, + { + keyword: 'if', + dataPath: '/properties/1', + schemaPath: '#/definitions/properties/allOf/1/items/allOf/1/if', + params: { + 'failingKeyword': 'then' + }, + message: 'should match "then" schema' + }, + { + dataPath: '', + keyword: 'type', + message: 'should be array', + params: { + type: 'array', + }, + schemaPath: '#/oneOf/1/type', + }, + { + dataPath: '', + keyword: 'oneOf', + message: 'should match exactly one schema in oneOf', + params: { + passingSchemas: null + }, + schemaPath: '#/oneOf' + } +]; diff --git a/test/fixtures/invalid-zeebe-output-type.js b/test/fixtures/invalid-zeebe-output-type.js new file mode 100644 index 0000000..5a41bbd --- /dev/null +++ b/test/fixtures/invalid-zeebe-output-type.js @@ -0,0 +1,80 @@ +export const template = { + 'name': 'InvalidZeebeOutputType', + 'id': 'com.camunda.example.InvalidZeebeOutputType', + 'appliesTo': [ + 'bpmn:Task' + ], + 'properties': [ + { + 'label': 'foo', + 'type': 'Text', + 'binding': { + 'type': 'zeebe:output', + 'source': 'foo' + } + }, + { + 'label': 'bar', + 'type': 'Boolean', + 'binding': { + 'type': 'zeebe:output', + 'source': 'bar' + } + } + ] +}; + +export const errors = [ + { + keyword: 'errorMessage', + dataPath: '/properties/1/type', + schemaPath: '#/definitions/properties/allOf/1/items/allOf/1/then/properties/type/errorMessage', + params: { + errors: [ + { + keyword: 'enum', + emUsed: true, + dataPath: '/properties/1/type', + schemaPath: '#/definitions/properties/allOf/1/items/allOf/1/then/properties/type/enum', + params: { + 'allowedValues': [ + 'String', + 'Text', + 'Hidden', + 'Dropdown' + ] + }, + message: 'should be equal to one of the allowed values' + } + ] + }, + message: 'invalid property type "Boolean" for binding type "zeebe:output"; must be any of { String, Text, Hidden, Dropdown }' + }, + { + keyword: 'if', + dataPath: '/properties/1', + schemaPath: '#/definitions/properties/allOf/1/items/allOf/1/if', + params: { + 'failingKeyword': 'then' + }, + message: 'should match "then" schema' + }, + { + dataPath: '', + keyword: 'type', + message: 'should be array', + params: { + type: 'array', + }, + schemaPath: '#/oneOf/1/type', + }, + { + dataPath: '', + keyword: 'oneOf', + message: 'should match exactly one schema in oneOf', + params: { + passingSchemas: null + }, + schemaPath: '#/oneOf' + } +]; diff --git a/test/fixtures/invalid-zeebe-task-definition-type-type.js b/test/fixtures/invalid-zeebe-task-definition-type-type.js new file mode 100644 index 0000000..d6525ec --- /dev/null +++ b/test/fixtures/invalid-zeebe-task-definition-type-type.js @@ -0,0 +1,78 @@ +export const template = { + 'name': 'InvalidZeebeTaskHeaderType', + 'id': 'com.camunda.example.InvalidZeebeTaskHeaderType', + 'appliesTo': [ + 'bpmn:Task' + ], + 'properties': [ + { + 'label': 'foo', + 'type': 'Text', + 'binding': { + 'type': 'zeebe:taskDefinition:type' + } + }, + { + 'label': 'bar', + 'type': 'Boolean', + 'binding': { + 'type': 'zeebe:taskDefinition:type' + } + } + ] +}; + +export const errors = [ + { + keyword: 'errorMessage', + dataPath: '/properties/1/type', + schemaPath: '#/definitions/properties/allOf/1/items/allOf/1/then/properties/type/errorMessage', + params: { + errors: [ + { + keyword: 'enum', + emUsed: true, + dataPath: '/properties/1/type', + schemaPath: '#/definitions/properties/allOf/1/items/allOf/1/then/properties/type/enum', + params: { + 'allowedValues': [ + 'String', + 'Text', + 'Hidden', + 'Dropdown' + ] + }, + message: 'should be equal to one of the allowed values' + } + ] + }, + message: 'invalid property type "Boolean" for binding type "zeebe:taskDefinition:type"; must be any of { String, Text, Hidden, Dropdown }' + }, + { + keyword: 'if', + dataPath: '/properties/1', + schemaPath: '#/definitions/properties/allOf/1/items/allOf/1/if', + params: { + 'failingKeyword': 'then' + }, + message: 'should match "then" schema' + }, + { + dataPath: '', + keyword: 'type', + message: 'should be array', + params: { + type: 'array', + }, + schemaPath: '#/oneOf/1/type', + }, + { + dataPath: '', + keyword: 'oneOf', + message: 'should match exactly one schema in oneOf', + params: { + passingSchemas: null + }, + schemaPath: '#/oneOf' + } +]; diff --git a/test/fixtures/invalid-zeebe-task-header-type.js b/test/fixtures/invalid-zeebe-task-header-type.js new file mode 100644 index 0000000..1f000dd --- /dev/null +++ b/test/fixtures/invalid-zeebe-task-header-type.js @@ -0,0 +1,80 @@ +export const template = { + 'name': 'InvalidZeebeTaskHeaderType', + 'id': 'com.camunda.example.InvalidZeebeTaskHeaderType', + 'appliesTo': [ + 'bpmn:Task' + ], + 'properties': [ + { + 'label': 'foo', + 'type': 'Text', + 'binding': { + 'type': 'zeebe:taskHeader', + 'key': 'foo' + } + }, + { + 'label': 'bar', + 'type': 'Boolean', + 'binding': { + 'type': 'zeebe:taskHeader', + 'key': 'foo' + } + } + ] +}; + +export const errors = [ + { + keyword: 'errorMessage', + dataPath: '/properties/1/type', + schemaPath: '#/definitions/properties/allOf/1/items/allOf/1/then/properties/type/errorMessage', + params: { + errors: [ + { + keyword: 'enum', + emUsed: true, + dataPath: '/properties/1/type', + schemaPath: '#/definitions/properties/allOf/1/items/allOf/1/then/properties/type/enum', + params: { + 'allowedValues': [ + 'String', + 'Text', + 'Hidden', + 'Dropdown' + ] + }, + message: 'should be equal to one of the allowed values' + } + ] + }, + message: 'invalid property type "Boolean" for binding type "zeebe:taskHeader"; must be any of { String, Text, Hidden, Dropdown }' + }, + { + keyword: 'if', + dataPath: '/properties/1', + schemaPath: '#/definitions/properties/allOf/1/items/allOf/1/if', + params: { + 'failingKeyword': 'then' + }, + message: 'should match "then" schema' + }, + { + dataPath: '', + keyword: 'type', + message: 'should be array', + params: { + type: 'array', + }, + schemaPath: '#/oneOf/1/type', + }, + { + dataPath: '', + keyword: 'oneOf', + message: 'should match exactly one schema in oneOf', + params: { + passingSchemas: null + }, + schemaPath: '#/oneOf' + } +]; diff --git a/test/fixtures/missing-binding-zeebe-header-key.js b/test/fixtures/missing-binding-zeebe-header-key.js new file mode 100644 index 0000000..1d2cf2a --- /dev/null +++ b/test/fixtures/missing-binding-zeebe-header-key.js @@ -0,0 +1,66 @@ +export const template = { + 'name': 'MissingBindingKey', + 'id': 'com.camunda.example.MissingBindingKey', + 'appliesTo': [ + 'bpmn:Task' + ], + 'properties': [ + { + 'label': 'foo', + 'type': 'String', + 'binding': { + 'type': 'zeebe:taskHeader' + } + } + ] +}; + +export const errors = [ + { + keyword: 'errorMessage', + dataPath: '/properties/0/binding', + schemaPath: '#/definitions/properties/allOf/1/items/properties/binding/allOf/2/then/errorMessage', + params: { + errors: [ + { + keyword: 'required', + emUsed: true, + dataPath: '/properties/0/binding', + schemaPath: '#/definitions/properties/allOf/1/items/properties/binding/allOf/2/then/required', + params: { + missingProperty: 'key' + }, + message: "should have required property 'key'" + } + ] + }, + message: 'property.binding "zeebe:taskHeader" requires key' + }, + { + keyword: 'if', + dataPath: '/properties/0/binding', + schemaPath: '#/definitions/properties/allOf/1/items/properties/binding/allOf/2/if', + params: { + 'failingKeyword': 'then' + }, + message: 'should match "then" schema' + }, + { + dataPath: '', + keyword: 'type', + message: 'should be array', + params: { + type: 'array', + }, + schemaPath: '#/oneOf/1/type', + }, + { + dataPath: '', + keyword: 'oneOf', + message: 'should match exactly one schema in oneOf', + params: { + passingSchemas: null + }, + schemaPath: '#/oneOf' + } +]; diff --git a/test/fixtures/missing-binding-zeebe-input-name.js b/test/fixtures/missing-binding-zeebe-input-name.js new file mode 100644 index 0000000..af00ce5 --- /dev/null +++ b/test/fixtures/missing-binding-zeebe-input-name.js @@ -0,0 +1,66 @@ +export const template = { + 'name': 'MissingBindingName', + 'id': 'com.camunda.example.MissingBindingName', + 'appliesTo': [ + 'bpmn:Task' + ], + 'properties': [ + { + 'label': 'foo', + 'type': 'String', + 'binding': { + 'type': 'zeebe:input' + } + } + ] +}; + +export const errors = [ + { + keyword: 'errorMessage', + dataPath: '/properties/0/binding', + schemaPath: '#/definitions/properties/allOf/1/items/properties/binding/allOf/0/then/errorMessage', + params: { + errors: [ + { + keyword: 'required', + emUsed: true, + dataPath: '/properties/0/binding', + schemaPath: '#/definitions/properties/allOf/1/items/properties/binding/allOf/0/then/required', + params: { + missingProperty: 'name' + }, + message: "should have required property 'name'" + } + ] + }, + message: 'property.binding "zeebe:input" requires name' + }, + { + keyword: 'if', + dataPath: '/properties/0/binding', + schemaPath: '#/definitions/properties/allOf/1/items/properties/binding/allOf/0/if', + params: { + 'failingKeyword': 'then' + }, + message: 'should match "then" schema' + }, + { + dataPath: '', + keyword: 'type', + message: 'should be array', + params: { + type: 'array', + }, + schemaPath: '#/oneOf/1/type', + }, + { + dataPath: '', + keyword: 'oneOf', + message: 'should match exactly one schema in oneOf', + params: { + passingSchemas: null + }, + schemaPath: '#/oneOf' + } +]; diff --git a/test/fixtures/missing-binding-zeebe-output-source.js b/test/fixtures/missing-binding-zeebe-output-source.js new file mode 100644 index 0000000..d2a3926 --- /dev/null +++ b/test/fixtures/missing-binding-zeebe-output-source.js @@ -0,0 +1,74 @@ +export const template = { + 'name': 'MissingBindingSource', + 'id': 'com.camunda.example.MissingBindingSource', + 'appliesTo': [ + 'bpmn:Task' + ], + 'properties': [ + { + 'label': 'foo', + 'type': 'String', + 'binding': { + 'type': 'zeebe:output', + 'source': 'foo' + } + }, + { + 'label': 'foo', + 'type': 'String', + 'binding': { + 'type': 'zeebe:output' + } + } + ] +}; + +export const errors = [ + { + keyword: 'errorMessage', + dataPath: '/properties/1/binding', + schemaPath: '#/definitions/properties/allOf/1/items/properties/binding/allOf/1/then/errorMessage', + params: { + errors: [ + { + keyword: 'required', + emUsed: true, + dataPath: '/properties/1/binding', + schemaPath: '#/definitions/properties/allOf/1/items/properties/binding/allOf/1/then/required', + params: { + missingProperty: 'source' + }, + message: "should have required property 'source'" + } + ] + }, + message: 'property.binding "zeebe:output" requires source' + }, + { + keyword: 'if', + dataPath: '/properties/1/binding', + schemaPath: '#/definitions/properties/allOf/1/items/properties/binding/allOf/1/if', + params: { + 'failingKeyword': 'then' + }, + message: 'should match "then" schema' + }, + { + dataPath: '', + keyword: 'type', + message: 'should be array', + params: { + type: 'array', + }, + schemaPath: '#/oneOf/1/type', + }, + { + dataPath: '', + keyword: 'oneOf', + message: 'should match exactly one schema in oneOf', + params: { + passingSchemas: null + }, + schemaPath: '#/oneOf' + } +]; diff --git a/test/spec/cloud.validationSpec.js b/test/spec/cloud.validationSpec.js new file mode 100644 index 0000000..dba8c23 --- /dev/null +++ b/test/spec/cloud.validationSpec.js @@ -0,0 +1,185 @@ +import { expect } from 'chai'; + +import util from 'util'; + +import schema from '../../resources/cloud.json'; + +import { + createValidator +} from '../helpers'; + +const validator = createValidator(schema); + + +describe('validation - cloud', function() { + + function validateTemplate(template) { + + const valid = validator(template); + + const errors = validator.errors; + + return { + valid, + errors + }; + } + + function testTemplate(name, file, only = false) { + + if (!file) { + file = `../fixtures/${name}.js`; + } + + (only ? it.only : it)('should validate template - ' + name, function() { + + // given + const testDefinition = require(file); + + const { + errors: expectedErrors, + template + } = testDefinition; + + // when + const { + errors + } = validateTemplate(template); + + // then + expect(errors).to.eql(expectedErrors); + }); + } + + // eslint-disable-next-line no-unused-vars + function testOnly(name, file) { + return testTemplate(name, file, true); + } + + + describe('single template', function() { + + testTemplate('cloud-rest-connector'); + + + testTemplate('missing-type'); + + + testTemplate('missing-applies-to'); + + + testTemplate('missing-template-name'); + + + testTemplate('missing-template-id'); + + + testTemplate('missing-properties'); + + + testTemplate('missing-binding'); + + + testTemplate('applies-to-single'); + + + testTemplate('number-value'); + + + testTemplate('additional-property'); + + + testTemplate('invalid-binding-type-cloud'); + + + testTemplate('choices-missing-value'); + + + testTemplate('choices-missing-name'); + + + testTemplate('missing-choices'); + + + testTemplate('missing-binding-zeebe-output-source'); + + + testTemplate('missing-binding-zeebe-input-name'); + + + testTemplate('missing-binding-zeebe-header-key'); + + + testTemplate('with-version'); + + + testTemplate('invalid-version'); + + + testTemplate('constraints'); + + + testTemplate('invalid-constraints'); + + + testTemplate('invalid-applies-to'); + + + testTemplate('entries-visible-boolean'); + + + testTemplate('pattern-string'); + + + testTemplate('cloud-optional-inputs-outputs'); + + + testTemplate('cloud-optional-invalid-type'); + + + describe('property type - binding type', function() { + + testTemplate('invalid-property-type'); + + + testTemplate('invalid-zeebe-input-type'); + + + testTemplate('invalid-zeebe-output-type'); + + + testTemplate('invalid-zeebe-task-header-type'); + + + testTemplate('invalid-zeebe-task-definition-type-type'); + + }); + + + describe('grouping', function() { + + testTemplate('groups'); + + + testTemplate('groups-missing-id'); + + + testTemplate('groups-missing-label'); + + }); + + }); + +}); + + +// helpers ///////////////// + +// eslint-disable-next-line no-unused-vars +function printNested(object) { + console.log(util.inspect(object, { + showHidden: false, + depth: null, + colors: true + })); +} diff --git a/test/spec/schemaSpec.js b/test/spec/schemaSpec.js index a1bccd4..183b132 100644 --- a/test/spec/schemaSpec.js +++ b/test/spec/schemaSpec.js @@ -2,6 +2,7 @@ import Ajv from 'ajv'; import { expect } from 'chai'; import platformSchema from '../../resources/schema.json'; +import cloudSchema from '../../resources/cloud.json'; describe('schema validation', function() { @@ -19,4 +20,18 @@ describe('schema validation', function() { expect(valid.errors).to.not.exist; }); + + it('should be valid (cloud)', function() { + + // given + const ajv = new Ajv(); + + // when + const valid = ajv.validateSchema(cloudSchema); + + // then + expect(valid).to.be.true; + expect(valid.errors).to.not.exist; + }); + }); \ No newline at end of file