mirror of
https://github.com/sartography/cr-connect-workflow.git
synced 2025-02-23 21:28:32 +00:00
Merge pull request #88 from sartography/rrt/dev
Fill out repeating sections during validation process.
This commit is contained in:
commit
7b9eda321e
@ -3,7 +3,7 @@ from crc import ma, app
|
|||||||
|
|
||||||
class ApiError(Exception):
|
class ApiError(Exception):
|
||||||
def __init__(self, code, message, status_code=400,
|
def __init__(self, code, message, status_code=400,
|
||||||
file_name="", task_id="", task_name="", tag=""):
|
file_name="", task_id="", task_name="", tag="", task_data = {}):
|
||||||
self.status_code = status_code
|
self.status_code = status_code
|
||||||
self.code = code # a short consistent string describing the error.
|
self.code = code # a short consistent string describing the error.
|
||||||
self.message = message # A detailed message that provides more information.
|
self.message = message # A detailed message that provides more information.
|
||||||
@ -11,6 +11,7 @@ class ApiError(Exception):
|
|||||||
self.task_name = task_name or "" # OPTIONAL: The name of the task in the BPMN Diagram.
|
self.task_name = task_name or "" # OPTIONAL: The name of the task in the BPMN Diagram.
|
||||||
self.file_name = file_name or "" # OPTIONAL: The file that caused the error.
|
self.file_name = file_name or "" # OPTIONAL: The file that caused the error.
|
||||||
self.tag = tag or "" # OPTIONAL: The XML Tag that caused the issue.
|
self.tag = tag or "" # OPTIONAL: The XML Tag that caused the issue.
|
||||||
|
self.task_data = task_data or "" # OPTIONAL: A snapshot of data connected to the task when error ocurred.
|
||||||
Exception.__init__(self, self.message)
|
Exception.__init__(self, self.message)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -20,6 +21,7 @@ class ApiError(Exception):
|
|||||||
instance.task_id = task.task_spec.name or ""
|
instance.task_id = task.task_spec.name or ""
|
||||||
instance.task_name = task.task_spec.description or ""
|
instance.task_name = task.task_spec.description or ""
|
||||||
instance.file_name = task.workflow.spec.file or ""
|
instance.file_name = task.workflow.spec.file or ""
|
||||||
|
instance.task_data = task.data
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -35,7 +37,8 @@ class ApiError(Exception):
|
|||||||
|
|
||||||
class ApiErrorSchema(ma.Schema):
|
class ApiErrorSchema(ma.Schema):
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ("code", "message", "workflow_name", "file_name", "task_name", "task_id")
|
fields = ("code", "message", "workflow_name", "file_name", "task_name", "task_id",
|
||||||
|
"task_data")
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(ApiError)
|
@app.errorhandler(ApiError)
|
||||||
|
@ -31,6 +31,7 @@ class NavigationItem(object):
|
|||||||
|
|
||||||
class Task(object):
|
class Task(object):
|
||||||
|
|
||||||
|
PROP_OPTIONS_REPEAT = "repeat"
|
||||||
PROP_OPTIONS_FILE = "spreadsheet.name"
|
PROP_OPTIONS_FILE = "spreadsheet.name"
|
||||||
PROP_OPTIONS_VALUE_COLUMN = "spreadsheet.value.column"
|
PROP_OPTIONS_VALUE_COLUMN = "spreadsheet.value.column"
|
||||||
PROP_OPTIONS_LABEL_COL = "spreadsheet.label.column"
|
PROP_OPTIONS_LABEL_COL = "spreadsheet.label.column"
|
||||||
|
@ -5,11 +5,13 @@ from datetime import datetime
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
|
import flask
|
||||||
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException
|
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException
|
||||||
from pandas import ExcelFile
|
from pandas import ExcelFile
|
||||||
from sqlalchemy import desc
|
from sqlalchemy import desc
|
||||||
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
from crc import session
|
from crc import session, app
|
||||||
from crc.api.common import ApiError
|
from crc.api.common import ApiError
|
||||||
from crc.models.file import FileType, FileDataModel, FileModel, LookupFileModel, LookupDataModel
|
from crc.models.file import FileType, FileDataModel, FileModel, LookupFileModel, LookupDataModel
|
||||||
from crc.models.workflow import WorkflowSpecModel, WorkflowModel, WorkflowSpecDependencyFile
|
from crc.models.workflow import WorkflowSpecModel, WorkflowModel, WorkflowSpecDependencyFile
|
||||||
@ -295,12 +297,17 @@ class FileService(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_file(file_id):
|
def delete_file(file_id):
|
||||||
data_models = session.query(FileDataModel).filter_by(file_model_id=file_id).all()
|
try:
|
||||||
for dm in data_models:
|
data_models = session.query(FileDataModel).filter_by(file_model_id=file_id).all()
|
||||||
lookup_files = session.query(LookupFileModel).filter_by(file_data_model_id=dm.id).all()
|
for dm in data_models:
|
||||||
for lf in lookup_files:
|
lookup_files = session.query(LookupFileModel).filter_by(file_data_model_id=dm.id).all()
|
||||||
session.query(LookupDataModel).filter_by(lookup_file_model_id=lf.id).delete()
|
for lf in lookup_files:
|
||||||
session.query(LookupFileModel).filter_by(id=lf.id).delete()
|
session.query(LookupDataModel).filter_by(lookup_file_model_id=lf.id).delete()
|
||||||
session.query(FileDataModel).filter_by(file_model_id=file_id).delete()
|
session.query(LookupFileModel).filter_by(id=lf.id).delete()
|
||||||
session.query(FileModel).filter_by(id=file_id).delete()
|
session.query(FileDataModel).filter_by(file_model_id=file_id).delete()
|
||||||
session.commit()
|
session.query(FileModel).filter_by(id=file_id).delete()
|
||||||
|
session.commit()
|
||||||
|
except IntegrityError as ie:
|
||||||
|
app.logger.error("Failed to delete file: %i, due to %s" % (file_id, str(ie)))
|
||||||
|
raise ApiError('file_integrity_error', "You are attempting to delete a file that is "
|
||||||
|
"required by other records in the system.")
|
@ -4,6 +4,7 @@ from typing import List
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
from SpiffWorkflow import WorkflowException
|
from SpiffWorkflow import WorkflowException
|
||||||
|
from SpiffWorkflow.exceptions import WorkflowTaskExecException
|
||||||
from ldap3.core.exceptions import LDAPSocketOpenError
|
from ldap3.core.exceptions import LDAPSocketOpenError
|
||||||
|
|
||||||
from crc import db, session, app
|
from crc import db, session, app
|
||||||
@ -309,6 +310,8 @@ class StudyService(object):
|
|||||||
for workflow_spec in new_specs:
|
for workflow_spec in new_specs:
|
||||||
try:
|
try:
|
||||||
StudyService._create_workflow_model(study_model, workflow_spec)
|
StudyService._create_workflow_model(study_model, workflow_spec)
|
||||||
|
except WorkflowTaskExecException as wtee:
|
||||||
|
errors.append(ApiError.from_task("workflow_execution_exception", str(wtee), wtee.task))
|
||||||
except WorkflowException as we:
|
except WorkflowException as we:
|
||||||
errors.append(ApiError.from_task_spec("workflow_execution_exception", str(we), we.sender))
|
errors.append(ApiError.from_task_spec("workflow_execution_exception", str(we), we.sender))
|
||||||
return errors
|
return errors
|
||||||
|
@ -7,8 +7,9 @@ from SpiffWorkflow import Task as SpiffTask, WorkflowException
|
|||||||
from SpiffWorkflow.bpmn.specs.ManualTask import ManualTask
|
from SpiffWorkflow.bpmn.specs.ManualTask import ManualTask
|
||||||
from SpiffWorkflow.bpmn.specs.ScriptTask import ScriptTask
|
from SpiffWorkflow.bpmn.specs.ScriptTask import ScriptTask
|
||||||
from SpiffWorkflow.bpmn.specs.UserTask import UserTask
|
from SpiffWorkflow.bpmn.specs.UserTask import UserTask
|
||||||
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
|
from SpiffWorkflow.camunda.specs.UserTask import EnumFormField
|
||||||
from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask
|
from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask
|
||||||
|
from SpiffWorkflow.exceptions import WorkflowTaskExecException
|
||||||
from SpiffWorkflow.specs import CancelTask, StartTask
|
from SpiffWorkflow.specs import CancelTask, StartTask
|
||||||
from flask import g
|
from flask import g
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
@ -17,7 +18,6 @@ from crc import db, app
|
|||||||
from crc.api.common import ApiError
|
from crc.api.common import ApiError
|
||||||
from crc.models.api_models import Task, MultiInstanceType
|
from crc.models.api_models import Task, MultiInstanceType
|
||||||
from crc.models.file import LookupDataModel
|
from crc.models.file import LookupDataModel
|
||||||
from crc.models.protocol_builder import ProtocolBuilderStatus
|
|
||||||
from crc.models.stats import TaskEventModel
|
from crc.models.stats import TaskEventModel
|
||||||
from crc.models.study import StudyModel
|
from crc.models.study import StudyModel
|
||||||
from crc.models.user import UserModel
|
from crc.models.user import UserModel
|
||||||
@ -39,7 +39,9 @@ class WorkflowService(object):
|
|||||||
the workflow Processor should be hidden behind this service.
|
the workflow Processor should be hidden behind this service.
|
||||||
This will help maintain a structure that avoids circular dependencies.
|
This will help maintain a structure that avoids circular dependencies.
|
||||||
But for now, this contains tools for converting spiff-workflow models into our
|
But for now, this contains tools for converting spiff-workflow models into our
|
||||||
own API models with additional information and capabilities."""
|
own API models with additional information and capabilities and
|
||||||
|
handles the testing of a workflow specification by completing it with
|
||||||
|
random selections, attempting to mimic a front end as much as possible. """
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def make_test_workflow(spec_id):
|
def make_test_workflow(spec_id):
|
||||||
@ -61,17 +63,25 @@ class WorkflowService(object):
|
|||||||
for study in db.session.query(StudyModel).filter(StudyModel.user_uid=="test"):
|
for study in db.session.query(StudyModel).filter(StudyModel.user_uid=="test"):
|
||||||
StudyService.delete_study(study.id)
|
StudyService.delete_study(study.id)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.query(UserModel).filter_by(uid="test").delete()
|
|
||||||
|
user = db.session.query(UserModel).filter_by(uid="test").first()
|
||||||
|
if user:
|
||||||
|
db.session.delete(user)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def test_spec(spec_id):
|
def test_spec(spec_id):
|
||||||
"""Runs a spec through it's paces to see if it results in any errors. Not fool-proof, but a good
|
"""Runs a spec through it's paces to see if it results in any errors.
|
||||||
sanity check."""
|
Not fool-proof, but a good sanity check. Returns the final data
|
||||||
|
output form the last task if successful. """
|
||||||
|
|
||||||
workflow_model = WorkflowService.make_test_workflow(spec_id)
|
workflow_model = WorkflowService.make_test_workflow(spec_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
processor = WorkflowProcessor(workflow_model, validate_only=True)
|
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:
|
except WorkflowException as we:
|
||||||
WorkflowService.delete_test_data()
|
WorkflowService.delete_test_data()
|
||||||
raise ApiError.from_task_spec("workflow_execution_exception", str(we),
|
raise ApiError.from_task_spec("workflow_execution_exception", str(we),
|
||||||
@ -87,11 +97,17 @@ class WorkflowService(object):
|
|||||||
add_docs_and_forms=True) # Assure we try to process the documenation, and raise those errors.
|
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)
|
||||||
task.complete()
|
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:
|
except WorkflowException as we:
|
||||||
WorkflowService.delete_test_data()
|
WorkflowService.delete_test_data()
|
||||||
raise ApiError.from_task_spec("workflow_execution_exception", str(we),
|
raise ApiError.from_task_spec("workflow_execution_exception", str(we),
|
||||||
we.sender)
|
we.sender)
|
||||||
|
|
||||||
WorkflowService.delete_test_data()
|
WorkflowService.delete_test_data()
|
||||||
|
return processor.bpmn_workflow.last_task.data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def populate_form_with_random_data(task, task_api):
|
def populate_form_with_random_data(task, task_api):
|
||||||
@ -101,22 +117,35 @@ class WorkflowService(object):
|
|||||||
|
|
||||||
form_data = {}
|
form_data = {}
|
||||||
for field in task_api.form.fields:
|
for field in task_api.form.fields:
|
||||||
if field.type == "enum":
|
if field.has_property(Task.PROP_OPTIONS_REPEAT):
|
||||||
if len(field.options) > 0:
|
group = field.get_property(Task.PROP_OPTIONS_REPEAT)
|
||||||
random_choice = random.choice(field.options)
|
if group not in form_data:
|
||||||
if isinstance(random_choice, dict):
|
form_data[group] = [{},{},{}]
|
||||||
form_data[field.id] = random.choice(field.options)['id']
|
for i in range(3):
|
||||||
else:
|
form_data[group][i][field.id] = WorkflowService.get_random_data_for_field(field, task)
|
||||||
# fixme: why it is sometimes an EnumFormFieldOption, and other times not?
|
else:
|
||||||
form_data[field.id] = random_choice.id ## Assume it is an EnumFormFieldOption
|
form_data[field.id] = WorkflowService.get_random_data_for_field(field, task)
|
||||||
|
if task.data is None:
|
||||||
|
task.data = {}
|
||||||
|
task.data.update(form_data)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_random_data_for_field(field, task):
|
||||||
|
if field.type == "enum":
|
||||||
|
if len(field.options) > 0:
|
||||||
|
random_choice = random.choice(field.options)
|
||||||
|
if isinstance(random_choice, dict):
|
||||||
|
return random.choice(field.options)['id']
|
||||||
else:
|
else:
|
||||||
raise ApiError.from_task("invalid_enum", "You specified an enumeration field (%s),"
|
# fixme: why it is sometimes an EnumFormFieldOption, and other times not?
|
||||||
" with no options" % field.id,
|
return random_choice.id ## Assume it is an EnumFormFieldOption
|
||||||
task)
|
else:
|
||||||
elif field.type == "autocomplete":
|
raise ApiError.from_task("invalid_enum", "You specified an enumeration field (%s),"
|
||||||
lookup_model = LookupService.get_lookup_model(task, field)
|
" with no options" % field.id, task)
|
||||||
if field.has_property(Task.PROP_LDAP_LOOKUP):
|
elif field.type == "autocomplete":
|
||||||
form_data[field.id] = {
|
lookup_model = LookupService.get_lookup_model(task, field)
|
||||||
|
if field.has_property(Task.PROP_LDAP_LOOKUP): # All ldap records get the same person.
|
||||||
|
return {
|
||||||
"label": "dhf8r",
|
"label": "dhf8r",
|
||||||
"value": "Dan Funk",
|
"value": "Dan Funk",
|
||||||
"data": {
|
"data": {
|
||||||
@ -126,32 +155,30 @@ class WorkflowService(object):
|
|||||||
"email_address": "dhf8r@virginia.edu",
|
"email_address": "dhf8r@virginia.edu",
|
||||||
"department": "Depertment of Psychocosmographictology",
|
"department": "Depertment of Psychocosmographictology",
|
||||||
"affiliation": "Rousabout",
|
"affiliation": "Rousabout",
|
||||||
"sponsor_type": "Staff"
|
"sponsor_type": "Staff"}
|
||||||
}
|
}
|
||||||
}
|
elif lookup_model:
|
||||||
elif lookup_model:
|
data = db.session.query(LookupDataModel).filter(
|
||||||
data = db.session.query(LookupDataModel).filter(
|
LookupDataModel.lookup_file_model == lookup_model).limit(10).all()
|
||||||
LookupDataModel.lookup_file_model == lookup_model).limit(10).all()
|
options = []
|
||||||
options = []
|
for d in data:
|
||||||
for d in data:
|
options.append({"id": d.value, "name": d.label})
|
||||||
options.append({"id": d.value, "name": d.label})
|
return random.choice(options)
|
||||||
form_data[field.id] = random.choice(options)
|
|
||||||
else:
|
|
||||||
raise ApiError.from_task("invalid_autocomplete", "The settings for this auto complete field "
|
|
||||||
"are incorrect: %s " % field.id, task)
|
|
||||||
elif field.type == "long":
|
|
||||||
form_data[field.id] = random.randint(1, 1000)
|
|
||||||
elif field.type == 'boolean':
|
|
||||||
form_data[field.id] = random.choice([True, False])
|
|
||||||
elif field.type == 'file':
|
|
||||||
form_data[field.id] = random.randint(1, 100)
|
|
||||||
elif field.type == 'files':
|
|
||||||
form_data[field.id] = random.randrange(1, 100)
|
|
||||||
else:
|
else:
|
||||||
form_data[field.id] = WorkflowService._random_string()
|
raise ApiError.from_task("invalid_autocomplete", "The settings for this auto complete field "
|
||||||
if task.data is None:
|
"are incorrect: %s " % field.id, task)
|
||||||
task.data = {}
|
elif field.type == "long":
|
||||||
task.data.update(form_data)
|
return random.randint(1, 1000)
|
||||||
|
elif field.type == 'boolean':
|
||||||
|
return random.choice([True, False])
|
||||||
|
elif field.type == 'file':
|
||||||
|
# fixme: produce some something sensible for files.
|
||||||
|
return random.randint(1, 100)
|
||||||
|
# fixme: produce some something sensible for files.
|
||||||
|
elif field.type == 'files':
|
||||||
|
return random.randrange(1, 100)
|
||||||
|
else:
|
||||||
|
return WorkflowService._random_string()
|
||||||
|
|
||||||
def __get_options(self):
|
def __get_options(self):
|
||||||
pass
|
pass
|
||||||
|
47
tests/data/repeat_form/repeat_form.bpmn
Normal file
47
tests/data/repeat_form/repeat_form.bpmn
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1v1rp1q" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
|
||||||
|
<bpmn:process id="Repeat" isExecutable="true">
|
||||||
|
<bpmn:startEvent id="StartEvent_1">
|
||||||
|
<bpmn:outgoing>SequenceFlow_0lvudp8</bpmn:outgoing>
|
||||||
|
</bpmn:startEvent>
|
||||||
|
<bpmn:sequenceFlow id="SequenceFlow_0lvudp8" sourceRef="StartEvent_1" targetRef="Task_14svgcu" />
|
||||||
|
<bpmn:endEvent id="EndEvent_0q4qzl9">
|
||||||
|
<bpmn:incoming>SequenceFlow_02vev7n</bpmn:incoming>
|
||||||
|
</bpmn:endEvent>
|
||||||
|
<bpmn:sequenceFlow id="SequenceFlow_02vev7n" sourceRef="Task_14svgcu" targetRef="EndEvent_0q4qzl9" />
|
||||||
|
<bpmn:userTask id="Task_14svgcu" name="Repeating Form" camunda:formKey="RepeatForm">
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<camunda:formData>
|
||||||
|
<camunda:formField id="name" label="Add a cat name" type="string" defaultValue="couger buttons">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="repeat" value="cats" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
</camunda:formData>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>SequenceFlow_0lvudp8</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>SequenceFlow_02vev7n</bpmn:outgoing>
|
||||||
|
</bpmn:userTask>
|
||||||
|
</bpmn:process>
|
||||||
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Repeat">
|
||||||
|
<bpmndi:BPMNEdge id="SequenceFlow_02vev7n_di" bpmnElement="SequenceFlow_02vev7n">
|
||||||
|
<di:waypoint x="370" y="117" />
|
||||||
|
<di:waypoint x="432" y="117" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="SequenceFlow_0lvudp8_di" bpmnElement="SequenceFlow_0lvudp8">
|
||||||
|
<di:waypoint x="215" y="117" />
|
||||||
|
<di:waypoint x="270" y="117" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||||
|
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="EndEvent_0q4qzl9_di" bpmnElement="EndEvent_0q4qzl9">
|
||||||
|
<dc:Bounds x="432" y="99" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="UserTask_18ly1yq_di" bpmnElement="Task_14svgcu">
|
||||||
|
<dc:Bounds x="270" y="77" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
</bpmndi:BPMNPlane>
|
||||||
|
</bpmndi:BPMNDiagram>
|
||||||
|
</bpmn:definitions>
|
@ -3,17 +3,16 @@ from unittest.mock import patch
|
|||||||
|
|
||||||
from tests.base_test import BaseTest
|
from tests.base_test import BaseTest
|
||||||
|
|
||||||
from crc.services.protocol_builder import ProtocolBuilderService
|
|
||||||
from crc import session, app
|
from crc import session, app
|
||||||
from crc.api.common import ApiErrorSchema
|
from crc.api.common import ApiErrorSchema
|
||||||
from crc.models.protocol_builder import ProtocolBuilderStudySchema
|
from crc.models.protocol_builder import ProtocolBuilderStudySchema
|
||||||
from crc.models.workflow import WorkflowSpecModel
|
from crc.models.workflow import WorkflowSpecModel
|
||||||
|
from crc.services.workflow_service import WorkflowService
|
||||||
|
|
||||||
|
|
||||||
class TestWorkflowSpecValidation(BaseTest):
|
class TestWorkflowSpecValidation(BaseTest):
|
||||||
|
|
||||||
def validate_workflow(self, workflow_name):
|
def validate_workflow(self, workflow_name):
|
||||||
self.load_example_data()
|
|
||||||
spec_model = self.load_test_spec(workflow_name)
|
spec_model = self.load_test_spec(workflow_name)
|
||||||
rv = self.app.get('/v1.0/workflow-specification/%s/validate' % spec_model.id, headers=self.logged_in_headers())
|
rv = self.app.get('/v1.0/workflow-specification/%s/validate' % spec_model.id, headers=self.logged_in_headers())
|
||||||
self.assert_success(rv)
|
self.assert_success(rv)
|
||||||
@ -22,6 +21,7 @@ class TestWorkflowSpecValidation(BaseTest):
|
|||||||
|
|
||||||
def test_successful_validation_of_test_workflows(self):
|
def test_successful_validation_of_test_workflows(self):
|
||||||
app.config['PB_ENABLED'] = False # Assure this is disabled.
|
app.config['PB_ENABLED'] = False # Assure this is disabled.
|
||||||
|
self.load_example_data()
|
||||||
self.assertEqual(0, len(self.validate_workflow("parallel_tasks")))
|
self.assertEqual(0, len(self.validate_workflow("parallel_tasks")))
|
||||||
self.assertEqual(0, len(self.validate_workflow("decision_table")))
|
self.assertEqual(0, len(self.validate_workflow("decision_table")))
|
||||||
self.assertEqual(0, len(self.validate_workflow("docx")))
|
self.assertEqual(0, len(self.validate_workflow("docx")))
|
||||||
@ -60,6 +60,7 @@ class TestWorkflowSpecValidation(BaseTest):
|
|||||||
self.assertEqual(0, len(errors), json.dumps(errors))
|
self.assertEqual(0, len(errors), json.dumps(errors))
|
||||||
|
|
||||||
def test_invalid_expression(self):
|
def test_invalid_expression(self):
|
||||||
|
self.load_example_data()
|
||||||
errors = self.validate_workflow("invalid_expression")
|
errors = self.validate_workflow("invalid_expression")
|
||||||
self.assertEqual(1, len(errors))
|
self.assertEqual(1, len(errors))
|
||||||
self.assertEqual("workflow_execution_exception", errors[0]['code'])
|
self.assertEqual("workflow_execution_exception", errors[0]['code'])
|
||||||
@ -68,8 +69,11 @@ class TestWorkflowSpecValidation(BaseTest):
|
|||||||
self.assertEqual("invalid_expression.bpmn", errors[0]['file_name'])
|
self.assertEqual("invalid_expression.bpmn", errors[0]['file_name'])
|
||||||
self.assertEqual('ExclusiveGateway_003amsm: Error evaluating expression \'this_value_does_not_exist==true\', '
|
self.assertEqual('ExclusiveGateway_003amsm: Error evaluating expression \'this_value_does_not_exist==true\', '
|
||||||
'name \'this_value_does_not_exist\' is not defined', errors[0]["message"])
|
'name \'this_value_does_not_exist\' is not defined', errors[0]["message"])
|
||||||
|
self.assertIsNotNone(errors[0]['task_data'])
|
||||||
|
self.assertIn("has_bananas", errors[0]['task_data'])
|
||||||
|
|
||||||
def test_validation_error(self):
|
def test_validation_error(self):
|
||||||
|
self.load_example_data()
|
||||||
errors = self.validate_workflow("invalid_spec")
|
errors = self.validate_workflow("invalid_spec")
|
||||||
self.assertEqual(1, len(errors))
|
self.assertEqual(1, len(errors))
|
||||||
self.assertEqual("workflow_validation_error", errors[0]['code'])
|
self.assertEqual("workflow_validation_error", errors[0]['code'])
|
||||||
@ -77,6 +81,7 @@ class TestWorkflowSpecValidation(BaseTest):
|
|||||||
self.assertEqual("invalid_spec.bpmn", errors[0]['file_name'])
|
self.assertEqual("invalid_spec.bpmn", errors[0]['file_name'])
|
||||||
|
|
||||||
def test_invalid_script(self):
|
def test_invalid_script(self):
|
||||||
|
self.load_example_data()
|
||||||
errors = self.validate_workflow("invalid_script")
|
errors = self.validate_workflow("invalid_script")
|
||||||
self.assertEqual(1, len(errors))
|
self.assertEqual(1, len(errors))
|
||||||
self.assertEqual("workflow_execution_exception", errors[0]['code'])
|
self.assertEqual("workflow_execution_exception", errors[0]['code'])
|
||||||
@ -84,3 +89,10 @@ class TestWorkflowSpecValidation(BaseTest):
|
|||||||
self.assertEqual("Invalid_Script_Task", errors[0]['task_id'])
|
self.assertEqual("Invalid_Script_Task", errors[0]['task_id'])
|
||||||
self.assertEqual("An Invalid Script Reference", errors[0]['task_name'])
|
self.assertEqual("An Invalid Script Reference", errors[0]['task_name'])
|
||||||
self.assertEqual("invalid_script.bpmn", errors[0]['file_name'])
|
self.assertEqual("invalid_script.bpmn", errors[0]['file_name'])
|
||||||
|
|
||||||
|
def test_repeating_sections_correctly_populated(self):
|
||||||
|
self.load_example_data()
|
||||||
|
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)
|
Loading…
x
Reference in New Issue
Block a user