Merge branch 'rrt/dev' into dev

This commit is contained in:
Aaron Louie 2020-05-22 20:29:21 -04:00
commit 00955723b0
21 changed files with 323 additions and 142 deletions

View File

@ -28,6 +28,7 @@ FRONTEND_AUTH_CALLBACK = environ.get('FRONTEND_AUTH_CALLBACK', default="http://l
SWAGGER_AUTH_KEY = environ.get('SWAGGER_AUTH_KEY', default="SWAGGER")
# %s/%i placeholders expected for uva_id and study_id in various calls.
PB_ENABLED = environ.get('PB_ENABLED', default=True)
PB_BASE_URL = environ.get('PB_BASE_URL', default="http://localhost:5001/pb/")
PB_USER_STUDIES_URL = environ.get('PB_USER_STUDIES_URL', default=PB_BASE_URL + "user_studies?uva_id=%s")
PB_INVESTIGATORS_URL = environ.get('PB_INVESTIGATORS_URL', default=PB_BASE_URL + "investigators?studyid=%i")

View File

@ -6,6 +6,7 @@ DEVELOPMENT = True
TESTING = True
SQLALCHEMY_DATABASE_URI = "postgresql://crc_user:crc_pass@localhost:5432/crc_test"
TOKEN_AUTH_SECRET_KEY = "Shhhh!!! This is secret! And better darn well not show up in prod."
PB_ENABLED = False
print('### USING TESTING CONFIG: ###')
print('SQLALCHEMY_DATABASE_URI = ', SQLALCHEMY_DATABASE_URI)

View File

@ -110,6 +110,19 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Study"
/study-files:
get:
operationId: crc.api.study.all_studies_and_files
summary: Provides a list of studies with submitted files
tags:
- Studies
responses:
'200':
description: An array of studies, with submitted files, ordered by the last modified date.
content:
application/json:
schema:
$ref: "#/components/schemas/Study"
/study/{study_id}:
parameters:
- name: study_id
@ -156,26 +169,6 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Study"
/study-update/{study_id}:
post:
operationId: crc.api.study.post_update_study_from_protocol_builder
summary: If the study is up-to-date with Protocol Builder, returns a 304 Not Modified. If out of date, return a 202 Accepted and study state changes to updating.
tags:
- Study Status
parameters:
- name: study_id
in: path
required: true
description: The id of the study that should be checked for updates.
schema:
type: integer
format: int32
responses:
'304':
description: Study is currently up to date and does not need to be reloaded from Protocol Builder
'202':
description: Request accepted, will preform an update. Study state set to "updating"
/workflow-specification:
get:
operationId: crc.api.workflow.all_specifications
@ -755,13 +748,6 @@ paths:
schema:
type: string
/render_docx:
parameters:
- name: data
in: query
required: true
description: The json data to use in populating the template
schema:
type: string
put:
operationId: crc.api.tools.render_docx
security: [] # Disable security for this endpoint only.
@ -777,6 +763,9 @@ paths:
file:
type: string
format: binary
data:
type: string
format: json
responses:
'200':
description: Returns the generated document.
@ -802,6 +791,54 @@ paths:
type: array
items:
$ref: "#/components/schemas/Script"
/approval:
parameters:
- name: approver_uid
in: query
required: false
description: Restrict results to a given approver uid, maybe we restrict the use of this at somepoint.
schema:
type: string
get:
operationId: crc.api.approval.get_approvals
summary: Provides a list of workflows approvals
tags:
- Approvals
responses:
'200':
description: An array of approvals
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Approval"
/approval/{approval_id}:
parameters:
- name: approval_id
in: path
required: true
description: The id of the approval in question.
schema:
type: integer
format: int32
put:
operationId: crc.api.approval.update_approval
summary: Updates an approval with the given parameters
tags:
- Approvals
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Approval'
responses:
'200':
description: Study updated successfully.
content:
application/json:
schema:
$ref: "#/components/schemas/Approval"
components:
securitySchemes:
jwt:
@ -1209,7 +1246,10 @@ components:
readOnly: true
task:
$ref: "#/components/schemas/Task"
Approval:
properties:
id:
type: number
format: integer
example: 5

10
crc/api/approval.py Normal file
View File

@ -0,0 +1,10 @@
from crc.models.approval import ApprovalModel, Approval
def get_approvals(approver_uid = None):
approval_model = ApprovalModel()
approval = Approval.from_model(approval_model)
return {}
def update_approval(approval_id):
return {}

View File

@ -1,20 +1,16 @@
from typing import List
from connexion import NoContent
from flask import g
from sqlalchemy.exc import IntegrityError
from crc import session
from crc.api.common import ApiError, ApiErrorSchema
from crc.models.protocol_builder import ProtocolBuilderStatus, ProtocolBuilderStudy
from crc.models.study import StudySchema, StudyModel, Study
from crc.models.study import StudySchema, StudyFilesSchema, StudyModel, Study
from crc.services.protocol_builder import ProtocolBuilderService
from crc.services.study_service import StudyService
def add_study(body):
"""This should never get called, and is subject to deprication. Studies
should be added through the protocol builder only."""
"""Or any study like object. """
study: Study = StudySchema().load(body)
study_model = StudyModel(**study.model_args())
session.add(study_model)
@ -59,30 +55,17 @@ def delete_study(study_id):
def all_studies():
"""Returns all the studies associated with the current user. Assures we are
in sync with values read in from the protocol builder. """
StudyService.synch_all_studies_with_protocol_builder(g.user)
"""Returns all the studies associated with the current user. """
StudyService.synch_with_protocol_builder_if_enabled(g.user)
studies = StudyService.get_studies_for_user(g.user)
results = StudySchema(many=True).dump(studies)
return results
def post_update_study_from_protocol_builder(study_id):
"""Update a single study based on data received from
the protocol builder."""
db_study = session.query(StudyModel).filter_by(study_id=study_id).all()
pb_studies: List[ProtocolBuilderStudy] = ProtocolBuilderService.get_studies(g.user.uid)
pb_study = next((pbs for pbs in pb_studies if pbs.STUDYID == study_id), None)
if pb_study:
db_study.update_from_protocol_builder(pb_study)
else:
db_study.inactive = True
db_study.protocol_builder_status = ProtocolBuilderStatus.ABANDONED
return NoContent, 304
def all_studies_and_files():
"""Returns all studies with submitted files"""
studies = StudyService.get_studies_with_files()
results = StudyFilesSchema(many=True).dump(studies)
return results

View File

@ -25,13 +25,14 @@ def render_markdown(data, template):
raise ApiError(code="invalid", message=str(e))
def render_docx(data):
def render_docx():
"""
Provides a quick way to verify that a Jinja docx template will work properly on a given json
data structure. Useful for folks that are building these templates.
"""
try:
file = connexion.request.files['file']
data = connexion.request.form['data']
target_stream = CompleteTemplate().make_template(file, json.loads(data))
return send_file(
io.BytesIO(target_stream.read()),

77
crc/models/approval.py Normal file
View File

@ -0,0 +1,77 @@
import enum
from marshmallow import INCLUDE
from crc import db, ma
from crc.models.study import StudyModel
from crc.models.workflow import WorkflowModel
class ApprovalStatus(enum.Enum):
WAITING = "WAITING" # no one has done jack.
APPROVED = "APPROVED" # approved by the reviewer
DECLINED = "DECLINED" # rejected by the reviewer
CANCELED = "CANCELED" # The document was replaced with a new version and this review is no longer needed.
class ApprovalModel(db.Model):
__tablename__ = 'approval'
id = db.Column(db.Integer, primary_key=True)
study_id = db.Column(db.Integer, db.ForeignKey(StudyModel.id), nullable=False)
study = db.relationship(StudyModel, backref='approval')
workflow_id = db.Column(db.Integer, db.ForeignKey(WorkflowModel.id), nullable=False)
workflow_version = db.Column(db.String)
approver_uid = db.Column(db.String) # Not linked to user model, as they may not have logged in yet.
status = db.Column(db.String)
message = db.Column(db.String)
class Approval(object):
@classmethod
def from_model(cls, model: ApprovalModel):
instance = cls()
instance.id = model.id
instance.workflow_version = model.workflow_version
instance.approver_uid = model.approver_uid
instance.status = model.status
instance.study_id = model.study_id
if model.study:
instance.title = model.study.title
class ApprovalSchema(ma.Schema):
class Meta:
model = Approval
fields = ["id", "workflow_version", "approver_uid", "status",
"study_id", "title"]
unknown = INCLUDE
# Carlos: Here is the data structure I was trying to imagine.
# If I were to continue down my current traing of thought, I'd create
# another class called just "Approval" that can take an ApprovalModel from the
# database and construct a data structure like this one, that can
# be provided to the API at an /approvals endpoint with GET and PUT
# dat = { "approvals": [
# {"id": 1,
# "study_id": 20,
# "workflow_id": 454,
# "study_title": "Dan Funk (dhf8r)", # Really it's just the name of the Principal Investigator
# "workflow_version": "21",
# "approver": { # Pulled from ldap
# "uid": "bgb22",
# "display_name": "Billy Bob (bgb22)",
# "title": "E42:He's a hoopy frood",
# "department": "E0:EN-Eng Study of Parallel Universes",
# },
# "files": [
# {
# "id": 124,
# "name": "ResearchRestart.docx",
# "content_type": "docx-something-whatever"
# }
# ]
# }
# ...
# ]

View File

@ -6,7 +6,7 @@ from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
from sqlalchemy import func, Index
from sqlalchemy.dialects.postgresql import UUID
from crc import db
from crc import db, ma
class FileType(enum.Enum):
@ -139,3 +139,9 @@ class LookupDataSchema(SQLAlchemyAutoSchema):
include_relationships = False
include_fk = False # Includes foreign keys
class SimpleFileSchema(ma.Schema):
class Meta:
model = FileModel
fields = ["name"]

View File

@ -5,6 +5,7 @@ from sqlalchemy import func
from crc import db, ma
from crc.api.common import ApiErrorSchema
from crc.models.file import FileModel, SimpleFileSchema
from crc.models.protocol_builder import ProtocolBuilderStatus, ProtocolBuilderStudy
from crc.models.workflow import WorkflowSpecCategoryModel, WorkflowState, WorkflowStatus, WorkflowSpecModel, \
WorkflowModel
@ -39,6 +40,10 @@ class StudyModel(db.Model):
if self.on_hold:
self.protocol_builder_status = ProtocolBuilderStatus.HOLD
def files(self):
_files = FileModel.query.filter_by(workflow_id=self.workflow[0].id)
return _files
class WorkflowMetadata(object):
def __init__(self, id, name, display_name, description, spec_version, category_id, state: WorkflowState, status: WorkflowStatus,
@ -154,3 +159,16 @@ class StudySchema(ma.Schema):
def make_study(self, data, **kwargs):
"""Can load the basic study data for updates to the database, but categories are write only"""
return Study(**data)
class StudyFilesSchema(ma.Schema):
# files = fields.List(fields.Nested(SimpleFileSchema), dump_only=True)
files = fields.Method('_files')
class Meta:
model = Study
additional = ["id", "title", "last_updated", "primary_investigator_id"]
def _files(self, obj):
return [file.name for file in obj.files()]

View File

@ -19,6 +19,8 @@ class UserModel(db.Model):
last_name = db.Column(db.String, nullable=True)
title = db.Column(db.String, nullable=True)
# Add Department and School
def encode_auth_token(self):
"""

View File

@ -73,10 +73,12 @@ class WorkflowModel(db.Model):
bpmn_workflow_json = db.Column(db.JSON)
status = db.Column(db.Enum(WorkflowStatus))
study_id = db.Column(db.Integer, db.ForeignKey('study.id'))
study = db.relationship("StudyModel", backref='workflow')
workflow_spec_id = db.Column(db.String, db.ForeignKey('workflow_spec.id'))
workflow_spec = db.relationship("WorkflowSpecModel")
spec_version = db.Column(db.String)
total_tasks = db.Column(db.Integer, default=0)
completed_tasks = db.Column(db.Integer, default=0)
# task_history = db.Column(db.ARRAY(db.String), default=[]) # The history stack of user completed tasks.
last_updated = db.Column(db.DateTime)
last_updated = db.Column(db.DateTime)
# todo: Add a version that represents the files associated with this workflow
# version = "32"

View File

@ -10,6 +10,7 @@ from crc.models.protocol_builder import ProtocolBuilderStudy, ProtocolBuilderStu
class ProtocolBuilderService(object):
ENABLED = app.config['PB_ENABLED']
STUDY_URL = app.config['PB_USER_STUDIES_URL']
INVESTIGATOR_URL = app.config['PB_INVESTIGATORS_URL']
REQUIRED_DOCS_URL = app.config['PB_REQUIRED_DOCS_URL']
@ -17,6 +18,7 @@ class ProtocolBuilderService(object):
@staticmethod
def get_studies(user_id) -> {}:
ProtocolBuilderService.__enabled_or_raise()
if not isinstance(user_id, str):
raise ApiError("invalid_user_id", "This user id is invalid: " + str(user_id))
response = requests.get(ProtocolBuilderService.STUDY_URL % user_id)
@ -30,40 +32,32 @@ class ProtocolBuilderService(object):
@staticmethod
def get_investigators(study_id) -> {}:
ProtocolBuilderService.check_args(study_id)
response = requests.get(ProtocolBuilderService.INVESTIGATOR_URL % study_id)
if response.ok and response.text:
pb_studies = json.loads(response.text)
return pb_studies
else:
raise ApiError("protocol_builder_error",
"Received an invalid response from the protocol builder (status %s): %s" %
(response.status_code, response.text))
return ProtocolBuilderService.__make_request(study_id, ProtocolBuilderService.INVESTIGATOR_URL)
@staticmethod
def get_required_docs(study_id) -> Optional[List[ProtocolBuilderRequiredDocument]]:
ProtocolBuilderService.check_args(study_id)
response = requests.get(ProtocolBuilderService.REQUIRED_DOCS_URL % study_id)
return ProtocolBuilderService.__make_request(study_id, ProtocolBuilderService.REQUIRED_DOCS_URL)
@staticmethod
def get_study_details(study_id) -> {}:
return ProtocolBuilderService.__make_request(study_id, ProtocolBuilderService.STUDY_DETAILS_URL)
@staticmethod
def __enabled_or_raise():
if not ProtocolBuilderService.ENABLED:
raise ApiError("protocol_builder_disabled", "The Protocol Builder Service is currently disabled.")
@staticmethod
def __make_request(study_id, url):
ProtocolBuilderService.__enabled_or_raise()
if not isinstance(study_id, int):
raise ApiError("invalid_study_id", "This study id is invalid: " + str(study_id))
response = requests.get(url % study_id)
if response.ok and response.text:
return json.loads(response.text)
else:
raise ApiError("protocol_builder_error",
"Received an invalid response from the protocol builder (status %s): %s" %
(response.status_code, response.text))
"Received an invalid response from the protocol builder (status %s): %s when calling "
"url '%s'." %
(response.status_code, response.text, url))
@staticmethod
def get_study_details(study_id) -> {}:
ProtocolBuilderService.check_args(study_id)
response = requests.get(ProtocolBuilderService.STUDY_DETAILS_URL % study_id)
if response.ok and response.text:
pb_study_details = json.loads(response.text)
return pb_study_details
else:
raise ApiError("protocol_builder_error",
"Received an invalid response from the protocol builder (status %s): %s" %
(response.status_code, response.text))
@staticmethod
def check_args(study_id):
if not isinstance(study_id, int):
raise ApiError("invalid_study_id", "This study id is invalid: " + str(study_id))

View File

@ -32,6 +32,12 @@ class StudyService(object):
studies.append(StudyService.get_study(study_model.id, study_model))
return studies
@staticmethod
def get_studies_with_files():
"""Returns a list of all studies"""
db_studies = session.query(StudyModel).all()
return db_studies
@staticmethod
def get_study(study_id, study_model: StudyModel = None):
"""Returns a study model that contains all the workflows organized by category.
@ -110,23 +116,29 @@ class StudyService(object):
"""Returns a list of documents related to the study, and any file information
that is available.."""
# Get PB required docs
try:
pb_docs = ProtocolBuilderService.get_required_docs(study_id=study_id)
except requests.exceptions.ConnectionError as ce:
app.logger.error("Failed to connect to the Protocol Builder - %s" % str(ce))
# Get PB required docs, if Protocol Builder Service is enabled.
if ProtocolBuilderService.ENABLED:
try:
pb_docs = ProtocolBuilderService.get_required_docs(study_id=study_id)
except requests.exceptions.ConnectionError as ce:
app.logger.error("Failed to connect to the Protocol Builder - %s" % str(ce))
pb_docs = []
else:
pb_docs = []
# Loop through all known document types, get the counts for those files, and use pb_docs to mark those required.
# Loop through all known document types, get the counts for those files,
# and use pb_docs to mark those as required.
doc_dictionary = FileService.get_reference_data(FileService.DOCUMENT_LIST, 'code', ['id'])
documents = {}
for code, doc in doc_dictionary.items():
pb_data = next((item for item in pb_docs if int(item['AUXDOCID']) == int(doc['id'])), None)
doc['required'] = False
if pb_data:
doc['required'] = True
if ProtocolBuilderService.ENABLED:
pb_data = next((item for item in pb_docs if int(item['AUXDOCID']) == int(doc['id'])), None)
doc['required'] = False
if pb_data:
doc['required'] = True
doc['study_id'] = study_id
doc['code'] = code
@ -153,7 +165,6 @@ class StudyService(object):
doc['status'] = workflow.status.value
documents[code] = doc
return documents
@ -201,9 +212,13 @@ class StudyService(object):
@staticmethod
def synch_all_studies_with_protocol_builder(user):
def synch_with_protocol_builder_if_enabled(user):
"""Assures that the studies we have locally for the given user are
in sync with the studies available in protocol builder. """
if not ProtocolBuilderService.ENABLED:
return
# Get studies matching this user from Protocol Builder
pb_studies: List[ProtocolBuilderStudy] = ProtocolBuilderService.get_studies(user.uid)

View File

@ -1,7 +1,9 @@
# Set environment variable to testing before loading.
# IMPORTANT - Environment must be loaded before app, models, etc....
import json
import os
os.environ["TESTING"] = "true"
import json
import unittest
import urllib.parse
import datetime
@ -10,10 +12,6 @@ from crc.models.protocol_builder import ProtocolBuilderStatus
from crc.models.study import StudyModel
from crc.services.file_service import FileService
from crc.services.study_service import StudyService
from crc.services.workflow_processor import WorkflowProcessor
os.environ["TESTING"] = "true"
from crc.models.file import FileModel, FileDataModel, CONTENT_TYPES
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel
from crc.models.user import UserModel
@ -32,6 +30,10 @@ class BaseTest(unittest.TestCase):
efficiently when we have a database in place.
"""
if not app.config['TESTING']:
raise (Exception("INVALID TEST CONFIGURATION. This is almost always in import order issue."
"The first class to import in each test should be the base_test.py file."))
auths = {}
test_uid = "dhf8r"

View File

@ -0,0 +1,26 @@
<?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: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="empty_workflow" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0lvudp8</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="SequenceFlow_0lvudp8" sourceRef="StartEvent_1" targetRef="EndEvent_0q4qzl9" />
<bpmn:endEvent id="EndEvent_0q4qzl9">
<bpmn:incoming>SequenceFlow_0lvudp8</bpmn:incoming>
</bpmn:endEvent>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="empty_workflow">
<bpmndi:BPMNEdge id="SequenceFlow_0lvudp8_di" bpmnElement="SequenceFlow_0lvudp8">
<di:waypoint x="215" y="117" />
<di:waypoint x="432" 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:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

14
tests/test_approvals.py Normal file
View File

@ -0,0 +1,14 @@
from tests.base_test import BaseTest
class TestApprovals(BaseTest):
def test_list_approvals(self):
rv = self.app.get('/v1.0/approval', headers=self.logged_in_headers())
self.assert_success(rv)
def test_update_approval(self):
rv = self.app.put('/v1.0/approval/1',
headers=self.logged_in_headers(),
data={})
self.assert_success(rv)

View File

@ -1,7 +1,7 @@
from unittest.mock import patch
from crc.services.protocol_builder import ProtocolBuilderService
from tests.base_test import BaseTest
from crc.services.protocol_builder import ProtocolBuilderService
class TestProtocolBuilder(BaseTest):
@ -10,6 +10,7 @@ class TestProtocolBuilder(BaseTest):
@patch('crc.services.protocol_builder.requests.get')
def test_get_studies(self, mock_get):
ProtocolBuilderService.ENABLED = True
mock_get.return_value.ok = True
mock_get.return_value.text = self.protocol_builder_response('user_studies.json')
response = ProtocolBuilderService.get_studies(self.test_uid)
@ -17,6 +18,7 @@ class TestProtocolBuilder(BaseTest):
@patch('crc.services.protocol_builder.requests.get')
def test_get_investigators(self, mock_get):
ProtocolBuilderService.ENABLED = True
mock_get.return_value.ok = True
mock_get.return_value.text = self.protocol_builder_response('investigators.json')
response = ProtocolBuilderService.get_investigators(self.test_study_id)
@ -28,6 +30,7 @@ class TestProtocolBuilder(BaseTest):
@patch('crc.services.protocol_builder.requests.get')
def test_get_required_docs(self, mock_get):
ProtocolBuilderService.ENABLED = True
mock_get.return_value.ok = True
mock_get.return_value.text = self.protocol_builder_response('required_docs.json')
response = ProtocolBuilderService.get_required_docs(self.test_study_id)
@ -37,6 +40,7 @@ class TestProtocolBuilder(BaseTest):
@patch('crc.services.protocol_builder.requests.get')
def test_get_details(self, mock_get):
ProtocolBuilderService.ENABLED = True
mock_get.return_value.ok = True
mock_get.return_value.text = self.protocol_builder_response('study_details.json')
response = ProtocolBuilderService.get_study_details(self.test_study_id)

View File

@ -1,4 +1,5 @@
import json
from tests.base_test import BaseTest
from datetime import datetime, timezone
from unittest.mock import patch
@ -8,7 +9,7 @@ from crc.models.protocol_builder import ProtocolBuilderStatus, \
from crc.models.stats import TaskEventModel
from crc.models.study import StudyModel, StudySchema
from crc.models.workflow import WorkflowSpecModel, WorkflowModel, WorkflowSpecCategoryModel
from tests.base_test import BaseTest
from crc.services.protocol_builder import ProtocolBuilderService
class TestStudyApi(BaseTest):
@ -38,24 +39,12 @@ class TestStudyApi(BaseTest):
study = session.query(StudyModel).first()
self.assertIsNotNone(study)
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators') # mock_studies
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_study_details') # mock_details
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_studies') # mock_studies
def test_get_study(self, mock_studies, mock_details, mock_docs, mock_investigators):
def test_get_study(self):
"""Generic test, but pretty detailed, in that the study should return a categorized list of workflows
This starts with out loading the example data, to show that all the bases are covered from ground 0."""
# Mock Protocol Builder responses
studies_response = self.protocol_builder_response('user_studies.json')
mock_studies.return_value = ProtocolBuilderStudySchema(many=True).loads(studies_response)
details_response = self.protocol_builder_response('study_details.json')
mock_details.return_value = json.loads(details_response)
docs_response = self.protocol_builder_response('required_docs.json')
mock_docs.return_value = json.loads(docs_response)
investigators_response = self.protocol_builder_response('investigators.json')
mock_investigators.return_value = json.loads(investigators_response)
"""NOTE: The protocol builder is not enabled or mocked out. As the master workflow (which is empty),
and the test workflow do not need it, and it is disabled in the configuration."""
new_study = self.add_test_study()
new_study = session.query(StudyModel).filter_by(id=new_study["id"]).first()
# Add a category
@ -65,7 +54,7 @@ class TestStudyApi(BaseTest):
# Create a workflow specification
self.create_workflow("random_fact", study=new_study, category_id=new_category.id)
# Assure there is a master specification, and it has the lookup files it needs.
spec = self.load_test_spec("top_level_workflow", master_spec=True)
spec = self.load_test_spec("empty_workflow", master_spec=True)
self.create_reference_document()
api_response = self.app.get('/v1.0/study/%i' % new_study.id,
@ -126,6 +115,9 @@ class TestStudyApi(BaseTest):
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_study_details') # mock_details
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_studies') # mock_studies
def test_get_all_studies(self, mock_studies, mock_details, mock_docs, mock_investigators):
# Enable the protocol builder for these tests, as the master_workflow and other workflows
# depend on using the PB for data.
ProtocolBuilderService.ENABLED = True
self.load_example_data()
s = StudyModel(
id=54321, # This matches one of the ids from the study_details_json data.
@ -208,6 +200,7 @@ class TestStudyApi(BaseTest):
self.assertEqual(study.sponsor, json_data['sponsor'])
self.assertEqual(study.ind_number, json_data['ind_number'])
def test_delete_study(self):
self.load_example_data()
study = session.query(StudyModel).first()

View File

@ -8,6 +8,7 @@ from crc.models.api_models import WorkflowApiSchema, MultiInstanceType, TaskSche
from crc.models.file import FileModelSchema
from crc.models.stats import TaskEventModel
from crc.models.workflow import WorkflowStatus
from crc.services.protocol_builder import ProtocolBuilderService
from crc.services.workflow_service import WorkflowService
from tests.base_test import BaseTest
@ -302,6 +303,9 @@ class TestTasksApi(BaseTest):
@patch('crc.services.protocol_builder.requests.get')
def test_multi_instance_task(self, mock_get):
# Enable the protocol builder.
ProtocolBuilderService.ENABLED = True
# This depends on getting a list of investigators back from the protocol builder.
mock_get.return_value.ok = True
mock_get.return_value.text = self.protocol_builder_response('investigators.json')

View File

@ -1,8 +1,8 @@
import json
import os
from crc import app
from tests.base_test import BaseTest
from crc import app
class TestStudyApi(BaseTest):
@ -22,11 +22,13 @@ class TestStudyApi(BaseTest):
{"option": "Address", "selected": False},
{"option": "Phone", "selected": True, "stored": ["Send or Transmit outside of UVA"]}]}
with open(filepath, 'rb') as f:
file_data = {'file': (f, 'my_new_file.bpmn')}
rv = self.app.put('/v1.0/render_docx?data=%s' % json.dumps(template_data),
file_data = {'file': (f, 'my_new_file.bpmn'), 'data': json.dumps(template_data)}
rv = self.app.put('/v1.0/render_docx',
data=file_data, follow_redirects=True,
content_type='multipart/form-data')
self.assert_success(rv)
self.assertIsNotNone(rv.data)
self.assertEquals('application/octet-stream', rv.content_type)
def test_list_scripts(self):
rv = self.app.get('/v1.0/list_scripts')

View File

@ -20,21 +20,7 @@ class TestWorkflowSpecValidation(BaseTest):
json_data = json.loads(rv.get_data(as_text=True))
return ApiErrorSchema(many=True).load(json_data)
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators') # mock_studies
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_study_details') # mock_details
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_studies') # mock_studies
def test_successful_validation_of_test_workflows(self, mock_studies, mock_details, mock_docs, mock_investigators):
# Mock Protocol Builder responses
studies_response = self.protocol_builder_response('user_studies.json')
mock_studies.return_value = ProtocolBuilderStudySchema(many=True).loads(studies_response)
details_response = self.protocol_builder_response('study_details.json')
mock_details.return_value = json.loads(details_response)
docs_response = self.protocol_builder_response('required_docs.json')
mock_docs.return_value = json.loads(docs_response)
investigators_response = self.protocol_builder_response('investigators.json')
mock_investigators.return_value = json.loads(investigators_response)
def test_successful_validation_of_test_workflows(self):
self.assertEqual(0, len(self.validate_workflow("parallel_tasks")))
self.assertEqual(0, len(self.validate_workflow("decision_table")))