diff --git a/Pipfile.lock b/Pipfile.lock index d9c2bfab..ce620efc 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -428,10 +428,10 @@ }, "mako": { "hashes": [ - "sha256:3139c5d64aa5d175dbafb95027057128b5fbd05a40c53999f3905ceb53366d9d", - "sha256:8e8b53c71c7e59f3de716b6832c4e401d903af574f6962edbbbf6ecc2a5fe6c9" + "sha256:8195c8c1400ceb53496064314c6736719c6f25e7479cd24c77be3d9361cddc27", + "sha256:93729a258e4ff0747c876bd9e20df1b9758028946e976324ccd2d68245c7b6a9" ], - "version": "==1.1.2" + "version": "==1.1.3" }, "markupsafe": { "hashes": [ @@ -489,11 +489,11 @@ }, "marshmallow-sqlalchemy": { "hashes": [ - "sha256:3247e41e424146340b03a369f2b7c6f0364477ccedc4e2481e84d5f3a8d3c67f", - "sha256:dbbe51d28bb28e7ee2782e51310477f7a2c5a111a301f6dd8e264e11ab820427" + "sha256:03a555b610bb307689b821b64e2416593ec21a85925c8c436c2cd08ebc6bb85e", + "sha256:0ef59c8da8da2e18e808e3880158049e9d72f3031c84cc804b6c533a0eb668a9" ], "index": "pypi", - "version": "==0.23.0" + "version": "==0.23.1" }, "numpy": { "hashes": [ @@ -778,7 +778,7 @@ "spiffworkflow": { "editable": true, "git": "https://github.com/sartography/SpiffWorkflow.git", - "ref": "c8d87826d496af825a184bdc3f0a751e603cfe44" + "ref": "b8a064a0bb76c705a1be04ee9bb8ac7beee56eb0" }, "sqlalchemy": { "hashes": [ @@ -876,11 +876,11 @@ }, "xlsxwriter": { "hashes": [ - "sha256:488e1988ab16ff3a9cd58c7656d0a58f8abe46ee58b98eecea78c022db28656b", - "sha256:97ab487b81534415c5313154203f3e8a637d792b1e6a8201e8f7f71da0203c2a" + "sha256:828b3285fc95105f5b1946a6a015b31cf388bd5378fdc6604e4d1b7839df2e77", + "sha256:82a3b0e73e3913483da23791d1a25e4d2dbb3837d1be4129473526b9a270a5cc" ], "index": "pypi", - "version": "==1.2.8" + "version": "==1.2.9" }, "zipp": { "hashes": [ diff --git a/crc/api/common.py b/crc/api/common.py index b89dd8d5..f8673a5b 100644 --- a/crc/api/common.py +++ b/crc/api/common.py @@ -1,3 +1,6 @@ +from SpiffWorkflow import WorkflowException +from SpiffWorkflow.exceptions import WorkflowTaskExecException + from crc import ma, app @@ -34,6 +37,16 @@ class ApiError(Exception): instance.file_name = task_spec._wf_spec.file return instance + @classmethod + def from_workflow_exception(cls, code, message, exp: WorkflowException): + """We catch a lot of workflow exception errors, + so consolidating the code, and doing the best things + we can with the data we have.""" + if isinstance(exp, WorkflowTaskExecException): + return ApiError.from_task(code, message, exp.task) + else: + return ApiError.from_task_spec(code, message, exp.sender) + class ApiErrorSchema(ma.Schema): class Meta: diff --git a/crc/api/workflow.py b/crc/api/workflow.py index efcccc26..9d6e4680 100644 --- a/crc/api/workflow.py +++ b/crc/api/workflow.py @@ -42,7 +42,9 @@ def validate_workflow_specification(spec_id): errors = [] try: + # Run the validation twice, the second time, just populate the required fields. WorkflowService.test_spec(spec_id) + WorkflowService.test_spec(spec_id, required_only=True) except ApiError as ae: errors.append(ae) return ApiErrorSchema(many=True).dump(errors) diff --git a/crc/models/api_models.py b/crc/models/api_models.py index d53e43bc..eee6d5f5 100644 --- a/crc/models/api_models.py +++ b/crc/models/api_models.py @@ -36,6 +36,7 @@ class Task(object): PROP_OPTIONS_VALUE_COLUMN = "spreadsheet.value.column" PROP_OPTIONS_LABEL_COL = "spreadsheet.label.column" PROP_LDAP_LOOKUP = "ldap.lookup" + VALIDATION_REQUIRED = "required" FIELD_TYPE_AUTO_COMPLETE = "autocomplete" diff --git a/crc/services/workflow_service.py b/crc/services/workflow_service.py index cf40b84d..dc900400 100644 --- a/crc/services/workflow_service.py +++ b/crc/services/workflow_service.py @@ -7,9 +7,7 @@ from SpiffWorkflow import Task as SpiffTask, WorkflowException from SpiffWorkflow.bpmn.specs.ManualTask import ManualTask from SpiffWorkflow.bpmn.specs.ScriptTask import ScriptTask from SpiffWorkflow.bpmn.specs.UserTask import UserTask -from SpiffWorkflow.camunda.specs.UserTask import EnumFormField from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask -from SpiffWorkflow.exceptions import WorkflowTaskExecException from SpiffWorkflow.specs import CancelTask, StartTask from flask import g from jinja2 import Template @@ -69,23 +67,22 @@ class WorkflowService(object): db.session.delete(user) @staticmethod - def test_spec(spec_id): + def test_spec(spec_id, required_only=False): """Runs a spec through it's paces to see if it results in any errors. Not fool-proof, but a good sanity check. Returns the final data - output form the last task if successful. """ + output form the last task if successful. + + required_only can be set to true, in which case this will run the + spec, only completing the required fields, rather than everything. + """ workflow_model = WorkflowService.make_test_workflow(spec_id) try: processor = WorkflowProcessor(workflow_model, validate_only=True) - except WorkflowTaskExecException as wtee: - WorkflowService.delete_test_data() - raise ApiError.from_task("workflow_execution_exception", str(wtee), - wtee.task) except WorkflowException as we: WorkflowService.delete_test_data() - raise ApiError.from_task_spec("workflow_execution_exception", str(we), - we.sender) + raise ApiError.from_workflow_exception("workflow_execution_exception", str(we), we) while not processor.bpmn_workflow.is_completed(): try: @@ -95,28 +92,26 @@ class WorkflowService(object): task_api = WorkflowService.spiff_task_to_api_task( task, add_docs_and_forms=True) # Assure we try to process the documenation, and raise those errors. - WorkflowService.populate_form_with_random_data(task, task_api) + WorkflowService.populate_form_with_random_data(task, task_api, required_only) task.complete() - except WorkflowTaskExecException as wtee: - WorkflowService.delete_test_data() - raise ApiError.from_task("workflow_execution_exception", str(wtee), - wtee.task) except WorkflowException as we: WorkflowService.delete_test_data() - raise ApiError.from_task_spec("workflow_execution_exception", str(we), - we.sender) + raise ApiError.from_workflow_exception("workflow_execution_exception", str(we), we) WorkflowService.delete_test_data() return processor.bpmn_workflow.last_task.data @staticmethod - def populate_form_with_random_data(task, task_api): + def populate_form_with_random_data(task, task_api, required_only): """populates a task with random data - useful for testing a spec.""" if not hasattr(task.task_spec, 'form'): return form_data = {} for field in task_api.form.fields: + if required_only and (not field.has_validation(Task.VALIDATION_REQUIRED) or + field.get_validation(Task.VALIDATION_REQUIRED).lower().strip() != "true"): + continue # Don't include any fields that aren't specifically marked as required. if field.has_property(Task.PROP_OPTIONS_REPEAT): group = field.get_property(Task.PROP_OPTIONS_REPEAT) if group not in form_data: diff --git a/tests/data/decision_table/decision_table.bpmn b/tests/data/decision_table/decision_table.bpmn index 796233e5..82bcb385 100644 --- a/tests/data/decision_table/decision_table.bpmn +++ b/tests/data/decision_table/decision_table.bpmn @@ -1,5 +1,5 @@ - + SequenceFlow_1ma1wxb @@ -8,7 +8,11 @@ - + + + + + SequenceFlow_1ma1wxb @@ -26,38 +30,37 @@ Based on the information you provided (Ginger left {{num_presents}}, we recommen ## {{message}} -We hope you both have an excellent day! - +We hope you both have an excellent day! SequenceFlow_0grui6f - - - - - - + + + - - - + + + + + + + + + + - - - - diff --git a/tests/data/exclusive_gateway/exclusive_gateway.bpmn b/tests/data/exclusive_gateway/exclusive_gateway.bpmn index 1c7e55fe..8467c954 100644 --- a/tests/data/exclusive_gateway/exclusive_gateway.bpmn +++ b/tests/data/exclusive_gateway/exclusive_gateway.bpmn @@ -8,7 +8,11 @@ - + + + + + SequenceFlow_1pnq3kg diff --git a/tests/data/random_fact/random_fact.bpmn b/tests/data/random_fact/random_fact.bpmn index 81f355c3..628f1bd4 100644 --- a/tests/data/random_fact/random_fact.bpmn +++ b/tests/data/random_fact/random_fact.bpmn @@ -1,5 +1,5 @@ - + SequenceFlow_0c7wlth @@ -108,6 +108,9 @@ Autoconverted link https://github.com/nodeca/pica (enable linkify to see) + + + @@ -121,8 +124,7 @@ Autoconverted link https://github.com/nodeca/pica (enable linkify to see) SequenceFlow_0641sh6 - - + @@ -155,6 +157,18 @@ Your random fact is: + + + + + + + + + + + + @@ -164,35 +178,23 @@ Your random fact is: + + + + + + - - - - - - - - - - - - - - - - - - diff --git a/tests/data/required_fields/required_fields.bpmn b/tests/data/required_fields/required_fields.bpmn new file mode 100644 index 00000000..7612f69b --- /dev/null +++ b/tests/data/required_fields/required_fields.bpmn @@ -0,0 +1,48 @@ + + + + + SequenceFlow_0lvudp8 + + + + SequenceFlow_02vev7n + + + + + + + + + + + + + + SequenceFlow_0lvudp8 + SequenceFlow_02vev7n + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test_workflow_processor.py b/tests/test_workflow_processor.py index 36d23755..b3f6c374 100644 --- a/tests/test_workflow_processor.py +++ b/tests/test_workflow_processor.py @@ -25,7 +25,7 @@ class TestWorkflowProcessor(BaseTest): def _populate_form_with_random_data(self, task): api_task = WorkflowService.spiff_task_to_api_task(task, add_docs_and_forms=True) - WorkflowService.populate_form_with_random_data(task, api_task) + WorkflowService.populate_form_with_random_data(task, api_task, required_only=False) def get_processor(self, study_model, spec_model): workflow_model = StudyService._create_workflow_model(study_model, spec_model) diff --git a/tests/test_workflow_service.py b/tests/test_workflow_service.py index 281d1756..f509f642 100644 --- a/tests/test_workflow_service.py +++ b/tests/test_workflow_service.py @@ -77,5 +77,5 @@ class TestWorkflowService(BaseTest): processor.do_engine_steps() task = processor.next_task() task_api = WorkflowService.spiff_task_to_api_task(task, add_docs_and_forms=True) - WorkflowService.populate_form_with_random_data(task, task_api) + WorkflowService.populate_form_with_random_data(task, task_api, required_only=False) self.assertTrue(isinstance(task.data["sponsor"], dict)) \ No newline at end of file diff --git a/tests/test_workflow_spec_validation_api.py b/tests/test_workflow_spec_validation_api.py index d46746dc..1594d681 100644 --- a/tests/test_workflow_spec_validation_api.py +++ b/tests/test_workflow_spec_validation_api.py @@ -95,4 +95,17 @@ class TestWorkflowSpecValidation(BaseTest): spec_model = self.load_test_spec('repeat_form') final_data = WorkflowService.test_spec(spec_model.id) self.assertIsNotNone(final_data) - self.assertIn('cats', final_data) \ No newline at end of file + self.assertIn('cats', final_data) + + def test_required_fields(self): + self.load_example_data() + spec_model = self.load_test_spec('required_fields') + final_data = WorkflowService.test_spec(spec_model.id) + self.assertIsNotNone(final_data) + self.assertIn('string_required', final_data) + self.assertIn('string_not_required', final_data) + + final_data = WorkflowService.test_spec(spec_model.id, required_only=True) + self.assertIsNotNone(final_data) + self.assertIn('string_required', final_data) + self.assertNotIn('string_not_required', final_data)