def test_queue_org_welcome_back(self): Program.mock_program_config('p1', {'project_tasklist_template': []}) # Case 1: New PC, returning. returning_pc1 = ProjectCohort.create( program_label='p1', organization_id='Organization_returning', project_id='Project_returning', cohort_label='2020', ) returning_pc1.put() returning_pc2 = ProjectCohort.create( program_label='p1', organization_id='Organization_returning', project_id='Project_returning', cohort_label='2019', created=datetime.datetime.now() - datetime.timedelta(days=365)) returning_pc2.put() # Case 2: New PC, but not returning. new_pc = ProjectCohort.create( program_label='p1', organization_id='Organization_new', project_id='Project_new', ) new_pc.put() # Case 3: Old PC (not created in the day). old_pc = ProjectCohort.create( program_label='p1', organization_id='Organization_old', project_id='Project_old', created=datetime.datetime.now() - datetime.timedelta(hours=48), ) old_pc.put() # Some tasks are created on put. We're not interested in these. creation_tasks = self.taskqueue_stub.get_filtered_tasks() templates = [ self.create_mandrill_template('p1-{}'.format( auto_prompt.ORG_WELCOME_BACK_SUFFIX)), ] auto_prompt.queue_org_welcome_back(templates) tasks = self.taskqueue_stub.get_filtered_tasks() num_new_tasks = len(tasks) - len(creation_tasks) # Only the returning pc should have a task queued. self.assertEqual(num_new_tasks, 1) expected_url = '/task/email_project/Project_returning/p1-org-welcome-back' self.assertIn(expected_url, [t.url for t in tasks]) Program.reset_mocks()
def test_batch_participation(self): user = User.create(email='*****@*****.**') user.put() pc_kwargs = { 'program_label': self.program_label, 'cohort_label': self.cohort_label, } pcs = [ ProjectCohort.create(**pc_kwargs), ProjectCohort.create(**pc_kwargs), ] ndb.put_multi(pcs) all_pds = [] for pc in pcs: pds = mock_one_finished_one_unfinished( 1, 'Participant_unfinished', 'Participant_finished', pc_id=pc.uid, code=pc.code, program_label=self.program_label, cohort_label=self.cohort_label, ) all_pds += pds # Forbidden without allowed endpoints. pc_ids = [pc.uid for pc in pcs] self.testapp.get( '/api/project_cohorts/participation?uid={}&uid={}'.format(*pc_ids), headers=jwt_headers(user), status=403) # Running various queries works as expected. self.batch_participation(user, pcs) # Simulate a new pd being written to the first pc by clearing that # memcache key. The server should fall back to sql and still give the # same results. id_key = ParticipantData.participation_by_pc_cache_key(pcs[0].uid) code_key = ParticipantData.participation_by_pc_cache_key(pcs[0].code) self.assertIsNotNone(memcache.get(id_key)) self.assertIsNotNone(memcache.get(code_key)) memcache.delete(id_key) memcache.delete(code_key) self.batch_participation(user, pcs) # Now with everything cached, clearing the db and running the same # queries again should have the same result. ParticipantData.delete_multi(all_pds) self.batch_participation(user, pcs)
def test_fix_open_responses(self): """Non-triton pcs shouldn't have the open response param.""" non_triton_empty = ProjectCohort.create(program_label='demo-program', ) non_triton_only = ProjectCohort.create( program_label='demo-program', survey_params_json=json.dumps({ 'show_open_response_questions': 'true', })) non_triton_extra = ProjectCohort.create( program_label='demo-program', survey_params_json=json.dumps({ 'foo': 'bar', 'show_open_response_questions': 'true', }), ) triton = ProjectCohort.create( program_label='triton', survey_params_json=json.dumps({ 'learning_conditions': "feedback-for-growth", 'show_open_response_questions': 'true', }), ) def get_mapped(pc): operation = next(fix_open_responses(pc), None) if operation: return operation.entity # Empty value unchanged mapped_non_triton_empty = get_mapped(non_triton_empty) self.assertEqual(mapped_non_triton_empty.survey_params, {}) # Key removed. mapped_non_triton_only = get_mapped(non_triton_only) self.assertNotIn('show_open_response_questions', mapped_non_triton_only.survey_params) # Key removed, other data preserved. mapped_non_triton_extra = get_mapped(non_triton_extra) self.assertNotIn('show_open_response_questions', mapped_non_triton_extra.survey_params) self.assertIn('foo', mapped_non_triton_extra.survey_params) # Triton pc untouched. mapped_triton = get_mapped(triton) self.assertIsNone(mapped_triton)
def create_pd_context(self): program_label = 'demo-program' program_config = Program.get_config(program_label) template = program_config['surveys'][0]['survey_tasklist_template'] project_cohort = ProjectCohort.create( program_label=program_label, organization_id='Organization_foo', project_id='Project_foo', cohort_label='2018', ) project_cohort.put() survey = Survey.create( template, program_label=program_label, organization_id='Organization_foo', project_cohort_id=project_cohort.uid, ordinal=1, ) survey.put() participant = Participant.create(name='Pascal', organization_id='PERTS') participant.put() return (project_cohort, survey, participant)
def test_put_clears_dashboard_queries(self): pc = ProjectCohort.create( program_label='demo-program', organization_id='Organization_Foo', project_id='Project_Foo', cohort_label='2018', ) org_key = util.cached_query_key( 'SuperDashboard', organization_id=pc.organization_id, ) program_cohort_key = util.cached_query_key( 'SuperDashboard', program_label=pc.program_label, cohort_label=pc.cohort_label, ) memcache.set(org_key, {'foo': 1}) memcache.set(program_cohort_key, {'foo': 1}) # This should clear memcache. pc.put() self.assertIsNone(memcache.get(org_key)) self.assertIsNone(memcache.get(program_cohort_key))
def test_completion_csv_allowed(self): org_id = 'Organization_foo' pc = ProjectCohort.create( organization_id=org_id, program_label=self.program_label, cohort_label=self.cohort_label, ) pc.put() user = User.create( email='*****@*****.**', owned_organizations=[org_id], ) user.put() t = AuthToken.create(user.uid) t.put() self.testapp.get( '/api/project_cohorts/{}/completion/ids.csv'.format(pc.uid), params={'token': t.token}, headers=login_headers(user.uid), # Authenticated, one-time token valid, has permission: 200. status=200, )
def test_delete_code_allowed(self): code = 'trout viper' path = '/api/codes/{}'.format(code.replace(' ', '-')) pc = ProjectCohort.create( code=code, organization_id='triton', program_label='triton', ) pc.put() pc.key.get() # simulate consistency, code fetches are eventual token = jwt_helper.encode({ 'user_id': 'User_foo', 'email': '*****@*****.**', 'allowed_endpoints': ['DELETE //neptune{}'.format(path)], }) self.testapp.delete( path, headers={'Authorization': 'Bearer ' + token}, status=204, ) # Project cohort AND Unique should be gone self.assertIsNone(pc.key.get()) unique_key = ndb.Key('Unique', ProjectCohort.uniqueness_key(code)) self.assertIsNone(unique_key.get())
def create_project_cohort(self, cohort_date=datetime.datetime.today()): program_label = 'demo-program' cohort_label = 'demo-cohort' program = Program.get_config(program_label) org_id = 'Org_Foo' liaison_id = 'User_liaison' project = Project.create(organization_id=org_id, program_label=program_label) project.put() one_day = datetime.timedelta(days=1) cohort_config = { 'label': cohort_label, 'name': 'Demo Cohort', 'open_date': str(cohort_date - one_day), # yesterday 'close_date': str(cohort_date + one_day), # tomorrow } program['cohorts'][cohort_label] = cohort_config Program.mock_program_config( program_label, {'cohorts': { cohort_label: cohort_config }}, ) pc = ProjectCohort.create( project_id=project.uid, organization_id=org_id, program_label=program_label, cohort_label=cohort_label, liaison_id=liaison_id, ) pc.put() return pc
def test_override_portal_message(self): label = 'override-program' config = {'override_portal_message': 'Override message.'} Program.mock_program_config(label, config) pc = ProjectCohort.create(program_label=label) self.assertEqual( pc.to_client_dict()['portal_message'], config['override_portal_message'], )
def test_cohort(self): user = User.create(email='*****@*****.**', user_type='super_admin') user.put() pc = ProjectCohort.create( project_id='Project_001', organization_id='Organization_001', program_label=self.program_label, cohort_label=self.cohort_label, liaison_id='User_001', ) pc.put() query = ''' query PCWithCohort($uid: String) { project_cohort(uid: $uid) { program_cohort { close_date label name open_date program_description program_label program_name registration_close_date registration_open_date } } } ''' expected = { 'project_cohort': { 'program_cohort': OrderedDict( (k, self.cohort[k]) for k in sorted(self.cohort.keys())), }, } response = self.testapp.post_json( '/api/graphql', { 'query': query, 'variables': { 'uid': pc.uid }, }, headers=login_headers(user.uid), ) self.assertEqual( response.body, json.dumps(expected), )
def create_with_pc(self): project = Project.create( program_label='demo-program', organization_id='Organization_Foo', ) project.put() pc = ProjectCohort.create( program_label=project.program_label, organization_id=project.organization_id, project_id=project.uid, cohort_label='2018', ) pc.put() return project.key.get(), pc.key.get()
def test_completion_anonymous_forbidden(self): pc = ProjectCohort.create( program_label=self.program_label, cohort_label=self.cohort_label, ) pc.put() user = User.create(email='*****@*****.**') user.put() self.testapp.get( '/api/project_cohorts/{}/completion'.format(pc.uid), headers=login_headers(user.uid), # user doesn't own ProjectCohort_foo, forbidden status=403, )
def create_with_project_cohort(self): checkpoint_template = { 'name': "Survey Foo", 'label': 'demo_survey__foo', 'body': "foo", 'tasks': [], } config = { 'surveys': [ { 'name': "Student Module", 'survey_tasklist_template': [checkpoint_template], }, ], } Program.mock_program_config(self.program_label, config) org = Organization.create(name='Foo Org') org.put() project = Project.create( program_label=self.program_label, organization_id=org.uid, ) project.put() pc = ProjectCohort.create( program_label=self.program_label, organization_id=org.uid, project_id=project.uid, ) pc.put() checkpoint = Checkpoint.create(parent_id='Survey_foo', ordinal=1, program_label=self.program_label, organization_id=org.uid, project_id=project.uid, project_cohort_id=pc.uid, status='incomplete', **checkpoint_template) checkpoint.put() pc.update_cached_properties() return org, project, pc, checkpoint
def test_patch_update_codes(self): codes = ('trout viper', 'solid snake') pcs = [] for c in codes: pcs.append(ProjectCohort.create( code=c, organization_id='triton', program_label='triton', )) ndb.put_multi(pcs) for pc in pcs: pc.key.get() # simulate consistency, code fetches are eventual token = jwt_helper.encode({ 'user_id': 'User_foo', 'email': '*****@*****.**', 'allowed_endpoints': ['PUT //neptune{}'.format(path(c)) for c in codes], }) body = {'portal_message': 'hi'} response = self.testapp.patch_json( '/api/codes', [{'method': 'PUT', 'path': path(c), 'body': body} for c in codes], headers={'Authorization': 'Bearer ' + token}, ) task_names = [t['task_name'] for t in json.loads(response.body)] # PATCHing two codes should result in two tasks. for name in task_names: tasks = self.taskqueue.get_filtered_tasks(name=name) self.assertEqual(len(tasks), 1) t = tasks[0] # Running the tasks should update the codes. self.assertEqual(t.method, 'PUT') self.testapp.put_json( t.url, json.loads(t.payload), headers=t.headers, ) for pc in pcs: fetched = pc.key.get() self.assertEqual(fetched.portal_message, 'hi')
def create_demo_data(self): # Existing things to relate to. program_label = 'demo-program' cohort_label = 'demo-cohort' org_id = 'Org_Foo' user_id = 'User_Liaison' pc = ProjectCohort.create( project_id='Project_foo', organization_id=org_id, program_label=program_label, cohort_label=cohort_label, liaison_id=user_id, portal_type='custom', custom_portal_url='http://www.example.com', ) pc.put() return pc
def test_dashboard_unlisted(self): """Some programs support other apps and aren't meant to be queried.""" # See #1015 Program.mock_program_config('ep19', {'listed': False}) user = User.create(email='*****@*****.**', user_type='super_admin') user.put() pc = ProjectCohort.create( program_label='ep19', organization_id='Org_foo', ) pc.put() response = self.testapp.get('/api/dashboard?program_label=ep19', headers=login_headers(user.uid)) self.assertEqual(response.body, '[]')
def create_org_with_pc(self): org = Organization.create(name="Foo College") org.put() project = Project.create( program_label='demo-program', organization_id=org.uid, ) project.put() pc = ProjectCohort.create( program_label=project.program_label, organization_id=org.uid, project_id=project.uid, cohort_label='2018', ) pc.put() # Simulate consistency return org, project.key.get(), pc.key.get()
def test_put_clears_dashboard_queries(self): org_id = 'Organization_Foo' program_label = 'demo-program' pc = ProjectCohort.create( program_label='demo-program', organization_id=org_id, project_id='Project_Foo', cohort_label='2018', ) pc.put() program_config = Program.get_config(program_label) template = program_config['surveys'][0]['survey_tasklist_template'] survey = Survey.create( template, program_label=program_label, organization_id=org_id, project_cohort_id=pc.uid, ordinal=1, ) survey.put() org_key = util.cached_query_key( 'SuperDashboard', organization_id=pc.organization_id, ) program_cohort_key = util.cached_query_key( 'SuperDashboard', program_label=pc.program_label, cohort_label=pc.cohort_label, ) memcache.set(org_key, {'foo': 1}) memcache.set(program_cohort_key, {'foo': 1}) # Re-fetch the org so it doesn't have an associated tasklist, which # saves checkpoints. This should clear memcache without relying on those # checkpoints. survey = survey.key.get() survey.status = 'ready' survey.put() self.assertIsNone(memcache.get(org_key)) self.assertIsNone(memcache.get(program_cohort_key))
def test_update_code_allowed(self): code = 'trout viper' path = '/api/codes/{}'.format(code.replace(' ', '-')) pc = ProjectCohort.create( code=code, organization_id='triton', program_label='triton', ) pc.put() pc.key.get() # simulate consistency, code fetches are eventual token = jwt_helper.encode({ 'user_id': 'User_foo', 'email': '*****@*****.**', 'allowed_endpoints': ['PUT //neptune{}'.format(path)], }) self.testapp.put_json( path, {'portal_message': 'hi'}, headers={'Authorization': 'Bearer ' + token} )
def test_completion_csv_forbidden(self): pc = ProjectCohort.create( program_label=self.program_label, cohort_label=self.cohort_label, ) pc.put() user = User.create(email='*****@*****.**') user.put() t = AuthToken.create(user.uid) t.put() self.testapp.get( '/api/project_cohorts/{}/completion/ids.csv'.format(pc.uid), params={'token': t.token}, headers=login_headers(user.uid), # user doesn't own ProjectCohort_foo, forbidden status=403, )
def create_project_cohort(self, cohort_date=datetime.datetime.today()): program = Program.get_config(self.program_label) liaison = User.create(email='*****@*****.**') org = Organization.create(name="Org Foo", liaison_id=liaison.uid) liaison.owned_organizations.append(org.uid) project = Project.create(organization_id=org.uid, program_label=self.program_label) liaison.put() org.put() project.put() pc = ProjectCohort.create( project_id=project.uid, organization_id=org.uid, program_label=self.program_label, cohort_label=self.cohort_label, liaison_id=liaison.uid, ) pc.put() surveys = Survey.create_for_project_cohort(program['surveys'], pc) ndb.put_multi(surveys) return liaison, org, project, pc, surveys
def test_completion_anonymous_allowed(self): org_id = 'Organization_foo' pc = ProjectCohort.create( organization_id=org_id, program_label=self.program_label, cohort_label=self.cohort_label, ) pc.put() today = datetime.datetime.now() yesterday = today - datetime.timedelta(days=1) tomorrow = today + datetime.timedelta(days=1) pd_params = { 'key': 'progress', 'value': '100', 'program_label': self.program_label, 'cohort_label': self.cohort_label, 'project_cohort_id': pc.uid, 'code': pc.code, 'survey_id': 'Survey_foo', 'survey_ordinal': 1, } old_pd = ParticipantData.create( created=yesterday.strftime(config.sql_datetime_format), modified=yesterday.strftime(config.sql_datetime_format), participant_id='Participant_foo', **pd_params) # Use a lower-level interface so we can set the modified time. row = ParticipantData.coerce_row_dict(old_pd.to_dict()) with mysql_connection.connect() as sql: sql.insert_or_update(ParticipantData.table, row) current_pd = ParticipantData.create(participant_id='Participant_bar', **pd_params) current_pd.put() user = User.create( email='*****@*****.**', owned_organizations=[org_id], ) user.put() result = self.testapp.get( '/api/project_cohorts/{}/completion'.format(pc.uid), params={ 'start': util.datelike_to_iso_string(today), 'end': util.datelike_to_iso_string(tomorrow), }, headers=login_headers(user.uid), # Authenticated, has permission: 200. status=200, ) expected = [{ 'value': '100', 'survey_ordinal': 1, 'participant_id': current_pd.participant_id, }] self.assertEqual(json.loads(result.body), expected)
def test_default_portal_type(self): """Portal type should be based on program config""" program = Program.get_config(self.program_label) pc = ProjectCohort.create(program_label=self.program_label, ) self.assertEqual(pc.portal_type, program['default_portal_type'])
def test_checklist_nudge(self): month_from_now = util.datelike_to_iso_string(datetime.date.today() + datetime.timedelta( days=30)) Program.mock_program_config( 'p1', { 'cohorts': { '2019': { 'label': '2019', 'open_date': '2019-06-01' }, '2020': { 'label': '2020', 'open_date': month_from_now }, }, 'project_tasklist_template': [] }, ) Program.mock_program_config( 'p2', { 'cohorts': {}, 'project_tasklist_template': [] }, ) templates = [ self.create_mandrill_template('p1-{}'.format( auto_prompt.CHECKLIST_NUDGE_SUFFIX)), ] # Case 1: 2020 PCs gets prompt current_pc1 = ProjectCohort.create( program_label='p1', organization_id='Organization_foo', project_id='Project_current1', cohort_label='2020', ) current_pc1.put() current_pc2 = ProjectCohort.create( program_label='p1', organization_id='Organization_bar', project_id='Project_current2', cohort_label='2020', ) current_pc2.put() # Case 2: 2019 PC does not old_pc = ProjectCohort.create( program_label='p1', cohort_label='2019', project_id='Project_old', ) old_pc.put() # Case 3: PC in other program does not other_pc = ProjectCohort.create( program_label='p2', cohort_label='2020', project_id='Project_other', ) other_pc.put() # Some tasks are created on put. We're not interested in these. creation_tasks = self.taskqueue_stub.get_filtered_tasks() auto_prompt.queue_checklist_nudge(templates) tasks = self.taskqueue_stub.get_filtered_tasks() num_new_tasks = len(tasks) - len(creation_tasks) # Only the 2 2020 pcs in the right program should have a task queued. self.assertEqual(num_new_tasks, 2) expected_url1 = '/task/email_project/Project_current1/p1-checklist-nudge' self.assertIn(expected_url1, [t.url for t in tasks]) expected_url2 = '/task/email_project/Project_current2/p1-checklist-nudge' self.assertIn(expected_url2, [t.url for t in tasks]) Program.reset_mocks()