2020-05-24 07:22:14 +00:00
|
|
|
import json
|
2020-06-11 20:39:00 +00:00
|
|
|
import random
|
|
|
|
import string
|
|
|
|
|
2020-06-12 18:09:08 +00:00
|
|
|
from flask import g
|
|
|
|
|
2020-05-24 05:53:48 +00:00
|
|
|
from tests.base_test import BaseTest
|
2020-06-03 02:01:49 +00:00
|
|
|
from crc import session, db
|
2020-06-12 17:46:10 +00:00
|
|
|
from crc.models.approval import ApprovalModel, ApprovalStatus
|
2020-06-02 22:17:00 +00:00
|
|
|
from crc.models.study import StudyModel
|
2020-06-12 17:46:10 +00:00
|
|
|
from crc.models.workflow import WorkflowModel
|
2020-05-24 07:22:14 +00:00
|
|
|
|
2020-05-24 05:53:48 +00:00
|
|
|
|
|
|
|
class TestApprovals(BaseTest):
|
|
|
|
def setUp(self):
|
|
|
|
"""Initial setup shared by all TestApprovals tests"""
|
|
|
|
self.load_example_data()
|
2020-06-11 20:39:00 +00:00
|
|
|
|
|
|
|
# Add a study with 2 approvers
|
|
|
|
study_workflow_approvals_1 = self._create_study_workflow_approvals(
|
|
|
|
user_uid="dhf8r", title="first study", primary_investigator_id="lb3dp",
|
|
|
|
approver_uids=["lb3dp", "dhf8r"], statuses=[ApprovalStatus.PENDING.value, ApprovalStatus.PENDING.value]
|
2020-05-26 16:21:36 +00:00
|
|
|
)
|
2020-06-11 20:39:00 +00:00
|
|
|
self.study = study_workflow_approvals_1['study']
|
|
|
|
self.workflow = study_workflow_approvals_1['workflow']
|
|
|
|
self.approval = study_workflow_approvals_1['approvals'][0]
|
|
|
|
self.approval_2 = study_workflow_approvals_1['approvals'][1]
|
|
|
|
|
|
|
|
# Add a study with 1 approver
|
|
|
|
study_workflow_approvals_2 = self._create_study_workflow_approvals(
|
|
|
|
user_uid="dhf8r", title="second study", primary_investigator_id="dhf8r",
|
|
|
|
approver_uids=["lb3dp"], statuses=[ApprovalStatus.PENDING.value]
|
2020-06-02 22:17:00 +00:00
|
|
|
)
|
2020-06-11 20:39:00 +00:00
|
|
|
self.unrelated_study = study_workflow_approvals_2['study']
|
|
|
|
self.unrelated_workflow = study_workflow_approvals_2['workflow']
|
|
|
|
self.approval_3 = study_workflow_approvals_2['approvals'][0]
|
2020-05-24 05:53:48 +00:00
|
|
|
|
|
|
|
def test_list_approvals_per_approver(self):
|
|
|
|
"""Only approvals associated with approver should be returned"""
|
2020-05-26 16:21:36 +00:00
|
|
|
approver_uid = self.approval_2.approver_uid
|
2020-06-02 01:45:09 +00:00
|
|
|
rv = self.app.get(f'/v1.0/approval', headers=self.logged_in_headers())
|
2020-05-24 05:53:48 +00:00
|
|
|
self.assert_success(rv)
|
|
|
|
|
2020-05-26 16:21:36 +00:00
|
|
|
response = json.loads(rv.get_data(as_text=True))
|
|
|
|
|
2020-06-02 22:17:00 +00:00
|
|
|
# Stored approvals are 3
|
2020-05-26 16:21:36 +00:00
|
|
|
approvals_count = ApprovalModel.query.count()
|
2020-06-02 22:17:00 +00:00
|
|
|
self.assertEqual(approvals_count, 3)
|
2020-05-26 16:21:36 +00:00
|
|
|
|
|
|
|
# but Dan's approvals should be only 1
|
|
|
|
self.assertEqual(len(response), 1)
|
|
|
|
|
|
|
|
# Confirm approver UID matches returned payload
|
2020-06-03 00:07:56 +00:00
|
|
|
approval = response[0]
|
|
|
|
self.assertEqual(approval['approver']['uid'], approver_uid)
|
2020-05-26 16:21:36 +00:00
|
|
|
|
2020-06-05 21:49:55 +00:00
|
|
|
def test_list_approvals_as_user(self):
|
|
|
|
"""All approvals as different user"""
|
|
|
|
rv = self.app.get('/v1.0/approval?as_user=lb3dp', headers=self.logged_in_headers())
|
2020-05-24 05:53:48 +00:00
|
|
|
self.assert_success(rv)
|
|
|
|
|
2020-05-26 16:21:36 +00:00
|
|
|
response = json.loads(rv.get_data(as_text=True))
|
|
|
|
|
2020-06-05 21:49:55 +00:00
|
|
|
# Returned approvals should match what's in the db for user ld3dp, we should get one
|
|
|
|
# approval back per study (2 studies), and that approval should have one related approval.
|
2020-05-26 16:21:36 +00:00
|
|
|
response_count = len(response)
|
2020-06-02 01:45:09 +00:00
|
|
|
self.assertEqual(2, response_count)
|
|
|
|
|
|
|
|
rv = self.app.get('/v1.0/approval', headers=self.logged_in_headers())
|
|
|
|
self.assert_success(rv)
|
2020-06-02 11:08:29 +00:00
|
|
|
response = json.loads(rv.get_data(as_text=True))
|
2020-06-02 01:45:09 +00:00
|
|
|
response_count = len(response)
|
|
|
|
self.assertEqual(1, response_count)
|
2020-06-11 20:39:00 +00:00
|
|
|
self.assertEqual(1, len(response[0]['related_approvals'])) # this approval has a related approval.
|
2020-06-02 01:45:09 +00:00
|
|
|
|
2020-06-02 11:43:19 +00:00
|
|
|
def test_update_approval_fails_if_not_the_approver(self):
|
2020-06-02 22:17:00 +00:00
|
|
|
approval = session.query(ApprovalModel).filter_by(approver_uid='lb3dp').first()
|
2020-06-02 11:43:19 +00:00
|
|
|
data = {'id': approval.id,
|
|
|
|
"approver_uid": "dhf8r",
|
|
|
|
'message': "Approved. I like the cut of your jib.",
|
|
|
|
'status': ApprovalStatus.APPROVED.value}
|
|
|
|
|
|
|
|
self.assertEqual(approval.status, ApprovalStatus.PENDING.value)
|
|
|
|
|
|
|
|
rv = self.app.put(f'/v1.0/approval/{approval.id}',
|
|
|
|
content_type="application/json",
|
|
|
|
headers=self.logged_in_headers(), # As dhf8r
|
|
|
|
data=json.dumps(data))
|
|
|
|
self.assert_failure(rv)
|
|
|
|
|
|
|
|
def test_accept_approval(self):
|
|
|
|
approval = session.query(ApprovalModel).filter_by(approver_uid='dhf8r').first()
|
|
|
|
data = {'id': approval.id,
|
2020-06-05 19:39:52 +00:00
|
|
|
"approver": {"uid": "dhf8r"},
|
2020-06-02 11:43:19 +00:00
|
|
|
'message': "Approved. I like the cut of your jib.",
|
|
|
|
'status': ApprovalStatus.APPROVED.value}
|
2020-06-02 01:45:09 +00:00
|
|
|
|
2020-06-02 11:43:19 +00:00
|
|
|
self.assertEqual(approval.status, ApprovalStatus.PENDING.value)
|
2020-06-02 01:45:09 +00:00
|
|
|
|
2020-06-02 11:43:19 +00:00
|
|
|
rv = self.app.put(f'/v1.0/approval/{approval.id}',
|
|
|
|
content_type="application/json",
|
|
|
|
headers=self.logged_in_headers(), # As dhf8r
|
|
|
|
data=json.dumps(data))
|
|
|
|
self.assert_success(rv)
|
|
|
|
|
|
|
|
session.refresh(approval)
|
|
|
|
|
|
|
|
# Updated record should now have the data sent to the endpoint
|
|
|
|
self.assertEqual(approval.message, data['message'])
|
|
|
|
self.assertEqual(approval.status, ApprovalStatus.APPROVED.value)
|
2020-05-26 16:21:36 +00:00
|
|
|
|
2020-06-02 11:43:19 +00:00
|
|
|
def test_decline_approval(self):
|
|
|
|
approval = session.query(ApprovalModel).filter_by(approver_uid='dhf8r').first()
|
|
|
|
data = {'id': approval.id,
|
2020-06-05 19:39:52 +00:00
|
|
|
"approver": {"uid": "dhf8r"},
|
2020-06-02 11:43:19 +00:00
|
|
|
'message': "Approved. I find the cut of your jib lacking.",
|
|
|
|
'status': ApprovalStatus.DECLINED.value}
|
2020-05-27 18:06:32 +00:00
|
|
|
|
2020-06-02 11:43:19 +00:00
|
|
|
self.assertEqual(approval.status, ApprovalStatus.PENDING.value)
|
2020-05-27 18:06:32 +00:00
|
|
|
|
2020-06-02 11:43:19 +00:00
|
|
|
rv = self.app.put(f'/v1.0/approval/{approval.id}',
|
2020-05-24 07:22:14 +00:00
|
|
|
content_type="application/json",
|
2020-06-02 11:43:19 +00:00
|
|
|
headers=self.logged_in_headers(), # As dhf8r
|
2020-05-27 18:06:32 +00:00
|
|
|
data=json.dumps(data))
|
2020-05-24 05:53:48 +00:00
|
|
|
self.assert_success(rv)
|
2020-05-27 18:06:32 +00:00
|
|
|
|
2020-06-02 11:43:19 +00:00
|
|
|
session.refresh(approval)
|
2020-05-27 18:06:32 +00:00
|
|
|
|
|
|
|
# Updated record should now have the data sent to the endpoint
|
2020-06-02 11:43:19 +00:00
|
|
|
self.assertEqual(approval.message, data['message'])
|
|
|
|
self.assertEqual(approval.status, ApprovalStatus.DECLINED.value)
|
2020-06-03 02:01:49 +00:00
|
|
|
|
|
|
|
def test_csv_export(self):
|
2020-06-12 17:46:10 +00:00
|
|
|
self.load_test_spec('two_forms')
|
|
|
|
self._add_lots_of_random_approvals(n=50, workflow_spec_name='two_forms')
|
|
|
|
|
|
|
|
# Get all workflows
|
|
|
|
workflows = db.session.query(WorkflowModel).filter_by(workflow_spec_id='two_forms').all()
|
|
|
|
|
|
|
|
# For each workflow, complete all tasks
|
|
|
|
for workflow in workflows:
|
|
|
|
workflow_api = self.get_workflow_api(workflow, user_uid=workflow.study.user_uid)
|
|
|
|
self.assertEqual('two_forms', workflow_api.workflow_spec_id)
|
|
|
|
|
|
|
|
# Log current user out.
|
2020-06-12 18:09:08 +00:00
|
|
|
g.user = None
|
|
|
|
self.assertIsNone(g.user)
|
2020-06-12 17:46:10 +00:00
|
|
|
|
|
|
|
# Complete the form for Step one and post it.
|
|
|
|
self.complete_form(workflow, workflow_api.next_task, {"color": "blue"}, error_code=None, user_uid=workflow.study.user_uid)
|
|
|
|
|
|
|
|
# Get the next Task
|
|
|
|
workflow_api = self.get_workflow_api(workflow, user_uid=workflow.study.user_uid)
|
|
|
|
self.assertEqual("StepTwo", workflow_api.next_task.name)
|
2020-06-11 21:15:44 +00:00
|
|
|
|
2020-06-12 17:46:10 +00:00
|
|
|
# Get all user Tasks and check that the data have been saved
|
|
|
|
task = workflow_api.next_task
|
|
|
|
self.assertIsNotNone(task.data)
|
|
|
|
for val in task.data.values():
|
|
|
|
self.assertIsNotNone(val)
|
2020-06-11 21:15:44 +00:00
|
|
|
|
2020-06-03 02:01:49 +00:00
|
|
|
rv = self.app.get(f'/v1.0/approval/csv', headers=self.logged_in_headers())
|
2020-06-11 20:39:00 +00:00
|
|
|
self.assert_success(rv)
|
|
|
|
|
|
|
|
def test_all_approvals(self):
|
2020-06-11 21:15:44 +00:00
|
|
|
self._add_lots_of_random_approvals()
|
|
|
|
|
2020-06-11 20:39:00 +00:00
|
|
|
not_canceled = session.query(ApprovalModel).filter(ApprovalModel.status != 'CANCELED').all()
|
|
|
|
not_canceled_study_ids = []
|
|
|
|
for a in not_canceled:
|
|
|
|
if a.study_id not in not_canceled_study_ids:
|
|
|
|
not_canceled_study_ids.append(a.study_id)
|
|
|
|
|
|
|
|
rv_all = self.app.get(f'/v1.0/all_approvals?status=false', headers=self.logged_in_headers())
|
|
|
|
self.assert_success(rv_all)
|
|
|
|
all_data = json.loads(rv_all.get_data(as_text=True))
|
|
|
|
self.assertEqual(len(all_data), len(not_canceled_study_ids), 'Should return all non-canceled approvals, grouped by study')
|
|
|
|
|
|
|
|
all_approvals = session.query(ApprovalModel).all()
|
|
|
|
all_approvals_study_ids = []
|
|
|
|
for a in all_approvals:
|
|
|
|
if a.study_id not in all_approvals_study_ids:
|
|
|
|
all_approvals_study_ids.append(a.study_id)
|
|
|
|
|
|
|
|
rv_all = self.app.get(f'/v1.0/all_approvals?status=true', headers=self.logged_in_headers())
|
|
|
|
self.assert_success(rv_all)
|
|
|
|
all_data = json.loads(rv_all.get_data(as_text=True))
|
|
|
|
self.assertEqual(len(all_data), len(all_approvals_study_ids), 'Should return all approvals, grouped by study')
|
|
|
|
|
|
|
|
def test_approvals_counts(self):
|
|
|
|
statuses = [name for name, value in ApprovalStatus.__members__.items()]
|
2020-06-11 21:15:44 +00:00
|
|
|
self._add_lots_of_random_approvals()
|
2020-06-11 20:39:00 +00:00
|
|
|
|
2020-06-11 21:15:44 +00:00
|
|
|
# Get the counts
|
2020-06-11 20:39:00 +00:00
|
|
|
rv_counts = self.app.get(f'/v1.0/approval-counts', headers=self.logged_in_headers())
|
|
|
|
self.assert_success(rv_counts)
|
|
|
|
counts = json.loads(rv_counts.get_data(as_text=True))
|
|
|
|
|
2020-06-11 21:15:44 +00:00
|
|
|
# Get the actual approvals
|
2020-06-11 20:39:00 +00:00
|
|
|
rv_approvals = self.app.get(f'/v1.0/approval', headers=self.logged_in_headers())
|
|
|
|
self.assert_success(rv_approvals)
|
|
|
|
approvals = json.loads(rv_approvals.get_data(as_text=True))
|
|
|
|
|
2020-06-11 21:15:44 +00:00
|
|
|
# Tally up the number of approvals in each status category
|
2020-06-11 20:39:00 +00:00
|
|
|
manual_counts = {}
|
|
|
|
for status in statuses:
|
|
|
|
manual_counts[status] = 0
|
|
|
|
|
|
|
|
for approval in approvals:
|
|
|
|
manual_counts[approval['status']] += 1
|
|
|
|
|
2020-06-11 21:15:44 +00:00
|
|
|
# Numbers in each category should match
|
2020-06-11 20:39:00 +00:00
|
|
|
for status in statuses:
|
2020-06-11 21:15:44 +00:00
|
|
|
self.assertEqual(counts[status], manual_counts[status], 'Approval counts for status %s should match' % status)
|
2020-06-11 20:39:00 +00:00
|
|
|
|
2020-06-11 21:15:44 +00:00
|
|
|
# Total number of approvals should match
|
|
|
|
total_counts = sum(counts[status] for status in statuses)
|
|
|
|
self.assertEqual(total_counts, len(approvals), 'Total approval counts for user should match number of approvals for user')
|
2020-06-11 20:39:00 +00:00
|
|
|
|
2020-06-12 17:46:10 +00:00
|
|
|
def _create_study_workflow_approvals(self, user_uid, title, primary_investigator_id, approver_uids, statuses,
|
|
|
|
workflow_spec_name="random_fact"):
|
2020-06-11 20:39:00 +00:00
|
|
|
study = self.create_study(uid=user_uid, title=title, primary_investigator_id=primary_investigator_id)
|
2020-06-12 17:46:10 +00:00
|
|
|
workflow = self.create_workflow(workflow_name=workflow_spec_name, study=study)
|
2020-06-11 20:39:00 +00:00
|
|
|
approvals = []
|
|
|
|
|
|
|
|
for i in range(len(approver_uids)):
|
|
|
|
approvals.append(self.create_approval(
|
|
|
|
study=study,
|
|
|
|
workflow=workflow,
|
|
|
|
approver_uid=approver_uids[i],
|
|
|
|
status=statuses[i],
|
|
|
|
version=1
|
|
|
|
))
|
|
|
|
|
|
|
|
return {
|
|
|
|
'study': study,
|
|
|
|
'workflow': workflow,
|
|
|
|
'approvals': approvals,
|
|
|
|
}
|
2020-06-11 21:15:44 +00:00
|
|
|
|
2020-06-12 17:46:10 +00:00
|
|
|
def _add_lots_of_random_approvals(self, n=100, workflow_spec_name="random_fact"):
|
2020-06-11 21:15:44 +00:00
|
|
|
num_studies_before = db.session.query(StudyModel).count()
|
|
|
|
statuses = [name for name, value in ApprovalStatus.__members__.items()]
|
|
|
|
|
|
|
|
# Add a whole bunch of approvals with random statuses
|
2020-06-12 17:46:10 +00:00
|
|
|
for i in range(n):
|
2020-06-11 21:15:44 +00:00
|
|
|
approver_uids = random.choices(["lb3dp", "dhf8r"])
|
|
|
|
self._create_study_workflow_approvals(
|
|
|
|
user_uid=random.choice(["lb3dp", "dhf8r"]),
|
|
|
|
title="".join(random.choices(string.ascii_lowercase, k=64)),
|
|
|
|
primary_investigator_id=random.choice(["lb3dp", "dhf8r"]),
|
|
|
|
approver_uids=approver_uids,
|
2020-06-12 17:46:10 +00:00
|
|
|
statuses=random.choices(statuses, k=len(approver_uids)),
|
|
|
|
workflow_spec_name=workflow_spec_name
|
2020-06-11 21:15:44 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
session.flush()
|
|
|
|
num_studies_after = db.session.query(StudyModel).count()
|
2020-06-12 17:46:10 +00:00
|
|
|
self.assertEqual(num_studies_after, num_studies_before + n)
|
2020-06-11 21:15:44 +00:00
|
|
|
|