def do_task(self, task, study_id, workflow_id, *args, **kwargs): self.check_args(args, 2) prefix = None if len(args) > 1: prefix = args[1] cmd = args[0] # study_info = {} # if self.__class__.__name__ in task.data: # study_info = task.data[self.__class__.__name__] retval = None if cmd == 'info': study = session.query(StudyModel).filter_by(id=study_id).first() schema = StudySchema() retval = schema.dump(study) if cmd == 'investigators': retval = StudyService().get_investigators(study_id) if cmd == 'roles': retval = StudyService().get_investigators(study_id, all=True) if cmd == 'details': details = self.pb.get_study_details(study_id) if len(details) > 0: retval = details[0] else: retval = None if cmd == 'sponsors': retval = self.pb.get_sponsors(study_id) if cmd == 'documents': retval = StudyService().get_documents_status(study_id) return self.box_it(retval, prefix)
def update_study(study_id, body): if study_id is None: raise ApiError('unknown_study', 'Please provide a valid Study ID.') study_model = session.query(StudyModel).filter_by(id=study_id).first() if study_model is None: raise ApiError('unknown_study', 'The study "' + study_id + '" is not recognized.') study: Study = StudySchema().load(body) study.update_model(study_model) session.add(study_model) session.commit() return StudySchema().dump(study)
def get_study(study_id): study = StudyService.get_study(study_id) if (study is None): raise ApiError("unknown_study", 'The study "' + study_id + '" is not recognized.', status_code=404) return StudySchema().dump(study)
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.""" """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.""" self.load_example_data() new_study = self.add_test_study() new_study = session.query(StudyModel).filter_by( id=new_study["id"]).first() api_response = self.app.get('/v1.0/study/%i' % new_study.id, headers=self.logged_in_headers(), content_type="application/json") self.assert_success(api_response) study = StudySchema().loads(api_response.get_data(as_text=True)) self.assertEqual(study.title, self.TEST_STUDY['title']) self.assertEqual(study.primary_investigator_id, self.TEST_STUDY['primary_investigator_id']) self.assertEqual(study.user_uid, self.TEST_STUDY['user_uid']) # Categories are read only, so switching to sub-scripting here. # This assumes there is one test category set up in the example data. category = study.categories[0] self.assertEqual("test_category", category['name']) self.assertEqual("Test Category", category['display_name']) self.assertEqual(1, len(category["workflows"])) workflow = category["workflows"][0] self.assertEqual("random_fact", workflow["name"]) self.assertEqual("optional", workflow["state"]) self.assertEqual("not_started", workflow["status"]) self.assertEqual(0, workflow["total_tasks"]) self.assertEqual(0, workflow["completed_tasks"])
def test_nonadmin_cannot_access_admin_only_endpoints(self): # Switch production mode on app.config['PRODUCTION'] = True self.load_example_data() # Non-admin user should not be able to delete a study non_admin_user = self._login_as_non_admin() non_admin_token_headers = dict(Authorization='Bearer ' + non_admin_user.encode_auth_token()) non_admin_study = self._make_fake_study(non_admin_user.uid) rv_add_study = self.app.post('/v1.0/study', content_type="application/json", headers=non_admin_token_headers, data=json.dumps( StudySchema().dump(non_admin_study))) self.assert_success(rv_add_study, 'Non-admin user should be able to add a study') new_non_admin_study = json.loads(rv_add_study.get_data(as_text=True)) db_non_admin_study = session.query(StudyModel).filter_by( id=new_non_admin_study['id']).first() self.assertIsNotNone(db_non_admin_study) rv_non_admin_del_study = self.app.delete( '/v1.0/study/%i' % db_non_admin_study.id, follow_redirects=False, headers=non_admin_token_headers) self.assert_failure(rv_non_admin_del_study, 401) # Switch production mode back off app.config['PRODUCTION'] = False
def test_get_study_has_details_about_files(self): # Set up the study and attach a file to it. self.load_example_data() self.create_reference_document() workflow = self.create_workflow('file_upload_form') processor = WorkflowProcessor(workflow) task = processor.next_task() irb_code = "UVACompl_PRCAppr" # The first file referenced in pb required docs. FileService.add_workflow_file(workflow_id=workflow.id, name="anything.png", content_type="png", binary_data=b'1234', irb_doc_code=irb_code) api_response = self.app.get('/v1.0/study/%i' % workflow.study_id, headers=self.logged_in_headers(), content_type="application/json") self.assert_success(api_response) study = StudySchema().loads(api_response.get_data(as_text=True)) self.assertEqual(1, len(study.files)) self.assertEqual("UVA Compliance/PRC Approval", study.files[0]["category"]) self.assertEqual("Cancer Center's PRC Approval Form", study.files[0]["description"]) self.assertEqual("UVA Compliance/PRC Approval.png", study.files[0]["download_name"])
def add_study(body): """Or any study like object. Body should include a title, and primary_investigator_id """ if 'primary_investigator_id' not in body: raise ApiError( "missing_pi", "Can't create a new study without a Primary Investigator.") if 'title' not in body: raise ApiError("missing_title", "Can't create a new study without a title.") study_model = StudyModel( user_uid=UserService.current_user().uid, title=body['title'], primary_investigator_id=body['primary_investigator_id'], last_updated=datetime.utcnow(), status=StudyStatus.in_progress) session.add(study_model) StudyService.add_study_update_event(study_model, status=StudyStatus.in_progress, event_type=StudyEventType.user, user_uid=g.user.uid) errors = StudyService._add_all_workflow_specs_to_study(study_model) session.commit() study = StudyService().get_study(study_model.id, do_status=True) study_data = StudySchema().dump(study) study_data["errors"] = ApiErrorSchema(many=True).dump(errors) return study_data
def add_test_study(self): rv = self.app.post('/v1.0/study', content_type="application/json", headers=self.logged_in_headers(), data=json.dumps(StudySchema().dump( self.TEST_STUDY))) self.assert_success(rv) return json.loads(rv.get_data(as_text=True))
def add_test_study(self): study_schema = StudySchema().dump(self.TEST_STUDY) study_schema['status'] = StudyStatus.in_progress.value rv = self.app.post('/v1.0/study', content_type="application/json", headers=self.logged_in_headers(), data=json.dumps(study_schema)) self.assert_success(rv) return json.loads(rv.get_data(as_text=True))
def put_study_on_hold(self, study_id): study = session.query(StudyModel).filter_by(id=study_id).first() study_schema = StudySchema().dump(study) study_schema['status'] = 'hold' study_schema['comment'] = 'This is my hold comment' self.update_study_status(study, study_schema) study_result = session.query(StudyModel).filter(StudyModel.id == study_id).first() return study_result
def user_studies(): """Returns all the studies associated with the current user. """ user = UserService.current_user(allow_admin_impersonate=True) StudyService.synch_with_protocol_builder_if_enabled(user) studies = StudyService().get_studies_for_user(user) if len(studies) == 0: studies = StudyService().get_studies_for_user(user, include_invalid=True) if len(studies) > 0: message = f"All studies associated with User: {user.uid} failed study validation" raise ApiError(code="study_integrity_error", message=message) results = StudySchema(many=True).dump(studies) return results
def test_abandon_study(self, mock_details): self.load_example_data() details_response = self.protocol_builder_response('study_details.json') mock_details.return_value = json.loads(details_response) study = session.query(StudyModel).first() self.assertEqual(study.status, StudyStatus.in_progress) study_schema = StudySchema().dump(study) study_schema['status'] = 'abandoned' study_schema['comment'] = 'This is my abandon comment' study_result = self.update_study_status(study, study_schema) self.assertEqual(StudyStatus.abandoned, study_result.status)
def test_update_study(self): self.load_example_data() study: StudyModel = session.query(StudyModel).first() study.title = "Pilot Study of Fjord Placement for Single Fraction Outcomes to Cortisol Susceptibility" study.protocol_builder_status = ProtocolBuilderStatus.ACTIVE rv = self.app.put('/v1.0/study/%i' % study.id, content_type="application/json", headers=self.logged_in_headers(), data=json.dumps(StudySchema().dump(study))) self.assert_success(rv) json_data = json.loads(rv.get_data(as_text=True)) self.assertEqual(study.title, json_data['title']) self.assertEqual(study.protocol_builder_status.name, json_data['protocol_builder_status'])
def test_admin_can_access_admin_only_endpoints(self): # Switch production mode on app.config['PRODUCTION'] = True self.load_example_data() admin_uids = app.config['ADMIN_UIDS'] self.assertGreater(len(admin_uids), 0) admin_uid = admin_uids[0] self.assertEqual(admin_uid, 'dhf8r') # This user is in the test ldap system. admin_headers = dict(Uid=admin_uid) rv = self.app.get('v1.0/login', follow_redirects=False, headers=admin_headers) self.assert_success(rv) admin_user = db.session.query(UserModel).filter( UserModel.uid == admin_uid).first() self.assertIsNotNone(admin_user) self.assertEqual(admin_uid, admin_user.uid) admin_study = self._make_fake_study(admin_uid) admin_token_headers = dict(Authorization='Bearer ' + admin_user.encode_auth_token().decode()) rv_add_study = self.app.post('/v1.0/study', content_type="application/json", headers=admin_token_headers, data=json.dumps( StudySchema().dump(admin_study)), follow_redirects=False) self.assert_success(rv_add_study, 'Admin user should be able to add a study') new_admin_study = json.loads(rv_add_study.get_data(as_text=True)) db_admin_study = db.session.query(StudyModel).filter_by( id=new_admin_study['id']).first() self.assertIsNotNone(db_admin_study) rv_del_study = self.app.delete('/v1.0/study/%i' % db_admin_study.id, follow_redirects=False, headers=admin_token_headers) self.assert_success(rv_del_study, 'Admin user should be able to delete a study') # Switch production mode back off app.config['PRODUCTION'] = False
def test_open_enrollment_study(self, mock_details): self.load_example_data() details_response = self.protocol_builder_response('study_details.json') mock_details.return_value = json.loads(details_response) study = session.query(StudyModel).first() self.assertEqual(study.status, StudyStatus.in_progress) study_schema = StudySchema().dump(study) study_schema['status'] = 'open_for_enrollment' study_schema['comment'] = 'This is my open enrollment comment' study_schema['enrollment_date'] = '2021-01-04T05:00:00.000Z' study_result = self.update_study_status(study, study_schema) self.assertEqual(StudyStatus.open_for_enrollment, study_result.status)
def do_task(self, task, study_id, workflow_id, *args, **kwargs): self.check_args(args) cmd = args[0] study_info = {} if self.__class__.__name__ in task.data: study_info = task.data[self.__class__.__name__] if cmd == 'info': study = session.query(StudyModel).filter_by(id=study_id).first() schema = StudySchema() self.add_data_to_task(task, {cmd: schema.dump(study)}) if cmd == 'investigators': self.add_data_to_task(task, {cmd: StudyService().get_investigators(study_id)}) if cmd == 'roles': self.add_data_to_task(task, {cmd: StudyService().get_investigators(study_id, all=True)}) if cmd == 'details': self.add_data_to_task(task, {cmd: self.pb.get_study_details(study_id)}) if cmd == 'approvals': self.add_data_to_task(task, {cmd: StudyService().get_approvals(study_id)}) if cmd == 'documents': self.add_data_to_task(task, {cmd: StudyService().get_documents_status(study_id)}) if cmd == 'protocol': self.add_data_to_task(task, {cmd: StudyService().get_protocol(study_id)})
def test_get_study_has_details_about_approvals(self): self.load_example_data() full_study = 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 ]) api_response = self.app.get('/v1.0/study/%i' % full_study['study'].id, headers=self.logged_in_headers(), content_type="application/json") self.assert_success(api_response) study = StudySchema().loads(api_response.get_data(as_text=True)) self.assertEqual(len(study.approvals), 2) for approval in study.approvals: self.assertEqual(full_study['study'].title, approval['title'])
def update_study(study_id, body): """Pretty limited, but allows manual modifications to the study status """ if study_id is None: raise ApiError('unknown_study', 'Please provide a valid Study ID.') study_model = session.query(StudyModel).filter_by(id=study_id).first() if study_model is None: raise ApiError('unknown_study', 'The study "' + study_id + '" is not recognized.') study: Study = StudyForUpdateSchema().load(body) status = StudyStatus(study.status) study_model.last_updated = datetime.utcnow() if study_model.status != status: study_model.status = status StudyService.add_study_update_event( study_model, status, StudyEventType.user, user_uid=UserService.current_user().uid if UserService.has_user() else None, comment='' if not hasattr(study, 'comment') else study.comment, ) if status == StudyStatus.open_for_enrollment: study_model.enrollment_date = study.enrollment_date session.add(study_model) session.commit() if status == StudyStatus.abandoned or status == StudyStatus.hold: WorkflowService.process_workflows_for_cancels(study_id) # Need to reload the full study to return it to the frontend study = StudyService.get_study(study_id) return StudySchema().dump(study)
def test_update_study(self): self.load_example_data() update_comment = 'Updating the study' study: StudyModel = session.query(StudyModel).first() study.title = "Pilot Study of Fjord Placement for Single Fraction Outcomes to Cortisol Susceptibility" study_schema = StudySchema().dump(study) study_schema['status'] = StudyStatus.hold.value study_schema['comment'] = update_comment rv = self.app.put('/v1.0/study/%i' % study.id, content_type="application/json", headers=self.logged_in_headers(), data=json.dumps(study_schema)) self.assert_success(rv) json_data = json.loads(rv.get_data(as_text=True)) self.assertEqual(study.title, json_data['title']) self.assertEqual(study.status.value, json_data['status']) # Making sure events history is being properly recorded study_event = session.query(StudyEvent).first() self.assertIsNotNone(study_event) self.assertEqual(study_event.status, StudyStatus.hold) self.assertEqual(study_event.event_type, StudyEventType.user) self.assertEqual(study_event.comment, update_comment) self.assertEqual(study_event.user_uid, self.test_uid)
def add_study(body): """Or any study like object. Body should include a title, and primary_investigator_id """ if 'primary_investigator_id' not in body: raise ApiError( "missing_pi", "Can't create a new study without a Primary Investigator.") if 'title' not in body: raise ApiError("missing_title", "Can't create a new study without a title.") study_model = StudyModel( user_uid=g.user.uid, title=body['title'], primary_investigator_id=body['primary_investigator_id'], last_updated=datetime.now(), protocol_builder_status=ProtocolBuilderStatus.ACTIVE) session.add(study_model) errors = StudyService._add_all_workflow_specs_to_study(study_model) session.commit() study = StudyService().get_study(study_model.id) study_data = StudySchema().dump(study) study_data["errors"] = ApiErrorSchema(many=True).dump(errors) return study_data
def all_studies(): """Returns all studies (regardless of user) with submitted files""" studies = StudyService.get_all_studies_with_files() results = StudySchema(many=True).dump(studies) return results
def test_admin_can_impersonate_another_user(self, mock_details): details_response = self.protocol_builder_response('study_details.json') mock_details.return_value = json.loads(details_response) # Switch production mode on app.config['PRODUCTION'] = True self.load_example_data() admin_user = self._login_as_admin() admin_token_headers = dict(Authorization='Bearer ' + admin_user.encode_auth_token()) # User should not be in the system yet. # non_admin_user = session.query(UserModel).filter(UserModel.uid == self.non_admin_uid).first() # self.assertIsNone(non_admin_user) # Admin should not be able to impersonate non-existent user # rv_1 = self.app.get( # '/v1.0/user?admin_impersonate_uid=' + self.non_admin_uid, # content_type="application/json", # headers=admin_token_headers, # follow_redirects=False #) # self.assert_failure(rv_1, 400) # Add the non-admin user now self.logout() non_admin_user = self._login_as_non_admin() self.assertEqual(non_admin_user.uid, self.non_admin_uid) non_admin_token_headers = dict(Authorization='Bearer ' + non_admin_user.encode_auth_token()) # Add a study for the non-admin user non_admin_study = self._make_fake_study(self.non_admin_uid) rv_add_study = self.app.post('/v1.0/study', content_type="application/json", headers=non_admin_token_headers, data=json.dumps( StudySchema().dump(non_admin_study))) self.assert_success(rv_add_study, 'Non-admin user should be able to add a study') self.logout() # Admin should be able to impersonate user now admin_user = self._login_as_admin() rv_2 = self.app.get('/v1.0/user?admin_impersonate_uid=' + self.non_admin_uid, content_type="application/json", headers=admin_token_headers, follow_redirects=False) self.assert_success(rv_2) user_data_2 = json.loads(rv_2.get_data(as_text=True)) self.assertEqual(user_data_2['uid'], self.non_admin_uid, 'Admin user should impersonate non-admin user') # Study endpoint should return non-admin user's studies rv_study = self.app.get('/v1.0/study', content_type="application/json", headers=admin_token_headers, follow_redirects=False) self.assert_success( rv_study, 'Admin user should be able to get impersonated user studies') study_data = json.loads(rv_study.get_data(as_text=True)) self.assertGreaterEqual(len(study_data), 1) self.assertEqual(study_data[0]['user_uid'], self.non_admin_uid) # Switch production mode back off app.config['PRODUCTION'] = False
def user_studies(): """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