2020-02-03 20:15:36 +00:00
|
|
|
import string
|
|
|
|
import random
|
2020-03-03 18:50:22 +00:00
|
|
|
from unittest.mock import patch
|
2020-02-03 20:15:36 +00:00
|
|
|
|
2020-02-25 17:01:25 +00:00
|
|
|
from SpiffWorkflow.bpmn.specs.EndEvent import EndEvent
|
|
|
|
|
2020-01-14 16:45:12 +00:00
|
|
|
from crc import session
|
2020-02-18 21:38:56 +00:00
|
|
|
from crc.api.common import ApiError
|
2020-02-10 21:19:23 +00:00
|
|
|
from crc.models.file import FileModel, FileDataModel
|
|
|
|
from crc.models.study import StudyModel
|
2020-01-24 16:52:52 +00:00
|
|
|
from crc.models.workflow import WorkflowSpecModel, WorkflowStatus
|
2019-12-18 19:02:17 +00:00
|
|
|
from tests.base_test import BaseTest
|
2020-02-10 21:19:23 +00:00
|
|
|
from crc.services.workflow_processor import WorkflowProcessor
|
2019-12-18 19:02:17 +00:00
|
|
|
|
|
|
|
|
2020-01-24 14:35:14 +00:00
|
|
|
class TestWorkflowProcessor(BaseTest):
|
2019-12-18 19:02:17 +00:00
|
|
|
|
2020-02-04 21:49:28 +00:00
|
|
|
def _randomString(self, stringLength=10):
|
|
|
|
"""Generate a random string of fixed length """
|
|
|
|
letters = string.ascii_lowercase
|
|
|
|
return ''.join(random.choice(letters) for i in range(stringLength))
|
|
|
|
|
2020-02-07 16:34:44 +00:00
|
|
|
def _populate_form_with_random_data(self, task):
|
2020-02-04 21:49:28 +00:00
|
|
|
form_data = {}
|
|
|
|
for field in task.task_spec.form.fields:
|
|
|
|
form_data[field.id] = self._randomString()
|
|
|
|
if task.data is None:
|
|
|
|
task.data = {}
|
|
|
|
task.data.update(form_data)
|
|
|
|
|
2019-12-18 19:02:17 +00:00
|
|
|
def test_create_and_complete_workflow(self):
|
|
|
|
self.load_example_data()
|
2020-03-03 18:50:22 +00:00
|
|
|
workflow_spec_model = self.load_test_spec("random_fact")
|
2020-02-10 21:19:23 +00:00
|
|
|
study = session.query(StudyModel).first()
|
|
|
|
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
|
|
|
self.assertEqual(study.id, processor.bpmn_workflow.data[WorkflowProcessor.STUDY_ID_KEY])
|
2019-12-18 19:02:17 +00:00
|
|
|
self.assertIsNotNone(processor)
|
|
|
|
self.assertEqual(WorkflowStatus.user_input_required, processor.get_status())
|
|
|
|
next_user_tasks = processor.next_user_tasks()
|
|
|
|
self.assertEqual(1, len(next_user_tasks))
|
|
|
|
task = next_user_tasks[0]
|
|
|
|
self.assertEqual("Task_User_Select_Type", task.get_name())
|
2020-02-12 03:13:46 +00:00
|
|
|
model = {"type": "buzzword"}
|
2019-12-18 19:02:17 +00:00
|
|
|
if task.data is None:
|
|
|
|
task.data = {}
|
|
|
|
task.data.update(model)
|
|
|
|
processor.complete_task(task)
|
|
|
|
self.assertEqual(WorkflowStatus.waiting, processor.get_status())
|
|
|
|
processor.do_engine_steps()
|
|
|
|
self.assertEqual(WorkflowStatus.complete, processor.get_status())
|
|
|
|
data = processor.get_data()
|
|
|
|
self.assertIsNotNone(data)
|
|
|
|
self.assertIn("details", data)
|
2019-12-31 21:32:47 +00:00
|
|
|
|
2020-01-23 20:32:53 +00:00
|
|
|
def test_workflow_with_dmn(self):
|
2019-12-31 21:32:47 +00:00
|
|
|
self.load_example_data()
|
2020-02-10 21:19:23 +00:00
|
|
|
study = session.query(StudyModel).first()
|
2020-03-03 18:50:22 +00:00
|
|
|
workflow_spec_model = self.load_test_spec("decision_table")
|
2020-01-23 20:32:53 +00:00
|
|
|
files = session.query(FileModel).filter_by(workflow_spec_id='decision_table').all()
|
2020-02-04 02:56:18 +00:00
|
|
|
self.assertEqual(2, len(files))
|
2020-02-10 21:19:23 +00:00
|
|
|
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
2019-12-31 21:32:47 +00:00
|
|
|
self.assertEqual(WorkflowStatus.user_input_required, processor.get_status())
|
|
|
|
next_user_tasks = processor.next_user_tasks()
|
2020-01-23 20:32:53 +00:00
|
|
|
self.assertEqual(1, len(next_user_tasks))
|
2019-12-31 21:32:47 +00:00
|
|
|
task = next_user_tasks[0]
|
2020-01-23 20:32:53 +00:00
|
|
|
self.assertEqual("get_num_presents", task.get_name())
|
|
|
|
model = {"num_presents": 1}
|
|
|
|
if task.data is None:
|
|
|
|
task.data = {}
|
|
|
|
task.data.update(model)
|
|
|
|
processor.complete_task(task)
|
|
|
|
processor.do_engine_steps()
|
2019-12-31 21:32:47 +00:00
|
|
|
data = processor.get_data()
|
2020-01-23 20:32:53 +00:00
|
|
|
self.assertIsNotNone(data)
|
|
|
|
self.assertIn("message", data)
|
2020-01-24 14:29:50 +00:00
|
|
|
self.assertEqual("Oh, Ginger.", data.get('message'))
|
2020-02-25 17:01:25 +00:00
|
|
|
self.assertEqual("End", processor.bpmn_workflow.last_task.task_spec.name)
|
|
|
|
self.assertEqual("Oh, Ginger.", processor.bpmn_workflow.last_task.data.get('message'))
|
|
|
|
|
2020-02-03 20:15:36 +00:00
|
|
|
|
|
|
|
def test_workflow_with_parallel_forms(self):
|
|
|
|
self.load_example_data()
|
2020-03-03 18:50:22 +00:00
|
|
|
workflow_spec_model = self.load_test_spec("parallel_tasks")
|
2020-02-10 21:19:23 +00:00
|
|
|
study = session.query(StudyModel).first()
|
|
|
|
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
2020-02-03 20:15:36 +00:00
|
|
|
self.assertEqual(WorkflowStatus.user_input_required, processor.get_status())
|
2020-02-11 18:40:14 +00:00
|
|
|
|
|
|
|
# Complete the first steps of the 4 parallel tasks
|
2020-02-03 20:15:36 +00:00
|
|
|
next_user_tasks = processor.next_user_tasks()
|
|
|
|
self.assertEqual(4, len(next_user_tasks))
|
2020-02-07 16:34:44 +00:00
|
|
|
self._populate_form_with_random_data(next_user_tasks[0])
|
|
|
|
self._populate_form_with_random_data(next_user_tasks[1])
|
|
|
|
self._populate_form_with_random_data(next_user_tasks[2])
|
|
|
|
self._populate_form_with_random_data(next_user_tasks[3])
|
2020-02-03 20:15:36 +00:00
|
|
|
processor.complete_task(next_user_tasks[0])
|
|
|
|
processor.complete_task(next_user_tasks[1])
|
|
|
|
processor.complete_task(next_user_tasks[2])
|
|
|
|
processor.complete_task(next_user_tasks[3])
|
2020-02-11 18:40:14 +00:00
|
|
|
|
|
|
|
# There are another 4 tasks to complete (each parallel task has a follow-up task)
|
2020-02-03 20:15:36 +00:00
|
|
|
next_user_tasks = processor.next_user_tasks()
|
|
|
|
self.assertEqual(4, len(next_user_tasks))
|
2020-02-07 16:34:44 +00:00
|
|
|
self._populate_form_with_random_data(next_user_tasks[0])
|
|
|
|
self._populate_form_with_random_data(next_user_tasks[1])
|
|
|
|
self._populate_form_with_random_data(next_user_tasks[2])
|
|
|
|
self._populate_form_with_random_data(next_user_tasks[3])
|
2020-02-03 20:15:36 +00:00
|
|
|
processor.complete_task(next_user_tasks[0])
|
|
|
|
processor.complete_task(next_user_tasks[1])
|
|
|
|
processor.complete_task(next_user_tasks[2])
|
|
|
|
processor.complete_task(next_user_tasks[3])
|
2020-02-11 18:40:14 +00:00
|
|
|
processor.do_engine_steps()
|
|
|
|
|
|
|
|
# Should be one last step after the above are complete
|
|
|
|
final_user_tasks = processor.next_user_tasks()
|
|
|
|
self.assertEqual(1, len(final_user_tasks))
|
|
|
|
self._populate_form_with_random_data(final_user_tasks[0])
|
|
|
|
processor.complete_task(final_user_tasks[0])
|
|
|
|
|
2020-02-03 20:15:36 +00:00
|
|
|
processor.do_engine_steps()
|
|
|
|
self.assertTrue(processor.bpmn_workflow.is_completed())
|
|
|
|
|
2020-02-07 16:34:44 +00:00
|
|
|
def test_workflow_processor_knows_the_text_task_even_when_parallel(self):
|
|
|
|
self.load_example_data()
|
2020-02-10 21:19:23 +00:00
|
|
|
study = session.query(StudyModel).first()
|
2020-03-03 18:50:22 +00:00
|
|
|
workflow_spec_model = self.load_test_spec("parallel_tasks")
|
2020-02-10 21:19:23 +00:00
|
|
|
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
2020-02-07 16:34:44 +00:00
|
|
|
self.assertEqual(WorkflowStatus.user_input_required, processor.get_status())
|
|
|
|
next_user_tasks = processor.next_user_tasks()
|
|
|
|
self.assertEqual(4, len(next_user_tasks))
|
|
|
|
self.assertEqual(next_user_tasks[0], processor.next_task(), "First task in list of 4")
|
|
|
|
|
|
|
|
# Complete the third open task, so do things out of order
|
|
|
|
# this should cause the system to recommend the first ready task that is a
|
|
|
|
# child of the last completed task.
|
|
|
|
task = next_user_tasks[2]
|
|
|
|
self._populate_form_with_random_data(task)
|
|
|
|
processor.complete_task(task)
|
|
|
|
next_user_tasks = processor.next_user_tasks()
|
|
|
|
self.assertEqual(processor.bpmn_workflow.last_task, task)
|
|
|
|
self.assertEqual(4, len(next_user_tasks))
|
|
|
|
self.assertEqual(task.children[0], processor.next_task())
|
|
|
|
|
2020-02-25 17:01:25 +00:00
|
|
|
def test_workflow_processor_returns_next_task_as_end_task_if_complete(self):
|
|
|
|
self.load_example_data()
|
2020-03-03 18:50:22 +00:00
|
|
|
workflow_spec_model = self.load_test_spec("random_fact")
|
2020-02-25 17:01:25 +00:00
|
|
|
study = session.query(StudyModel).first()
|
|
|
|
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
|
|
|
processor.do_engine_steps()
|
|
|
|
task = processor.next_task()
|
|
|
|
task.data = {"type": "buzzword"}
|
|
|
|
processor.complete_task(task)
|
|
|
|
self.assertEqual(WorkflowStatus.waiting, processor.get_status())
|
|
|
|
processor.do_engine_steps()
|
|
|
|
self.assertEqual(WorkflowStatus.complete, processor.get_status())
|
|
|
|
task = processor.next_task()
|
|
|
|
self.assertIsNotNone(task)
|
|
|
|
self.assertIn("details", task.data)
|
|
|
|
self.assertIsInstance(task.task_spec, EndEvent)
|
|
|
|
|
2020-02-04 21:49:28 +00:00
|
|
|
def test_workflow_with_bad_expression_raises_sensible_error(self):
|
2020-02-10 21:19:23 +00:00
|
|
|
self.load_example_data()
|
|
|
|
|
2020-02-04 21:49:28 +00:00
|
|
|
workflow_spec_model = self.load_test_spec("invalid_expression")
|
2020-02-10 21:19:23 +00:00
|
|
|
study = session.query(StudyModel).first()
|
|
|
|
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
2020-02-04 21:49:28 +00:00
|
|
|
processor.do_engine_steps()
|
|
|
|
next_user_tasks = processor.next_user_tasks()
|
|
|
|
self.assertEqual(1, len(next_user_tasks))
|
2020-02-07 16:34:44 +00:00
|
|
|
self._populate_form_with_random_data(next_user_tasks[0])
|
2020-02-04 21:49:28 +00:00
|
|
|
processor.complete_task(next_user_tasks[0])
|
2020-02-18 21:38:56 +00:00
|
|
|
with self.assertRaises(ApiError) as context:
|
2020-02-04 21:49:28 +00:00
|
|
|
processor.do_engine_steps()
|
2020-02-18 21:38:56 +00:00
|
|
|
self.assertEqual("invalid_expression", context.exception.code)
|
2020-02-04 21:49:28 +00:00
|
|
|
|
2020-02-10 21:19:23 +00:00
|
|
|
def test_workflow_with_docx_template(self):
|
|
|
|
self.load_example_data()
|
|
|
|
study = session.query(StudyModel).first()
|
|
|
|
workflow_spec_model = self.load_test_spec("docx")
|
|
|
|
files = session.query(FileModel).filter_by(workflow_spec_id='docx').all()
|
|
|
|
self.assertEqual(2, len(files))
|
|
|
|
workflow_spec_model = session.query(WorkflowSpecModel).filter_by(id="docx").first()
|
|
|
|
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
|
|
|
self.assertEqual(WorkflowStatus.user_input_required, processor.get_status())
|
|
|
|
next_user_tasks = processor.next_user_tasks()
|
|
|
|
self.assertEqual(1, len(next_user_tasks))
|
|
|
|
task = next_user_tasks[0]
|
|
|
|
self.assertEqual("task_gather_information", task.get_name())
|
|
|
|
self._populate_form_with_random_data(task)
|
|
|
|
processor.complete_task(task)
|
2020-02-03 20:15:36 +00:00
|
|
|
|
2020-02-10 21:19:23 +00:00
|
|
|
files = session.query(FileModel).filter_by(study_id=study.id, workflow_id=processor.get_workflow_id()).all()
|
|
|
|
self.assertEqual(0, len(files))
|
|
|
|
processor.do_engine_steps()
|
|
|
|
files = session.query(FileModel).filter_by(study_id=study.id, workflow_id=processor.get_workflow_id()).all()
|
|
|
|
self.assertEqual(1, len(files), "The task should create a new file.")
|
|
|
|
file_data = session.query(FileDataModel).filter(FileDataModel.file_model_id == files[0].id).first()
|
|
|
|
self.assertIsNotNone(file_data.data)
|
|
|
|
self.assertTrue(len(file_data.data) > 0)
|
2020-02-11 18:40:14 +00:00
|
|
|
# Not going any farther here, assuming this is tested in libraries correctly.
|
2020-03-03 18:50:22 +00:00
|
|
|
|
|
|
|
def test_load_study_information(self):
|
|
|
|
""" Test a workflow that includes requests to pull in Study Details."""
|
|
|
|
|
|
|
|
self.load_example_data()
|
|
|
|
study = session.query(StudyModel).first()
|
|
|
|
workflow_spec_model = self.load_test_spec("study_details")
|
|
|
|
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
|
|
|
processor.do_engine_steps()
|
|
|
|
task = processor.bpmn_workflow.last_task
|
|
|
|
self.assertIsNotNone(task.data)
|
|
|
|
self.assertIn("study", task.data)
|
|
|
|
self.assertIn("info", task.data["study"])
|
|
|
|
self.assertIn("title", task.data["study"]["info"])
|
|
|
|
self.assertIn("last_updated", task.data["study"]["info"])
|
|
|
|
self.assertIn("sponsor", task.data["study"]["info"])
|
|
|
|
|