Пример #1
0
    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()
Пример #2
0
    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)
Пример #3
0
    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)
Пример #5
0
    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())
Пример #8
0
    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
Пример #9
0
    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),
        )
Пример #11
0
    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,
        )
Пример #13
0
    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, '[]')
Пример #17
0
    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()
Пример #18
0
    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)
Пример #23
0
 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'])
Пример #24
0
    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()