diff --git a/index.js b/index.js index 611b381..5bdb836 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ const camundaCloud10Rules = withConfig({ 'loop-characteristics': 'error', 'message-reference': 'error', 'no-candidate-users': 'error', + 'no-execution-listeners': 'error', 'no-expression': 'error', 'no-loop': 'error', 'no-multiple-none-start-events': 'error', @@ -75,8 +76,11 @@ const camundaCloud85Rules = withConfig({ 'wait-for-completion': 'error' }, { version: '8.5' }); -const camundaCloud86Rules = withConfig( - omit(camundaCloud85Rules, 'inclusive-gateway'), { version: '8.6' }); +const camundaCloud86Rules = withConfig({ + ...omit(camundaCloud85Rules, [ 'inclusive-gateway', 'no-execution-listeners' ]), + 'duplicate-execution-listeners': 'error', + 'execution-listener': 'error' +}, { version: '8.6' }); const camundaPlatform719Rules = withConfig({ 'history-time-to-live': 'info' @@ -105,12 +109,14 @@ const rules = { 'called-element': './rules/camunda-cloud/called-element', 'collapsed-subprocess': './rules/camunda-cloud/collapsed-subprocess', 'connector-properties': './rules/camunda-cloud/connector-properties', + 'duplicate-execution-listeners': './rules/camunda-cloud/duplicate-execution-listeners', 'duplicate-task-headers': './rules/camunda-cloud/duplicate-task-headers', 'error-reference': './rules/camunda-cloud/error-reference', 'escalation-boundary-event-attached-to-ref': './rules/camunda-cloud/escalation-boundary-event-attached-to-ref', 'escalation-reference': './rules/camunda-cloud/escalation-reference', 'event-based-gateway-target': './rules/camunda-cloud/event-based-gateway-target', 'executable-process': './rules/camunda-cloud/executable-process', + 'execution-listener': './rules/camunda-cloud/execution-listener', 'feel': './rules/camunda-cloud/feel', 'history-time-to-live': './rules/camunda-platform/history-time-to-live', 'implementation': './rules/camunda-cloud/implementation', @@ -119,6 +125,7 @@ const rules = { 'loop-characteristics': './rules/camunda-cloud/loop-characteristics', 'message-reference': './rules/camunda-cloud/message-reference', 'no-candidate-users': './rules/camunda-cloud/no-candidate-users', + 'no-execution-listeners': './rules/camunda-cloud/no-execution-listeners', 'no-expression': './rules/camunda-cloud/no-expression', 'no-loop': './rules/camunda-cloud/no-loop', 'no-multiple-none-start-events': './rules/camunda-cloud/no-multiple-none-start-events', diff --git a/package-lock.json b/package-lock.json index ee54dac..d275ee0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "modeler-moddle": "^0.2.0", "sinon": "^17.0.1", "sinon-chai": "^3.7.0", - "zeebe-bpmn-moddle": "^1.1.0" + "zeebe-bpmn-moddle": "^1.4.0" }, "engines": { "node": "*" @@ -3118,10 +3118,11 @@ } }, "node_modules/zeebe-bpmn-moddle": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/zeebe-bpmn-moddle/-/zeebe-bpmn-moddle-1.1.0.tgz", - "integrity": "sha512-ES/UZFO0VmKvAzL4+cD3VcQpKvlmgLtnFKTyiv0DdDcxNrdQg1rI0OmUdrKMiybAbtAgPDkVXZCusE3kkXwEyQ==", - "dev": true + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/zeebe-bpmn-moddle/-/zeebe-bpmn-moddle-1.4.0.tgz", + "integrity": "sha512-XSm0fMHPjQ5cmEGxga02du9arxb5NKH5ve7VQ0LFSes4wGcrZ/oJjaR3NlBqH6xTTD3g+Mcbh45+yesydPUQPg==", + "dev": true, + "license": "MIT" } }, "dependencies": { @@ -5405,9 +5406,9 @@ "dev": true }, "zeebe-bpmn-moddle": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/zeebe-bpmn-moddle/-/zeebe-bpmn-moddle-1.1.0.tgz", - "integrity": "sha512-ES/UZFO0VmKvAzL4+cD3VcQpKvlmgLtnFKTyiv0DdDcxNrdQg1rI0OmUdrKMiybAbtAgPDkVXZCusE3kkXwEyQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/zeebe-bpmn-moddle/-/zeebe-bpmn-moddle-1.4.0.tgz", + "integrity": "sha512-XSm0fMHPjQ5cmEGxga02du9arxb5NKH5ve7VQ0LFSes4wGcrZ/oJjaR3NlBqH6xTTD3g+Mcbh45+yesydPUQPg==", "dev": true } } diff --git a/package.json b/package.json index f16d0ea..6bc90f3 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "modeler-moddle": "^0.2.0", "sinon": "^17.0.1", "sinon-chai": "^3.7.0", - "zeebe-bpmn-moddle": "^1.1.0" + "zeebe-bpmn-moddle": "^1.4.0" }, "dependencies": { "@bpmn-io/feel-lint": "^1.2.0", diff --git a/rules/camunda-cloud/duplicate-execution-listeners.js b/rules/camunda-cloud/duplicate-execution-listeners.js new file mode 100644 index 0000000..278e1f5 --- /dev/null +++ b/rules/camunda-cloud/duplicate-execution-listeners.js @@ -0,0 +1,33 @@ +const { + findExtensionElement, + hasDuplicatedPropertiesValues +} = require('../utils/element'); + +const { reportErrors } = require('../utils/reporter'); + +const { skipInNonExecutableProcess } = require('../utils/rule'); + +module.exports = skipInNonExecutableProcess(function() { + function check(node, reporter) { + const executionListeners = findExtensionElement(node, 'zeebe:ExecutionListeners'); + + if (!executionListeners) { + return; + } + + const errors = hasDuplicatedExecutionListeners(executionListeners, node); + + if (errors && errors.length) { + reportErrors(node, reporter, errors); + } + } + + return { + check + }; +}); + +// helpers ////////// +function hasDuplicatedExecutionListeners(executionListeners, parentNode = null) { + return hasDuplicatedPropertiesValues(executionListeners, 'listeners', [ 'eventType', 'type' ], parentNode); +} diff --git a/rules/camunda-cloud/execution-listener.js b/rules/camunda-cloud/execution-listener.js new file mode 100644 index 0000000..ac8bdc6 --- /dev/null +++ b/rules/camunda-cloud/execution-listener.js @@ -0,0 +1,34 @@ +const { + findExtensionElement, + hasProperties +} = require('../utils/element'); + +const { reportErrors } = require('../utils/reporter'); + +const { skipInNonExecutableProcess } = require('../utils/rule'); + + +module.exports = skipInNonExecutableProcess(function() { + function check(node, reporter) { + const executionListeners = findExtensionElement(node, 'zeebe:ExecutionListeners'); + + if (!executionListeners) { + return; + } + + const listeners = executionListeners.get('listeners'); + const errors = listeners.flatMap(listener => hasProperties(listener, { + type: { + required: true + } + }, node)); + + if (errors.length) { + reportErrors(node, reporter, errors); + } + } + + return { + check + }; +}); diff --git a/rules/camunda-cloud/no-execution-listeners.js b/rules/camunda-cloud/no-execution-listeners.js new file mode 100644 index 0000000..1684023 --- /dev/null +++ b/rules/camunda-cloud/no-execution-listeners.js @@ -0,0 +1,21 @@ +const { reportErrors } = require('../utils/reporter'); + +const { skipInNonExecutableProcess } = require('../utils/rule'); + +const { hasNoExtensionElement } = require('../utils/element'); + +const ALLOWED_VERSION = '8.6'; + +module.exports = skipInNonExecutableProcess(function() { + function check(node, reporter) { + const errors = hasNoExtensionElement(node, 'zeebe:ExecutionListeners', node, ALLOWED_VERSION); + + if (errors && errors.length) { + reportErrors(node, reporter, errors); + } + } + + return { + check + }; +}); diff --git a/rules/utils/element.js b/rules/utils/element.js index ea25e0e..9e5ede6 100644 --- a/rules/utils/element.js +++ b/rules/utils/element.js @@ -1,4 +1,5 @@ const { + filter, isArray, isDefined, isFunction, @@ -6,6 +7,7 @@ const { isObject, isString, isUndefined, + matchPattern, some } = require('min-dash'); @@ -133,6 +135,53 @@ module.exports.hasDuplicatedPropertyValues = function(node, propertiesName, prop return []; }; +// @TODO(@barmac): use tree algorithm to reduce complexity +module.exports.hasDuplicatedPropertiesValues = function(node, containerPropertyName, propertiesNames, parentNode = null) { + const properties = node.get(containerPropertyName); + + // (1) find duplicates + const duplicates = properties.reduce((foundDuplicates, property, index) => { + const previous = properties.slice(0, index); + const isDuplicate = previous.find(p => propertiesNames.every(propertyName => p.get(propertyName) === property.get(propertyName))); + + if (isDuplicate) { + return foundDuplicates.concat(property); + } + + return foundDuplicates; + }, []); + + // (2) report error for each duplicate + if (duplicates.length) { + return duplicates.map(duplicate => { + const propertiesMap = {}; + for (const property of propertiesNames) { + propertiesMap[property] = duplicate.get(property); + } + + // (3) find properties with duplicate + const duplicateProperties = filter(properties, matchPattern(propertiesMap)); + const duplicatesSummary = propertiesNames.map(propertyName => `property <${ propertyName }> with duplicate value of <${ propertiesMap[propertyName] }>`).join(', '); + + // (4) report error + return { + message: `Properties of type <${ duplicate.$type }> have properties with duplicate values (${ duplicatesSummary })`, + path: null, + data: { + type: ERROR_TYPES.PROPERTY_VALUES_DUPLICATED, + node, + parentNode: parentNode == node ? null : parentNode, + duplicatedProperties: propertiesMap, + properties: duplicateProperties, + propertiesName: containerPropertyName + } + }; + }); + } + + return []; +}; + module.exports.hasProperties = function(node, properties, parentNode = null) { return Object.entries(properties).reduce((results, property) => { const [ propertyName, propertyChecks ] = property; diff --git a/rules/utils/error-types.js b/rules/utils/error-types.js index 66b9987..185ba61 100644 --- a/rules/utils/error-types.js +++ b/rules/utils/error-types.js @@ -19,6 +19,7 @@ module.exports.ERROR_TYPES = Object.freeze({ PROPERTY_REQUIRED: 'camunda.propertyRequired', PROPERTY_TYPE_NOT_ALLOWED: 'camunda.propertyTypeNotAllowed', PROPERTY_VALUE_DUPLICATED: 'camunda.propertyValueDuplicated', + PROPERTY_VALUES_DUPLICATED: 'camunda.propertiesValuesDuplicated', PROPERTY_VALUE_NOT_ALLOWED: 'camunda.propertyValueNotAllowed', PROPERTY_VALUE_REQUIRED: 'camunda.propertyValueRequired', SECRET_EXPRESSION_FORMAT_DEPRECATED: 'camunda.secretExpressionFormatDeprecated' diff --git a/test/camunda-cloud/duplicate-execution-listeners.spec.js b/test/camunda-cloud/duplicate-execution-listeners.spec.js new file mode 100644 index 0000000..85c520c --- /dev/null +++ b/test/camunda-cloud/duplicate-execution-listeners.spec.js @@ -0,0 +1,295 @@ +const RuleTester = require('bpmnlint/lib/testers/rule-tester'); + +const rule = require('../../rules/camunda-cloud/duplicate-execution-listeners'); + +const { + createDefinitions, + createModdle, + createProcess +} = require('../helper'); + +const { ERROR_TYPES } = require('../../rules/utils/element'); + +const valid = [ + { + name: 'service task', + moddleElement: createModdle(createProcess(` + + + + + + + + + `)) + }, + { + name: 'send task', + moddleElement: createModdle(createProcess(` + + + + + + + + + `)) + }, + { + name: 'user task', + moddleElement: createModdle(createProcess(` + + + + + + + + + `)) + }, + { + name: 'business rule task', + moddleElement: createModdle(createProcess(` + + + + + + + + + + `)) + }, + { + name: 'script task', + moddleElement: createModdle(createProcess(` + + + + + + + + + + + `)) + }, + { + name: 'message throw event', + moddleElement: createModdle(createProcess(` + + + + + + + + + + `)) + }, + { + name: 'service task (non-executable process)', + config: { version: '8.6' }, + moddleElement: createModdle(createDefinitions(` + + + + + + + + + + + `)) + } +]; + +const invalid = [ + { + name: 'service task', + moddleElement: createModdle(createProcess(` + + + + + + + + + `)), + report: { + id: 'ServiceTask_1', + message: 'Properties of type have properties with duplicate values (property with duplicate value of , property with duplicate value of )', + path: null, + data: { + type: ERROR_TYPES.PROPERTY_VALUES_DUPLICATED, + node: 'zeebe:ExecutionListeners', + parentNode: 'ServiceTask_1', + 'duplicatedProperties': { + 'eventType': 'start', + 'type': 'duplicate' + }, + properties: [ + 'zeebe:ExecutionListener', + 'zeebe:ExecutionListener' + ], + propertiesName: 'listeners' + } + } + }, + { + name: 'service task (multiple duplicates)', + moddleElement: createModdle(createProcess(` + + + + + + + + + + `)), + report: [ + { + id: 'ServiceTask_1', + message: 'Properties of type have properties with duplicate values (property with duplicate value of , property with duplicate value of )', + path: null, + data: { + type: ERROR_TYPES.PROPERTY_VALUES_DUPLICATED, + node: 'zeebe:ExecutionListeners', + parentNode: 'ServiceTask_1', + 'duplicatedProperties': { + 'eventType': 'start', + 'type': 'duplicate' + }, + properties: [ + 'zeebe:ExecutionListener', + 'zeebe:ExecutionListener', + 'zeebe:ExecutionListener' + ], + propertiesName: 'listeners' + } + }, + { + id: 'ServiceTask_1', + message: 'Properties of type have properties with duplicate values (property with duplicate value of , property with duplicate value of )', + path: null, + data: { + type: ERROR_TYPES.PROPERTY_VALUES_DUPLICATED, + node: 'zeebe:ExecutionListeners', + parentNode: 'ServiceTask_1', + 'duplicatedProperties': { + 'eventType': 'start', + 'type': 'duplicate' + }, + properties: [ + 'zeebe:ExecutionListener', + 'zeebe:ExecutionListener', + 'zeebe:ExecutionListener' + ], + propertiesName: 'listeners' + } + } + ] + }, + { + name: 'service task (multiple duplicates)', + moddleElement: createModdle(createProcess(` + + + + + + + + + + + `)), + report: [ + { + id: 'ServiceTask_1', + message: 'Properties of type have properties with duplicate values (property with duplicate value of , property with duplicate value of )', + path: null, + data: { + type: ERROR_TYPES.PROPERTY_VALUES_DUPLICATED, + node: 'zeebe:ExecutionListeners', + parentNode: 'ServiceTask_1', + 'duplicatedProperties': { + 'eventType': 'start', + 'type': 'duplicate' + }, + properties: [ + 'zeebe:ExecutionListener', + 'zeebe:ExecutionListener' + ], + propertiesName: 'listeners' + } + }, + { + id: 'ServiceTask_1', + message: 'Properties of type have properties with duplicate values (property with duplicate value of , property with duplicate value of )', + path: null, + data: { + type: ERROR_TYPES.PROPERTY_VALUES_DUPLICATED, + node: 'zeebe:ExecutionListeners', + parentNode: 'ServiceTask_1', + 'duplicatedProperties': { + 'eventType': 'start', + 'type': 'duplicate_2' + }, + properties: [ + 'zeebe:ExecutionListener', + 'zeebe:ExecutionListener' + ], + propertiesName: 'listeners' + } + } + ] + }, + { + name: 'service task (no type)', + moddleElement: createModdle(createProcess(` + + + + + + + + + `)), + report: { + id: 'ServiceTask_1', + message: 'Properties of type have properties with duplicate values (property with duplicate value of , property with duplicate value of <>)', + path: null, + data: { + type: ERROR_TYPES.PROPERTY_VALUES_DUPLICATED, + node: 'zeebe:ExecutionListeners', + parentNode: 'ServiceTask_1', + 'duplicatedProperties': { + 'eventType': 'start', + 'type': '' + }, + properties: [ + 'zeebe:ExecutionListener', + 'zeebe:ExecutionListener' + ], + propertiesName: 'listeners' + } + } + } +]; + +RuleTester.verify('duplicate-execution-listeners', rule, { + valid, + invalid +}); \ No newline at end of file diff --git a/test/camunda-cloud/execution-listener.spec.js b/test/camunda-cloud/execution-listener.spec.js new file mode 100644 index 0000000..0030a5f --- /dev/null +++ b/test/camunda-cloud/execution-listener.spec.js @@ -0,0 +1,186 @@ +const RuleTester = require('bpmnlint/lib/testers/rule-tester'); + +const rule = require('../../rules/camunda-cloud/execution-listener'); + +const { + createModdle, + createProcess +} = require('../helper'); + +const { ERROR_TYPES } = require('../../rules/utils/element'); + +const valid = [ + { + name: 'execution listener with type', + config: { version: '8.6' }, + moddleElement: createModdle(createProcess(` + + + + + + + + `)) + } +]; + +const invalid = [ + { + name: 'execution listener with empty type', + config: { version: '8.6' }, + moddleElement: createModdle(createProcess(` + + + + + + + + `)), + report: { + id: 'UserTask_1', + message: 'Element of type must have property ', + path: [ + 'extensionElements', + 'values', + 0, + 'listeners', + 0, + 'type' + ], + data: { + type: ERROR_TYPES.PROPERTY_REQUIRED, + node: 'zeebe:ExecutionListener', + parentNode: 'UserTask_1', + requiredProperty: 'type' + } + } + }, + { + name: 'execution listener with no type', + config: { version: '8.6' }, + moddleElement: createModdle(createProcess(` + + + + + + + + `)), + report: { + id: 'UserTask_1', + message: 'Element of type must have property ', + path: [ + 'extensionElements', + 'values', + 0, + 'listeners', + 0, + 'type' + ], + data: { + type: ERROR_TYPES.PROPERTY_REQUIRED, + node: 'zeebe:ExecutionListener', + parentNode: 'UserTask_1', + requiredProperty: 'type' + } + } + }, + { + name: 'multiple execution listeners with no type', + config: { version: '8.6' }, + moddleElement: createModdle(createProcess(` + + + + + + + + + + + `)), + report: [ + { + id: 'UserTask_1', + message: 'Element of type must have property ', + path: [ + 'extensionElements', + 'values', + 0, + 'listeners', + 0, + 'type' + ], + data: { + type: ERROR_TYPES.PROPERTY_REQUIRED, + node: 'zeebe:ExecutionListener', + parentNode: 'UserTask_1', + requiredProperty: 'type' + } + }, + { + id: 'UserTask_1', + message: 'Element of type must have property ', + path: [ + 'extensionElements', + 'values', + 0, + 'listeners', + 1, + 'type' + ], + data: { + type: ERROR_TYPES.PROPERTY_REQUIRED, + node: 'zeebe:ExecutionListener', + parentNode: 'UserTask_1', + requiredProperty: 'type' + } + }, + { + id: 'UserTask_1', + message: 'Element of type must have property ', + path: [ + 'extensionElements', + 'values', + 0, + 'listeners', + 2, + 'type' + ], + data: { + type: ERROR_TYPES.PROPERTY_REQUIRED, + node: 'zeebe:ExecutionListener', + parentNode: 'UserTask_1', + requiredProperty: 'type' + } + }, + { + id: 'UserTask_1', + message: 'Element of type must have property ', + path: [ + 'extensionElements', + 'values', + 0, + 'listeners', + 3, + 'type' + ], + data: { + type: ERROR_TYPES.PROPERTY_REQUIRED, + node: 'zeebe:ExecutionListener', + parentNode: 'UserTask_1', + requiredProperty: 'type' + } + } + ] + } +]; + +RuleTester.verify('execution-listener', rule, { + valid, + invalid +}); \ No newline at end of file diff --git a/test/camunda-cloud/no-execution-listeners.spec.js b/test/camunda-cloud/no-execution-listeners.spec.js new file mode 100644 index 0000000..aa51c77 --- /dev/null +++ b/test/camunda-cloud/no-execution-listeners.spec.js @@ -0,0 +1,57 @@ +const RuleTester = require('bpmnlint/lib/testers/rule-tester'); + +const rule = require('../../rules/camunda-cloud/no-execution-listeners'); + +const { + createModdle, + createProcess +} = require('../helper'); + +const { ERROR_TYPES } = require('../../rules/utils/element'); + +const valid = [ + { + name: 'user task without execution listeners', + config: { version: '8.5' }, + moddleElement: createModdle(createProcess(` + + `)) + } +]; + +const invalid = [ + { + name: 'no execution listeners (Camunda 8.5)', + config: { version: '8.5' }, + moddleElement: createModdle(createProcess(` + + + + + + + + `)), + report: { + id: 'UserTask_1', + message: 'Extension element of type only allowed by Camunda 8.6', + path: [ + 'extensionElements', + 'values', + 0 + ], + data: { + type: ERROR_TYPES.EXTENSION_ELEMENT_NOT_ALLOWED, + node: 'UserTask_1', + parentNode: null, + extensionElement: 'zeebe:ExecutionListeners', + allowedVersion: '8.6' + } + } + } +]; + +RuleTester.verify('no-execution-listeners', rule, { + valid, + invalid +}); \ No newline at end of file diff --git a/test/config/configs.spec.js b/test/config/configs.spec.js index db129f9..8c701b9 100644 --- a/test/config/configs.spec.js +++ b/test/config/configs.spec.js @@ -17,6 +17,7 @@ describe('configs', function() { 'loop-characteristics': [ 'error', { version: '1.0' } ], 'message-reference': [ 'error', { version: '1.0' } ], 'no-candidate-users': [ 'error', { version: '1.0' } ], + 'no-execution-listeners': [ 'error', { version: '1.0' } ], 'no-expression': [ 'error', { version: '1.0' } ], 'no-loop': [ 'error', { version: '1.0' } ], 'no-multiple-none-start-events' : [ 'error', { version: '1.0' } ], @@ -47,6 +48,7 @@ describe('configs', function() { 'loop-characteristics': [ 'error', { version: '1.1' } ], 'message-reference': [ 'error', { version: '1.1' } ], 'no-candidate-users': [ 'error', { version: '1.1' } ], + 'no-execution-listeners': [ 'error', { version: '1.1' } ], 'no-expression': [ 'error', { version: '1.1' } ], 'no-loop': [ 'error', { version: '1.1' } ], 'no-multiple-none-start-events' : [ 'error', { version: '1.1' } ], @@ -77,6 +79,7 @@ describe('configs', function() { 'loop-characteristics': [ 'error', { version: '1.2' } ], 'message-reference': [ 'error', { version: '1.2' } ], 'no-candidate-users': [ 'error', { version: '1.2' } ], + 'no-execution-listeners': [ 'error', { version: '1.2' } ], 'no-expression': [ 'error', { version: '1.2' } ], 'no-loop': [ 'error', { version: '1.2' } ], 'no-multiple-none-start-events' : [ 'error', { version: '1.2' } ], @@ -107,6 +110,7 @@ describe('configs', function() { 'loop-characteristics': [ 'error', { version: '1.3' } ], 'message-reference': [ 'error', { version: '1.3' } ], 'no-candidate-users': [ 'error', { version: '1.3' } ], + 'no-execution-listeners': [ 'error', { version: '1.3' } ], 'no-expression': [ 'error', { version: '1.3' } ], 'no-loop': [ 'error', { version: '1.3' } ], 'no-multiple-none-start-events' : [ 'error', { version: '1.3' } ], @@ -138,6 +142,7 @@ describe('configs', function() { 'loop-characteristics': [ 'error', { version: '8.0' } ], 'message-reference': [ 'error', { version: '8.0' } ], 'no-candidate-users': [ 'error', { version: '8.0' } ], + 'no-execution-listeners': [ 'error', { version: '8.0' } ], 'no-expression': [ 'error', { version: '8.0' } ], 'no-loop': [ 'error', { version: '8.0' } ], 'no-multiple-none-start-events' : [ 'error', { version: '8.0' } ], @@ -169,6 +174,7 @@ describe('configs', function() { 'loop-characteristics': [ 'error', { version: '8.1' } ], 'message-reference': [ 'error', { version: '8.1' } ], 'no-candidate-users': [ 'error', { version: '8.1' } ], + 'no-execution-listeners': [ 'error', { version: '8.1' } ], 'no-expression': [ 'error', { version: '8.1' } ], 'no-loop': [ 'error', { version: '8.1' } ], 'no-multiple-none-start-events' : [ 'error', { version: '8.1' } ], @@ -201,6 +207,7 @@ describe('configs', function() { 'link-event': [ 'error', { version: '8.2' } ], 'loop-characteristics': [ 'error', { version: '8.2' } ], 'message-reference': [ 'error', { version: '8.2' } ], + 'no-execution-listeners': [ 'error', { version: '8.2' } ], 'no-expression': [ 'error', { version: '8.2' } ], 'no-loop': [ 'error', { version: '8.2' } ], 'no-multiple-none-start-events' : [ 'error', { version: '8.2' } ], @@ -233,6 +240,7 @@ describe('configs', function() { 'link-event': [ 'error', { version: '8.3' } ], 'loop-characteristics': [ 'error', { version: '8.3' } ], 'message-reference': [ 'error', { version: '8.3' } ], + 'no-execution-listeners': [ 'error', { version: '8.3' } ], 'no-expression': [ 'error', { version: '8.3' } ], 'no-loop': [ 'error', { version: '8.3' } ], 'no-multiple-none-start-events' : [ 'error', { version: '8.3' } ], @@ -265,6 +273,7 @@ describe('configs', function() { 'link-event': [ 'error', { version: '8.4' } ], 'loop-characteristics': [ 'error', { version: '8.4' } ], 'message-reference': [ 'error', { version: '8.4' } ], + 'no-execution-listeners': [ 'error', { version: '8.4' } ], 'no-expression': [ 'error', { version: '8.4' } ], 'no-loop': [ 'error', { version: '8.4' } ], 'no-multiple-none-start-events' : [ 'error', { version: '8.4' } ], @@ -297,6 +306,7 @@ describe('configs', function() { 'link-event': [ 'error', { version: '8.5' } ], 'loop-characteristics': [ 'error', { version: '8.5' } ], 'message-reference': [ 'error', { version: '8.5' } ], + 'no-execution-listeners': [ 'error', { version: '8.5' } ], 'no-expression': [ 'error', { version: '8.5' } ], 'no-loop': [ 'error', { version: '8.5' } ], 'no-multiple-none-start-events' : [ 'error', { version: '8.5' } ], @@ -316,6 +326,7 @@ describe('configs', function() { it('camunda-cloud-8-6', expectRules(configs, 'camunda-cloud-8-6', { 'called-element': [ 'error', { version: '8.6' } ], 'connector-properties': [ 'warn', { version: '8.6' } ], + 'duplicate-execution-listeners': [ 'error', { version: '8.6' } ], 'duplicate-task-headers': [ 'error', { version: '8.6' } ], 'element-type': [ 'error', { version: '8.6' } ], 'error-reference': [ 'error', { version: '8.6' } ], @@ -323,6 +334,7 @@ describe('configs', function() { 'escalation-reference': [ 'error', { version: '8.6' } ], 'event-based-gateway-target': [ 'error', { version: '8.6' } ], 'executable-process': [ 'error', { version: '8.6' } ], + 'execution-listener': [ 'error', { version: '8.6' } ], 'feel': [ 'error', { version: '8.6' } ], 'implementation': [ 'error', { version: '8.6' } ], 'link-event': [ 'error', { version: '8.6' } ], @@ -368,12 +380,14 @@ describe('configs', function() { 'called-element': 'error', 'collapsed-subprocess': 'error', 'connector-properties': 'warn', + 'duplicate-execution-listeners': 'error', 'duplicate-task-headers': 'error', 'error-reference': 'error', 'escalation-boundary-event-attached-to-ref': 'error', 'escalation-reference': 'error', 'event-based-gateway-target': 'error', 'executable-process': 'error', + 'execution-listener': 'error', 'feel': 'error', 'history-time-to-live': 'info', 'implementation': 'error', @@ -382,6 +396,7 @@ describe('configs', function() { 'loop-characteristics': 'error', 'message-reference': 'error', 'no-candidate-users': 'error', + 'no-execution-listeners': 'error', 'no-expression': 'error', 'no-loop': 'error', 'no-multiple-none-start-events': 'error', diff --git a/test/utils/element.spec.js b/test/utils/element.spec.js index 16bd82a..bde6cad 100644 --- a/test/utils/element.spec.js +++ b/test/utils/element.spec.js @@ -4,6 +4,7 @@ const { ERROR_TYPES, formatNames, hasDuplicatedPropertyValues, + hasDuplicatedPropertiesValues, hasExpression, hasExtensionElement, hasNoExtensionElement, @@ -450,6 +451,156 @@ describe('utils/element', function() { }); + describe('#hasDuplicatedPropertiesValues', function() { + + it('should not return errors', function() { + + // given + const executionListeners = createElement('zeebe:ExecutionListeners', { + listeners: [ + createElement('zeebe:ExecutionListener', { + eventType: 'start', + type: 'foo' + }), + createElement('zeebe:ExecutionListener', { + eventType: 'start', + type: 'bar' + }), + createElement('zeebe:ExecutionListener', { + eventType: 'start', + type: 'baz' + }) + ] + }); + + // when + const errors = hasDuplicatedPropertiesValues(executionListeners, 'listeners', [ 'eventType', 'type' ]); + + // then + expect(errors).to.be.empty; + }); + + + it('should return error', function() { + + // given + const executionListeners = createElement('zeebe:ExecutionListeners', { + listeners: [ + createElement('zeebe:ExecutionListener', { + eventType: 'start', + type: 'foo' + }), + createElement('zeebe:ExecutionListener', { + eventType: 'start', + type: 'foo' + }), + createElement('zeebe:ExecutionListener', { + eventType: 'start', + type: 'bar' + }), + createElement('zeebe:ExecutionListener', { + eventType: 'start', + type: 'baz' + }) + ] + }); + + // when + const errors = hasDuplicatedPropertiesValues(executionListeners, 'listeners', [ 'eventType', 'type' ]); + + // then + expect(errors).to.exist; + expect(errors).to.have.length(1); + + expect(errors[ 0 ]).eql({ + message: 'Properties of type have properties with duplicate values (property with duplicate value of , property with duplicate value of )', + path: null, + data: { + type: ERROR_TYPES.PROPERTY_VALUES_DUPLICATED, + node: executionListeners, + parentNode: null, + duplicatedProperties: { + eventType: 'start', + type: 'foo' + }, + properties: executionListeners.get('listeners').filter(listener => listener.get('type') === 'foo'), + propertiesName: 'listeners' + } + }); + }); + + + it('should return errors', function() { + + // given + const executionListeners = createElement('zeebe:ExecutionListeners', { + listeners: [ + createElement('zeebe:ExecutionListener', { + eventType: 'start', + type: 'foo' + }), + createElement('zeebe:ExecutionListener', { + eventType: 'start', + type: 'foo' + }), + createElement('zeebe:ExecutionListener', { + eventType: 'start', + type: 'bar' + }), + createElement('zeebe:ExecutionListener', { + eventType: 'start', + type: 'bar' + }), + createElement('zeebe:ExecutionListener', { + eventType: 'start', + type: 'baz' + }) + ] + }); + + // when + const errors = hasDuplicatedPropertiesValues(executionListeners, 'listeners', [ 'eventType', 'type' ]); + + // then + expect(errors).to.exist; + expect(errors).to.have.length(2); + + expect(errors[ 0 ]).eql({ + message: 'Properties of type have properties with duplicate values (property with duplicate value of , property with duplicate value of )', + path: null, + data: { + type: ERROR_TYPES.PROPERTY_VALUES_DUPLICATED, + node: executionListeners, + parentNode: null, + duplicatedProperties: { + eventType: 'start', + type: 'foo' + }, + properties: executionListeners.get('listeners').filter(listener => listener.get('type') === 'foo'), + propertiesName: 'listeners' + } + }); + + expect(errors[ 1 ]).eql({ + message: 'Properties of type have properties with duplicate values (property with duplicate value of , property with duplicate value of )', + path: null, + data: { + type: ERROR_TYPES.PROPERTY_VALUES_DUPLICATED, + node: executionListeners, + parentNode: null, + duplicatedProperties: { + eventType: 'start', + type: 'bar' + }, + properties: executionListeners.get('listeners').filter(listener => listener.get('type') === 'bar'), + propertiesName: 'listeners' + } + }); + }); + + }); + + describe('#hasProperty', function() { it('should not return errors (single property)', function() {