From af293a91d12771d02ea37246fb02c53b57a10c03 Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 19 Jan 2023 10:47:07 -0500 Subject: [PATCH] Squashed 'SpiffWorkflow/' changes from 4195453a4..1f51db962 1f51db962 Merge pull request #283 from sartography/feature/better_errors 69fb4967e Patching up some bugs and logical disconnects as I test out the errors. cf5be0096 * Making a few more things consistent in the error messages -- so there isn't filename for validation errors, and file_name for WorkflowExceptions. Same for line_number vs sourceline. * Assure than an error_type is consistently set on exceptions. * ValidationExceptions should not bild up a detailed error message that replicates information available within it. 440ee16c8 Responding to some excellent suggestions from Elizabeth: 655e415e1 Merge pull request #282 from subhakarks/fix-workfowspec-dump 1f6d3cf4e Explain that the error happened in a pre-script or post script. 8119abd14 Added a top level SpiffWorklowException that all exceptions inherit from. Aside from a message string you can append information to these exceptions with "add_note", which is a new method that all exceptions have starting in python 3.11 Switched arguments to the WorkflowException, WorkflowTaskException - which now always takes a string message as the first argument, and named arguments thereafter to be consistent with all other error messages in Python. Consistently raise ValidationExceptions whenever we encounter an error anywhere during parsing of xml. The BPMN/WorkflowTaskExecException is removed, in favor of just calling a WorkflowTaskException. There is nothing BPMN Specific in the logic, so no need for this. Consolidated error message logic so that things like "Did you mean" just get added by default if possible. So we don't have to separately deal with that logic each time. Better Error messages for DMN (include row number as a part of the error information) 13463b5c5 fix for workflowspec dump be26100bc Merge pull request #280 from sartography/feature/remove-unused-bpmn-attributes-and-methods 23a5c1d70 remove 'entering_* methods 4e5875ec8 remove sequence flow 5eed83ab1 Merge pull request #278 from sartography/feature/remove-old-serializer 614f1c68a remove compact serializer and references e7e410d4a remove old serializer and references git-subtree-dir: SpiffWorkflow git-subtree-split: 1f51db962ccaed5810f5d0f7d76a932f056430ab --- doc/errors.rst | 90 +++++++++++ graphics/spiffworkflow_logo_ideas.svg | 131 ++++++++++++++- tests/SpiffWorkflow/bpmn/ApprovalsTest.py | 89 +--------- .../SpiffWorkflow/bpmn/BpmnSerializerTest.py | 118 -------------- .../bpmn/BpmnWorkflowSerializerTest.py | 9 -- .../bpmn/BpmnWorkflowTestCase.py | 8 +- .../bpmn/CallActivityEndEventTest.py | 4 +- tests/SpiffWorkflow/bpmn/CustomScriptTest.py | 4 +- .../bpmn/InvalidWorkflowsTest.py | 24 +-- tests/SpiffWorkflow/bpmn/ParserTest.py | 17 +- tests/SpiffWorkflow/bpmn/ScriptTest.py | 4 +- tests/SpiffWorkflow/bpmn/ServiceTaskTest.py | 5 +- .../bpmn/data/data_object_invalid.bpmn | 152 ++++++++++++++++++ .../SpiffWorkflow/bpmn/serializer/dictTest.py | 73 --------- .../SpiffWorkflow/bpmn/serializer/jsonTest.py | 38 ----- .../InvalidBusinessRuleTaskParserTest.py | 16 +- .../camunda/data/DMNMultiInstance.bpmn | 88 +++++----- .../data/dmn/test_integer_decision_multi.dmn | 4 +- .../camunda/specs/UserTaskSpecTest.py | 100 +++++------- .../spiff/PrescriptPostscriptTest.py | 18 ++- tests/SpiffWorkflow/spiff/ServiceTaskTest.py | 1 - .../spiff/ServiceTaskVariableTest.py | 1 - 22 files changed, 516 insertions(+), 478 deletions(-) create mode 100644 doc/errors.rst delete mode 100644 tests/SpiffWorkflow/bpmn/BpmnSerializerTest.py create mode 100644 tests/SpiffWorkflow/bpmn/data/data_object_invalid.bpmn delete mode 100644 tests/SpiffWorkflow/bpmn/serializer/dictTest.py delete mode 100644 tests/SpiffWorkflow/bpmn/serializer/jsonTest.py diff --git a/doc/errors.rst b/doc/errors.rst new file mode 100644 index 00000000..31c606de --- /dev/null +++ b/doc/errors.rst @@ -0,0 +1,90 @@ +SpiffWorkflow Exceptions +==================================== +Details about the exceptions and exception hierarchy within SpiffWorkflow + +SpiffWorkflowException +---------- +Base exception for all exceptions raised by SpiffWorkflow + +ValidationException +---------- + +**Extends** +SpiffWorkflowException + +Thrown during the parsing of a workflow. + +**Attributes/Methods** + +- **tag**: The type of xml tag being parsed +- **id**: the id attribute of the xml tag, if available. +- **name**: the name attribute of the xml tag, if available. +- **line_number**: the line number where the tag occurs. +- **file_name**: The name of the file where the error occurred. +- **message**: a human readable error message. + + +WorkflowException +-------- +When an error occurs with a Task Specification (maybe should have been called +a SpecException) + +**Extends** +SpiffWorkflowException + +**Attributes/Methods** + +- **sender**: The TaskSpec - the specific Task, Gateway, etc... that caused the error to happen. +- **error**: a human readable error message describing the problem. +- **get_task_trace**: Provided a specific Task, will work it's way through the workflow / sub-processes +and call activities to show where an error occurred. Useful if the error happened within a deeply nested structure (where call activities include call activities ....) + +WorkflowDataException +------------------ +When an exception occurs moving data between tasks and Data Objects (including +data inputs and data outputs.) + +**Extends** +WorkflowException + +**Attributes/Methods** + +(in addition to the values in a WorkflowException) + + - **task**: The specific task (not the task spec, but the actual executing task) + - **data_input**: The spec of the input variable + - **data_output**: The spec of the output variable + +WorkflowTaskException +-------- +**Extends** +WorkflowException + +**Attributes/Methods** + +(in addition to the values in a WorkflowException) + + - **task**: The specific task (not the task spec, but the actual executing task) + - **error_msg**: The detailed human readable message. (conflicts with error above) + - **exception**: The original exception this wraps around. + - **line_number** The line number that contains the error + - **offset** The point in the line that caused the error + - **error_line** The content of the line that caused the error. + +It will accept the line_number and error_line as arguments - if the +underlying error provided is a SyntaxError it will try to derive this +information from the error. +If this is a name error, it will attempt to calculate a did-you-mean +error_msg. + +Unused / Deprecated errors +-------------------- + +** StorageException ** +Deprecated -- Used only by the PrettyXmlSerializer - which is not under active +support. + +** DeadMethodCalled ** +Something related to WeakMethod -- which doesn't look to be utilized anymore. + + diff --git a/graphics/spiffworkflow_logo_ideas.svg b/graphics/spiffworkflow_logo_ideas.svg index 28e5d8ac..b51b8b52 100644 --- a/graphics/spiffworkflow_logo_ideas.svg +++ b/graphics/spiffworkflow_logo_ideas.svg @@ -26,13 +26,13 @@ showgrid="false" showguides="true" inkscape:guide-bbox="true" - inkscape:zoom="0.27433373" - inkscape:cx="-586.87643" - inkscape:cy="1882.7433" + inkscape:zoom="1.5518659" + inkscape:cx="2265.0153" + inkscape:cy="3541.2209" inkscape:window-width="1916" - inkscape:window-height="1076" + inkscape:window-height="916" inkscape:window-x="0" - inkscape:window-y="0" + inkscape:window-y="162" inkscape:window-maximized="1" inkscape:current-layer="layer1"> + + + @@ -839,6 +851,91 @@ x="-580.08496" y="716.69928">Draw the code + + + + + + + + + + + + + + + + + + Draw the code + + + + + diff --git a/tests/SpiffWorkflow/bpmn/ApprovalsTest.py b/tests/SpiffWorkflow/bpmn/ApprovalsTest.py index 7576a23f..857c81f0 100644 --- a/tests/SpiffWorkflow/bpmn/ApprovalsTest.py +++ b/tests/SpiffWorkflow/bpmn/ApprovalsTest.py @@ -2,7 +2,6 @@ import unittest from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from SpiffWorkflow.bpmn.specs.events.event_definitions import MessageEventDefinition from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' @@ -86,8 +85,7 @@ class ApprovalsTest(BpmnWorkflowTestCase): self.save_restore() self.do_next_named_step('Parallel_Approvals_SP.Manager_Approval') self.do_next_exclusive_step('Parallel_Approvals_SP.Step1') - self.do_next_exclusive_step( - 'Parallel_Approvals_SP.Supervisor_Approval') + self.do_next_exclusive_step('Parallel_Approvals_SP.Supervisor_Approval') self.do_next_exclusive_step('Approvals.Parallel_SP_Done') def testSaveRestoreWaiting(self): @@ -108,93 +106,10 @@ class ApprovalsTest(BpmnWorkflowTestCase): self.save_restore() self.do_next_exclusive_step('Parallel_Approvals_SP.Step1') self.save_restore() - self.do_next_exclusive_step( - 'Parallel_Approvals_SP.Supervisor_Approval') + self.do_next_exclusive_step('Parallel_Approvals_SP.Supervisor_Approval') self.save_restore() self.do_next_exclusive_step('Approvals.Parallel_SP_Done') - def testReadonlyWaiting(self): - - self.do_next_named_step('First_Approval_Wins.Manager_Approval') - - readonly = self.get_read_only_workflow() - self.assertEqual(1, len(readonly.get_ready_user_tasks())) - self.assertEqual('Approvals.First_Approval_Wins_Done', - readonly.get_ready_user_tasks()[0].task_spec.name) - self.assertRaises(AssertionError, readonly.do_engine_steps) - self.assertRaises(AssertionError, readonly.refresh_waiting_tasks) - self.assertRaises(AssertionError, readonly.catch, MessageEventDefinition('Cheese')) - self.assertRaises( - AssertionError, readonly.get_ready_user_tasks()[0].complete) - - self.do_next_exclusive_step('Approvals.First_Approval_Wins_Done') - - readonly = self.get_read_only_workflow() - self.assertEqual(2, len(readonly.get_ready_user_tasks())) - self.assertEqual( - ['Approvals.Manager_Approval__P_', - 'Approvals.Supervisor_Approval__P_'], - sorted(t.task_spec.name for t in readonly.get_ready_user_tasks())) - self.assertRaises( - AssertionError, readonly.get_ready_user_tasks()[0].complete) - - self.do_next_named_step('Approvals.Supervisor_Approval__P_') - - readonly = self.get_read_only_workflow() - self.assertEqual(1, len(readonly.get_ready_user_tasks())) - self.assertEqual('Approvals.Manager_Approval__P_', - readonly.get_ready_user_tasks()[0].task_spec.name) - self.assertRaises( - AssertionError, readonly.get_ready_user_tasks()[0].complete) - self.do_next_named_step('Approvals.Manager_Approval__P_') - - readonly = self.get_read_only_workflow() - self.assertEqual(1, len(readonly.get_ready_user_tasks())) - self.assertEqual('Approvals.Parallel_Approvals_Done', - readonly.get_ready_user_tasks()[0].task_spec.name) - self.assertRaises( - AssertionError, readonly.get_ready_user_tasks()[0].complete) - self.do_next_exclusive_step('Approvals.Parallel_Approvals_Done') - - readonly = self.get_read_only_workflow() - self.assertEqual(2, len(readonly.get_ready_user_tasks())) - self.assertEqual( - ['Parallel_Approvals_SP.Manager_Approval', - 'Parallel_Approvals_SP.Step1'], - sorted(t.task_spec.name for t in readonly.get_ready_user_tasks())) - self.assertRaises( - AssertionError, readonly.get_ready_user_tasks()[0].complete) - self.do_next_named_step('Parallel_Approvals_SP.Manager_Approval') - - readonly = self.get_read_only_workflow() - self.assertEqual(1, len(readonly.get_ready_user_tasks())) - self.assertEqual('Parallel_Approvals_SP.Step1', - readonly.get_ready_user_tasks()[0].task_spec.name) - self.assertRaises( - AssertionError, readonly.get_ready_user_tasks()[0].complete) - self.do_next_exclusive_step('Parallel_Approvals_SP.Step1') - - readonly = self.get_read_only_workflow() - self.assertEqual(1, len(readonly.get_ready_user_tasks())) - self.assertEqual('Parallel_Approvals_SP.Supervisor_Approval', - readonly.get_ready_user_tasks()[0].task_spec.name) - self.assertRaises( - AssertionError, readonly.get_ready_user_tasks()[0].complete) - self.do_next_exclusive_step( - 'Parallel_Approvals_SP.Supervisor_Approval') - - readonly = self.get_read_only_workflow() - self.assertEqual(1, len(readonly.get_ready_user_tasks())) - self.assertEqual('Approvals.Parallel_SP_Done', - readonly.get_ready_user_tasks()[0].task_spec.name) - self.assertRaises( - AssertionError, readonly.get_ready_user_tasks()[0].complete) - self.do_next_exclusive_step('Approvals.Parallel_SP_Done') - - readonly = self.get_read_only_workflow() - self.assertEqual(0, len(readonly.get_ready_user_tasks())) - self.assertEqual(0, len(readonly.get_waiting_tasks())) - def suite(): return unittest.TestLoader().loadTestsFromTestCase(ApprovalsTest) diff --git a/tests/SpiffWorkflow/bpmn/BpmnSerializerTest.py b/tests/SpiffWorkflow/bpmn/BpmnSerializerTest.py deleted file mode 100644 index a8b6ebd5..00000000 --- a/tests/SpiffWorkflow/bpmn/BpmnSerializerTest.py +++ /dev/null @@ -1,118 +0,0 @@ -import os -import unittest - -from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine -from SpiffWorkflow.bpmn.serializer.BpmnSerializer import BpmnSerializer -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from .BpmnLoaderForTests import TestBpmnParser - - -class BpmnSerializerTest(unittest.TestCase): - CORRELATE = BpmnSerializer - - def load_workflow_spec(self, filename, process_name): - f = os.path.join(os.path.dirname(__file__), 'data', filename) - parser = TestBpmnParser() - parser.add_bpmn_files_by_glob(f) - top_level_spec = parser.get_spec(process_name) - subprocesses = parser.get_subprocess_specs(process_name) - return top_level_spec, subprocesses - - def setUp(self): - super(BpmnSerializerTest, self).setUp() - self.serializer = BpmnSerializer() - self.spec, subprocesses = self.load_workflow_spec('random_fact.bpmn', 'random_fact') - self.workflow = BpmnWorkflow(self.spec, subprocesses) - - def testDeserializeWorkflowSpec(self): - self.assertIsNotNone(self.spec) - - def testSerializeWorkflowSpec(self): - spec_serialized = self.serializer.serialize_workflow_spec(self.spec) - result = self.serializer.deserialize_workflow_spec(spec_serialized) - spec_serialized2 = self.serializer.serialize_workflow_spec(result) - self.assertEqual(spec_serialized, spec_serialized2) - - def testSerializeWorkflow(self): - json = self.serializer.serialize_workflow(self.workflow) - print(json) - - def testDeserializeWorkflow(self): - self._compare_with_deserialized_copy(self.workflow) - - def testDeserializeCallActivityChildren(self): - """Tested as a part of deserialize workflow.""" - pass - - def testSerializeTask(self): - json = self.serializer.serialize_workflow(self.workflow) - print(json) - - def testDeserializeTask(self): - self._compare_with_deserialized_copy(self.workflow) - - def testDeserializeActiveWorkflow(self): - self.workflow.do_engine_steps() - self._compare_with_deserialized_copy(self.workflow) - - def testDeserializeWithData(self): - self.workflow.data["test"] = "my_test" - json = self.serializer.serialize_workflow(self.workflow) - wf2 = self.serializer.deserialize_workflow(json, workflow_spec=self.spec) - self.assertEqual('my_test', wf2.get_data("test")) - - def testDeserializeWithDefaultScriptEngineClass(self): - json = self.serializer.serialize_workflow(self.workflow) - wf2 = self.serializer.deserialize_workflow(json, workflow_spec=self.spec) - self.assertIsNotNone(self.workflow.script_engine) - self.assertIsNotNone(wf2.script_engine) - self.assertEqual(self.workflow.script_engine.__class__, - wf2.script_engine.__class__) - - @unittest.skip("Deserialize does not persist the script engine, Fix me.") - def testDeserializeWithCustomScriptEngine(self): - class CustomScriptEngine(PythonScriptEngine): - pass - - self.workflow.script_engine = CustomScriptEngine() - json = self.serializer.serialize_workflow(self.workflow) - wf2 = self.serializer.deserialize_workflow(json, workflow_spec=self.spec) - self.assertEqual(self.workflow.script_engine.__class__, - wf2.script_engine.__class__) - - def testDeserializeWithDataOnTask(self): - self.workflow.do_engine_steps() - user_task = self.workflow.get_ready_user_tasks()[0] - user_task.data = {"test":"my_test"} - self._compare_with_deserialized_copy(self.workflow) - - def testLastTaskIsSetAndWorksThroughRestore(self): - self.workflow.do_engine_steps() - json = self.serializer.serialize_workflow(self.workflow) - wf2 = self.serializer.deserialize_workflow(json, workflow_spec=self.spec) - self.assertIsNotNone(self.workflow.last_task) - self.assertIsNotNone(wf2.last_task) - self._compare_workflows(self.workflow, wf2) - - def _compare_with_deserialized_copy(self, wf): - json = self.serializer.serialize_workflow(wf) - wf2 = self.serializer.deserialize_workflow(json, workflow_spec=self.spec) - self._compare_workflows(wf, wf2) - - def _compare_workflows(self, w1, w2): - self.assertIsInstance(w1, BpmnWorkflow) - self.assertIsInstance(w2, BpmnWorkflow) - self.assertEqual(w1.data, w2.data) - self.assertEqual(w1.name, w2.name) - for task in w1.get_ready_user_tasks(): - w2_task = w2.get_task(task.id) - self.assertIsNotNone(w2_task) - self.assertEqual(task.data, w2_task.data) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(BpmnSerializerTest) - - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/tests/SpiffWorkflow/bpmn/BpmnWorkflowSerializerTest.py b/tests/SpiffWorkflow/bpmn/BpmnWorkflowSerializerTest.py index ac2ae463..e5b77156 100644 --- a/tests/SpiffWorkflow/bpmn/BpmnWorkflowSerializerTest.py +++ b/tests/SpiffWorkflow/bpmn/BpmnWorkflowSerializerTest.py @@ -1,13 +1,11 @@ import os import unittest import json -from uuid import uuid4 from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine from SpiffWorkflow.bpmn.parser.BpmnParser import BpmnParser from SpiffWorkflow.bpmn.serializer.workflow import BpmnWorkflowSerializer -from SpiffWorkflow.bpmn.serializer.BpmnSerializer import BpmnSerializer from SpiffWorkflow.bpmn.workflow import BpmnWorkflow from tests.SpiffWorkflow.bpmn.BpmnLoaderForTests import TestUserTaskConverter @@ -48,13 +46,6 @@ class BpmnWorkflowSerializerTest(unittest.TestCase): version = self.serializer.get_version(spec_serialized) self.assertEqual(version, self.SERIALIZER_VERSION) - def testSerializeToOldSerializerThenNewSerializer(self): - old_serializer = BpmnSerializer() - old_json = old_serializer.serialize_workflow(self.workflow) - new_workflow = old_serializer.deserialize_workflow(old_json) - new_json = self.serializer.serialize_json(new_workflow) - new_workflow_2 = self.serializer.deserialize_json(new_json) - def testSerializeWorkflow(self): serialized = self.serializer.serialize_json(self.workflow) json.loads(serialized) diff --git a/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py b/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py index 9b7865bd..8f2f0af5 100644 --- a/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py +++ b/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py @@ -122,7 +122,7 @@ class BpmnWorkflowTestCase(unittest.TestCase): before_dump = self.workflow.get_dump() # Check that we can actully convert this to JSON json_str = json.dumps(before_state) - after = self.serializer.workflow_from_dict(json.loads(json_str), read_only=False) + after = self.serializer.workflow_from_dict(json.loads(json_str)) # Check that serializing and deserializing results in the same workflow after_state = self.serializer.workflow_to_dict(after) after_dump = after.get_dump() @@ -132,11 +132,7 @@ class BpmnWorkflowTestCase(unittest.TestCase): self.workflow = after def restore(self, state): - self.workflow = self.serializer.workflow_from_dict(state, read_only=False) - - def get_read_only_workflow(self): - state = self._get_workflow_state() - return self.serializer.workflow_from_dict(state, read_only=True) + self.workflow = self.serializer.workflow_from_dict(state) def _get_workflow_state(self, do_steps=True): if do_steps: diff --git a/tests/SpiffWorkflow/bpmn/CallActivityEndEventTest.py b/tests/SpiffWorkflow/bpmn/CallActivityEndEventTest.py index f3d1522d..1e3d158e 100644 --- a/tests/SpiffWorkflow/bpmn/CallActivityEndEventTest.py +++ b/tests/SpiffWorkflow/bpmn/CallActivityEndEventTest.py @@ -3,9 +3,9 @@ import unittest from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine -from SpiffWorkflow.bpmn.exceptions import WorkflowTaskExecException from SpiffWorkflow.bpmn.workflow import BpmnWorkflow +from SpiffWorkflow.exceptions import WorkflowTaskException from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'kellym' @@ -60,7 +60,7 @@ class CallActivityTest(BpmnWorkflowTestCase): def test_call_acitivity_errors_include_task_trace(self): error_spec = self.subprocesses.get('ErroringBPMN') error_spec, subprocesses = self.load_workflow_spec('call_activity_*.bpmn', 'ErroringBPMN') - with self.assertRaises(WorkflowTaskExecException) as context: + with self.assertRaises(WorkflowTaskException) as context: self.workflow = BpmnWorkflow(error_spec, subprocesses) self.workflow.do_engine_steps() self.assertEquals(2, len(context.exception.task_trace)) diff --git a/tests/SpiffWorkflow/bpmn/CustomScriptTest.py b/tests/SpiffWorkflow/bpmn/CustomScriptTest.py index 12f69a2a..8cbca47f 100644 --- a/tests/SpiffWorkflow/bpmn/CustomScriptTest.py +++ b/tests/SpiffWorkflow/bpmn/CustomScriptTest.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- import unittest +from SpiffWorkflow.exceptions import WorkflowTaskException from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from SpiffWorkflow.bpmn.exceptions import WorkflowTaskExecException from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'McDonald, danfunk' @@ -46,7 +46,7 @@ class CustomInlineScriptTest(BpmnWorkflowTestCase): def test_overwrite_function_with_local_variable(self): ready_task = self.workflow.get_tasks(TaskState.READY)[0] ready_task.data = {'custom_function': "bill"} - with self.assertRaises(WorkflowTaskExecException) as e: + with self.assertRaises(WorkflowTaskException) as e: self.workflow.do_engine_steps() self.assertTrue('' in str(e.exception)) self.assertTrue('custom_function' in str(e.exception)) diff --git a/tests/SpiffWorkflow/bpmn/InvalidWorkflowsTest.py b/tests/SpiffWorkflow/bpmn/InvalidWorkflowsTest.py index ffb8f024..9987567a 100644 --- a/tests/SpiffWorkflow/bpmn/InvalidWorkflowsTest.py +++ b/tests/SpiffWorkflow/bpmn/InvalidWorkflowsTest.py @@ -31,18 +31,11 @@ class InvalidWorkflowsTest(BpmnWorkflowTestCase): except ValidationException as ex: self.assertTrue('No start event found' in ('%r' % ex), '\'No start event found\' should be a substring of error message: \'%r\'' % ex) - self.assertTrue('No-Start-Event.bpmn20.xml' in ('%r' % ex), + self.assertTrue('No-Start-Event.bpmn20.xml' in ex.file_name, '\'No-Start-Event.bpmn20.xml\' should be a substring of error message: \'%r\'' % ex) - self.assertTrue('process' in ('%r' % ex), - '\'process\' should be a substring of error message: \'%r\'' % ex) - self.assertTrue( - 'sid-669ddebf-4196-41ee-8b04-bcc90bc5f983' in ('%r' % ex), - '\'sid-669ddebf-4196-41ee-8b04-bcc90bc5f983\' should be a substring of error message: \'%r\'' % ex) - self.assertTrue('No Start Event' in ('%r' % ex), - '\'No Start Event\' should be a substring of error message: \'%r\'' % ex) def testSubprocessNotFound(self): - + with self.assertRaises(ValidationException) as exc: self.load_workflow_spec('Invalid-Workflows/Subprocess-Not-Found.bpmn20.xml', 'Subprocess Not Found') self.assertIn("The process 'Missing subprocess' was not found.", str(exc)) @@ -60,15 +53,12 @@ class InvalidWorkflowsTest(BpmnWorkflowTestCase): 'There is no support implemented for this task type' in ( '%r' % ex), '\'There is no support implemented for this task type\' should be a substring of error message: \'%r\'' % ex) - self.assertTrue('Unsupported-Task.bpmn20.xml' in ('%r' % ex), + self.assertTrue('Unsupported-Task.bpmn20.xml' in ex.file_name, '\'Unsupported-Task.bpmn20.xml\' should be a substring of error message: \'%r\'' % ex) - self.assertTrue('businessRuleTask' in ('%r' % ex), - '\'businessRuleTask\' should be a substring of error message: \'%r\'' % ex) - self.assertTrue( - 'sid-75EEAB28-3B69-4282-B91A-0F3C97931834' in ('%r' % ex), - '\'sid-75EEAB28-3B69-4282-B91A-0F3C97931834\' should be a substring of error message: \'%r\'' % ex) - self.assertTrue('Business Rule Task' in ('%r' % ex), - '\'Business Rule Task\' should be a substring of error message: \'%r\'' % ex) + self.assertTrue('businessRuleTask' in ex.tag, + '\'businessRuleTask\' should be a substring of the tag: \'%r\'' % ex) + self.assertTrue('Business Rule Task' in ex.name, + '\'Business Rule Task\' should be the name: \'%s\'' % ex.name) def suite(): diff --git a/tests/SpiffWorkflow/bpmn/ParserTest.py b/tests/SpiffWorkflow/bpmn/ParserTest.py index 5703273e..59a32775 100644 --- a/tests/SpiffWorkflow/bpmn/ParserTest.py +++ b/tests/SpiffWorkflow/bpmn/ParserTest.py @@ -1,7 +1,8 @@ import unittest import os -from SpiffWorkflow.bpmn.parser.BpmnParser import BpmnParser +from SpiffWorkflow.bpmn.parser.BpmnParser import BpmnParser, BpmnValidator +from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException class ParserTest(unittest.TestCase): @@ -27,3 +28,17 @@ class ParserTest(unittest.TestCase): self.assertEqual(generate.data_output_associations[0].name, 'obj_1') self.assertEqual(len(read.data_input_associations), 1) self.assertEqual(read.data_input_associations[0].name, 'obj_1') + + def testValidatorError(self): + parser = BpmnParser(validator=BpmnValidator()) + bpmn_file = os.path.join(os.path.dirname(__file__), 'data', + 'data_object_invalid.bpmn') + errored = False + try: + parser.add_bpmn_file(bpmn_file) + except ValidationException as ex: + errored = True + self.assertEqual(ex.file_name, bpmn_file) + self.assertEqual(14, ex.line_number) + self.assertIn('DataObjectReference_0cm8dnh', str(ex)) + assert(errored, "This should have errored out with a validation exception.") diff --git a/tests/SpiffWorkflow/bpmn/ScriptTest.py b/tests/SpiffWorkflow/bpmn/ScriptTest.py index efe9f1c8..da0a4a29 100644 --- a/tests/SpiffWorkflow/bpmn/ScriptTest.py +++ b/tests/SpiffWorkflow/bpmn/ScriptTest.py @@ -2,7 +2,7 @@ import unittest -from SpiffWorkflow.bpmn.exceptions import WorkflowTaskExecException +from SpiffWorkflow.exceptions import WorkflowTaskException from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase @@ -39,7 +39,7 @@ class InlineScriptTest(BpmnWorkflowTestCase): # StartTask doesn't know about testvar, it happened earlier. # calling an exec that references testvar, in the context of the # start task should fail. - with self.assertRaises(WorkflowTaskExecException): + with self.assertRaises(WorkflowTaskException): result = self.workflow.script_engine.evaluate(startTask, 'testvar == True') diff --git a/tests/SpiffWorkflow/bpmn/ServiceTaskTest.py b/tests/SpiffWorkflow/bpmn/ServiceTaskTest.py index 29060708..a0112efe 100644 --- a/tests/SpiffWorkflow/bpmn/ServiceTaskTest.py +++ b/tests/SpiffWorkflow/bpmn/ServiceTaskTest.py @@ -8,16 +8,15 @@ sys.path.insert(0, os.path.join(dirname, '..', '..', '..')) from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from SpiffWorkflow.bpmn.exceptions import WorkflowTaskExecException from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase class ServiceTaskTest(BpmnWorkflowTestCase): def setUp(self): - spec, subprocesses = self.load_workflow_spec('service_task.bpmn', + spec, subprocesses = self.load_workflow_spec('service_task.bpmn', 'service_task_example1') - self.workflow = BpmnWorkflow(spec, subprocesses) + self.workflow = BpmnWorkflow(spec, subprocesses) def testRunThroughHappy(self): self.workflow.do_engine_steps() diff --git a/tests/SpiffWorkflow/bpmn/data/data_object_invalid.bpmn b/tests/SpiffWorkflow/bpmn/data/data_object_invalid.bpmn new file mode 100644 index 00000000..1d2d2ed8 --- /dev/null +++ b/tests/SpiffWorkflow/bpmn/data/data_object_invalid.bpmn @@ -0,0 +1,152 @@ + + + + + + Flow_18858hr + + + + + + + + + + Flow_19pyf8s + + + + Flow_1r7v9yo + Flow_1tnu3ej + + + DataObjectReference_0pztwm3 + Property_1uusomz + + + + Flow_18858hr + Flow_0gbxq9s + + DataObjectReference_17fhr1j + + + + Flow_0gbxq9s + Flow_1r7v9yo + + + Flow_1tnu3ej + Flow_19pyf8s + + + DataObjectReference_0cm8dnh + Property_1q5wp77 + + + Flow_0yx8lkz + + + Flow_0yx8lkz + Flow_0rk4i35 + + + + Flow_0rk4i35 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/SpiffWorkflow/bpmn/serializer/dictTest.py b/tests/SpiffWorkflow/bpmn/serializer/dictTest.py deleted file mode 100644 index 3556bee1..00000000 --- a/tests/SpiffWorkflow/bpmn/serializer/dictTest.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- - -from builtins import str -import sys -import unittest -import os -dirname = os.path.dirname(__file__) -sys.path.insert(0, os.path.join(dirname, '..', '..', '..', '..')) - -import uuid -from SpiffWorkflow.bpmn.serializer.dict import BPMNDictionarySerializer -from tests.SpiffWorkflow.serializer.baseTest import SerializerTest -from SpiffWorkflow.workflow import Workflow - - -class BPMNDictionarySerializerTest(SerializerTest): - - def setUp(self): - super(BPMNDictionarySerializerTest, self).setUp() - self.serializer = BPMNDictionarySerializer() - self.return_type = dict - - def _compare_results(self, item1, item2, - exclude_dynamic=False, - exclude_items=None): - exclude_items = exclude_items if exclude_items is not None else [] - if exclude_dynamic: - if 'last_state_change' not in exclude_items: - exclude_items.append('last_state_change') - if 'last_task' not in exclude_items: - exclude_items.append('last_task') - if uuid.UUID not in exclude_items: - exclude_items.append(uuid.UUID) - if type(item1) in exclude_items: - return - - if isinstance(item1, dict): - self.assertIsInstance(item2, dict) - for key, value in list(item1.items()): - self.assertIn(key, item2) - if key in exclude_items: - continue - self._compare_results(value, item2[key], - exclude_dynamic=exclude_dynamic, - exclude_items=exclude_items) - for key in item2: - self.assertIn(key, item1) - - elif isinstance(item1, list): - msg = "item is not a list (is a " + str(type(item2)) + ")" - self.assertIsInstance(item2, list, msg) - msg = "list lengths differ: {} vs {}".format( - len(item1), len(item2)) - self.assertEqual(len(item1), len(item2), msg) - for i, listitem in enumerate(item1): - self._compare_results(listitem, item2[i], - exclude_dynamic=exclude_dynamic, - exclude_items=exclude_items) - - elif isinstance(item1, Workflow): - raise Exception("Item is a Workflow") - - else: - msg = "{}: types differ: {} vs {}".format( - str(item2), type(item1), type(item2)) - self.assertEqual(type(item1), type(item2), msg) - self.assertEqual(item1, item2) - - -def suite(): - return unittest.defaultTestLoader.loadTestsFromTestCase(BPMNDictionarySerializerTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/tests/SpiffWorkflow/bpmn/serializer/jsonTest.py b/tests/SpiffWorkflow/bpmn/serializer/jsonTest.py deleted file mode 100644 index 89ee0542..00000000 --- a/tests/SpiffWorkflow/bpmn/serializer/jsonTest.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- - -import sys -import unittest -import os -dirname = os.path.dirname(__file__) -sys.path.insert(0, os.path.join(dirname, '..', '..', '..', '..')) - -import json -from SpiffWorkflow.bpmn.serializer.json import BPMNJSONSerializer -from tests.SpiffWorkflow.serializer.dictTest import DictionarySerializerTest - - -class BPMNJSONSerializerTest(DictionarySerializerTest): - - def setUp(self): - super(BPMNJSONSerializerTest, self).setUp() - self.serializer = BPMNJSONSerializer() - self.return_type = str - - def _prepare_result(self, item): - return json.loads(item) - - def _compare_results(self, item1, item2, exclude_dynamic=False, - exclude_items=None): - if exclude_dynamic: - exclude_items = ['__uuid__'] - else: - exclude_items = [] - super(BPMNJSONSerializerTest, self)._compare_results(item1, item2, - exclude_dynamic=exclude_dynamic, - exclude_items=exclude_items) - - -def suite(): - return unittest.defaultTestLoader.loadTestsFromTestCase(BPMNJSONSerializerTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/tests/SpiffWorkflow/camunda/InvalidBusinessRuleTaskParserTest.py b/tests/SpiffWorkflow/camunda/InvalidBusinessRuleTaskParserTest.py index 3ca9f3d0..757767d6 100644 --- a/tests/SpiffWorkflow/camunda/InvalidBusinessRuleTaskParserTest.py +++ b/tests/SpiffWorkflow/camunda/InvalidBusinessRuleTaskParserTest.py @@ -1,7 +1,7 @@ import os import unittest -from SpiffWorkflow.bpmn.exceptions import WorkflowTaskExecException +from SpiffWorkflow.exceptions import SpiffWorkflowException, WorkflowException from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow @@ -15,17 +15,23 @@ class BusinessRuleTaskParserTest(BaseTestCase): 'invalid/InvalidDecision.bpmn', 'Process_1', 'invalid_decision.dmn') self.workflow = BpmnWorkflow(self.spec) + def testExceptionPrint(self): + e1 = Exception("test 1") + print (e1) + e = SpiffWorkflowException("test") + print (e) + def testDmnRaisesTaskErrors(self): self.workflow = BpmnWorkflow(self.spec) self.workflow.get_tasks(TaskState.READY)[0].set_data(x=3) try: self.workflow.do_engine_steps() self.assertTrue(False, "An error should have been raised.") - except WorkflowTaskExecException as we: + except WorkflowException as we: self.assertTrue(True, "An error was raised..") - self.assertEquals("InvalidDecisionTaskId", we.sender.name) - self.maxDiff = 1000 - self.assertEquals("Error evaluating expression spam= 1", str(we)) + self.assertEqual("InvalidDecisionTaskId", we.task_spec.name) + self.maxDiff = 1000 + self.assertEquals("Error evaluating expression 'spam= 1'. Rule failed on row 1. Business Rule Task 'Invalid Decision'.", str(we)) def suite(): return unittest.TestLoader().loadTestsFromTestCase(BusinessRuleTaskParserTest) diff --git a/tests/SpiffWorkflow/camunda/data/DMNMultiInstance.bpmn b/tests/SpiffWorkflow/camunda/data/DMNMultiInstance.bpmn index ddf2c44e..d00fd87d 100644 --- a/tests/SpiffWorkflow/camunda/data/DMNMultiInstance.bpmn +++ b/tests/SpiffWorkflow/camunda/data/DMNMultiInstance.bpmn @@ -1,5 +1,5 @@ - + Flow_1b29lxw @@ -46,59 +46,59 @@ of documentation - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - + - - - - - - - - - - - - + - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/tests/SpiffWorkflow/camunda/data/dmn/test_integer_decision_multi.dmn b/tests/SpiffWorkflow/camunda/data/dmn/test_integer_decision_multi.dmn index 7565b4c0..a24c8400 100644 --- a/tests/SpiffWorkflow/camunda/data/dmn/test_integer_decision_multi.dmn +++ b/tests/SpiffWorkflow/camunda/data/dmn/test_integer_decision_multi.dmn @@ -1,8 +1,8 @@ - + - + item.x diff --git a/tests/SpiffWorkflow/camunda/specs/UserTaskSpecTest.py b/tests/SpiffWorkflow/camunda/specs/UserTaskSpecTest.py index 33f62191..3de8fa2a 100644 --- a/tests/SpiffWorkflow/camunda/specs/UserTaskSpecTest.py +++ b/tests/SpiffWorkflow/camunda/specs/UserTaskSpecTest.py @@ -1,9 +1,7 @@ -import json import unittest -from SpiffWorkflow.camunda.specs.UserTask import FormField, UserTask, Form, \ - EnumFormField -from SpiffWorkflow.specs.base import TaskSpec +from SpiffWorkflow.camunda.specs.UserTask import FormField, UserTask, Form, EnumFormField +from SpiffWorkflow.camunda.serializer.task_spec_converters import UserTaskConverter from SpiffWorkflow.specs.WorkflowSpec import WorkflowSpec @@ -13,7 +11,6 @@ class UserTaskSpecTest(unittest.TestCase): def create_instance(self): if 'testtask' in self.wf_spec.task_specs: del self.wf_spec.task_specs['testtask'] - task_spec = TaskSpec(self.wf_spec, 'testtask', description='foo') self.form = Form() return UserTask(self.wf_spec, 'userTask', self.form) @@ -33,43 +30,6 @@ class UserTaskSpecTest(unittest.TestCase): self.assertEqual(self.form, self.user_spec.form) def testSerialize(self): - pass - - def test_text_field(self): - form_field = FormField(form_type="text") - form_field.id = "1234" - self.form.add_field(form_field) - self.assertEqual(form_field, self.user_spec.form.fields[0]) - - def test_enum_field(self): - enum_field = EnumFormField() - enum_field.label = "Which kind of fool are you" - enum_field.add_option('old fool', 'This is old, therefor it is good.') - enum_field.add_option('new fool', - 'This is new, therefor it is better.') - self.form.add_field(enum_field) - self.assertEqual(enum_field, self.user_spec.form.fields[-1]) - - def test_properties(self): - form_field = FormField(form_type="text") - self.assertFalse(form_field.has_property("wilma")) - form_field.add_property("wilma", "flintstone") - self.assertTrue(form_field.has_property("wilma")) - self.assertEquals("flintstone", form_field.get_property("wilma")) - - def test_validations(self): - form_field = FormField(form_type="text") - self.assertFalse(form_field.has_validation("barney")) - form_field.add_validation("barney", "rubble") - self.assertTrue(form_field.has_validation("barney")) - self.assertEquals("rubble", form_field.get_validation("barney")) - - def testIsEngineTask(self): - self.assertFalse(self.user_spec.is_engine_task()) - - def test_convert_to_dict(self): - form = Form() - field1 = FormField(form_type="text") field1.id = "quest" field1.label = "What is your quest?" @@ -89,21 +49,14 @@ class UserTaskSpecTest(unittest.TestCase): field2.add_property("description", "You know what to do.") field2.add_validation("maxlength", "25") - form.key = "formKey" - form.add_field(field1) - form.add_field(field2) + self.form.key = "formKey" + self.form.add_field(field1) + self.form.add_field(field2) - def JsonableHandler(Obj): - if hasattr(Obj, 'jsonable'): - return Obj.jsonable() - else: - raise 'Object of type %s with value of %s is not JSON serializable' % ( - type(Obj), repr(Obj)) - - json_form = json.dumps(form, default=JsonableHandler) - actual = json.loads(json_form) - - expected = { + converter = UserTaskConverter() + dct = converter.to_dict(self.user_spec) + self.assertEqual(dct['name'], 'userTask') + self.assertEqual(dct['form'], { "fields": [ { "default_value": "I seek the grail!", @@ -137,12 +90,39 @@ class UserTaskSpecTest(unittest.TestCase): } ], "key": "formKey", - } + }) - expected_parsed = json.loads(json.dumps(expected)) + def test_text_field(self): + form_field = FormField(form_type="text") + form_field.id = "1234" + self.form.add_field(form_field) + self.assertEqual(form_field, self.user_spec.form.fields[0]) - self.maxDiff = None - self.assertDictEqual(actual, expected_parsed) + def test_enum_field(self): + enum_field = EnumFormField() + enum_field.label = "Which kind of fool are you" + enum_field.add_option('old fool', 'This is old, therefor it is good.') + enum_field.add_option('new fool', + 'This is new, therefor it is better.') + self.form.add_field(enum_field) + self.assertEqual(enum_field, self.user_spec.form.fields[-1]) + + def test_properties(self): + form_field = FormField(form_type="text") + self.assertFalse(form_field.has_property("wilma")) + form_field.add_property("wilma", "flintstone") + self.assertTrue(form_field.has_property("wilma")) + self.assertEquals("flintstone", form_field.get_property("wilma")) + + def test_validations(self): + form_field = FormField(form_type="text") + self.assertFalse(form_field.has_validation("barney")) + form_field.add_validation("barney", "rubble") + self.assertTrue(form_field.has_validation("barney")) + self.assertEquals("rubble", form_field.get_validation("barney")) + + def testIsEngineTask(self): + self.assertFalse(self.user_spec.is_engine_task()) def suite(): diff --git a/tests/SpiffWorkflow/spiff/PrescriptPostscriptTest.py b/tests/SpiffWorkflow/spiff/PrescriptPostscriptTest.py index 52077059..320586a9 100644 --- a/tests/SpiffWorkflow/spiff/PrescriptPostscriptTest.py +++ b/tests/SpiffWorkflow/spiff/PrescriptPostscriptTest.py @@ -1,3 +1,4 @@ +from SpiffWorkflow.exceptions import SpiffWorkflowException from SpiffWorkflow.task import TaskState from .BaseTestCase import BaseTestCase from SpiffWorkflow.bpmn.workflow import BpmnWorkflow @@ -18,7 +19,7 @@ class PrescriptPostsciptTest(BaseTestCase): self.call_activity_test(True) def testDataObject(self): - + spec, subprocesses = self.load_workflow_spec('prescript_postscript_data_object.bpmn', 'Process_1') self.workflow = BpmnWorkflow(spec, subprocesses) # Set a on the workflow and b in the first task. @@ -45,8 +46,21 @@ class PrescriptPostsciptTest(BaseTestCase): ready_tasks[0].complete() self.assertDictEqual({'a': 1, 'b': 2, 'c': 12, 'z': 6}, ready_tasks[0].data) + def test_for_error(self, save_restore=False): + + spec, subprocesses = self.load_workflow_spec('prescript_postscript.bpmn', 'Process_1') + self.workflow = BpmnWorkflow(spec, subprocesses) + if save_restore: + self.save_restore() + ready_tasks = self.workflow.get_tasks(TaskState.READY) + # Calling do-engine steps without setting variables will raise an exception. + with self.assertRaises(SpiffWorkflowException) as se: + self.workflow.do_engine_steps() + ex = se.exception + self.assertIn("Error occurred in the Pre-Script", str(ex)) + def call_activity_test(self, save_restore=False): - + spec, subprocesses = self.load_workflow_spec('prescript_postscript_*.bpmn', 'parent') self.workflow = BpmnWorkflow(spec, subprocesses) if save_restore: diff --git a/tests/SpiffWorkflow/spiff/ServiceTaskTest.py b/tests/SpiffWorkflow/spiff/ServiceTaskTest.py index 1d3035b3..66b2d86a 100644 --- a/tests/SpiffWorkflow/spiff/ServiceTaskTest.py +++ b/tests/SpiffWorkflow/spiff/ServiceTaskTest.py @@ -9,7 +9,6 @@ sys.path.insert(0, os.path.join(dirname, '..', '..', '..')) from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from SpiffWorkflow.bpmn.exceptions import WorkflowTaskExecException from .BaseTestCase import BaseTestCase class ServiceTaskDelegate: diff --git a/tests/SpiffWorkflow/spiff/ServiceTaskVariableTest.py b/tests/SpiffWorkflow/spiff/ServiceTaskVariableTest.py index 12237ae6..834f0c6f 100644 --- a/tests/SpiffWorkflow/spiff/ServiceTaskVariableTest.py +++ b/tests/SpiffWorkflow/spiff/ServiceTaskVariableTest.py @@ -9,7 +9,6 @@ sys.path.insert(0, os.path.join(dirname, '..', '..', '..')) from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from SpiffWorkflow.bpmn.exceptions import WorkflowTaskExecException from .BaseTestCase import BaseTestCase class ServiceTaskDelegate: