def test_not_enough_assignments_to_allocate(self):
        """Test for the case when there are too few assignments in the pool."""

        email = '*****@*****.**'
        name = 'Student 1'
        submission = transforms.dumps([
            {'index': 0, 'type': 'regex', 'value': 'S1-1', 'correct': True},
            {'index': 1, 'type': 'choices', 'value': 3, 'correct': False},
            {'index': 2, 'type': 'regex', 'value': 'is-S1', 'correct': True},
        ])
        payload = {
            'answers': submission, 'assessment_type': LEGACY_REVIEW_UNIT_ID}

        actions.login(email)
        actions.register(self, name)
        response = actions.submit_assessment(
            self, LEGACY_REVIEW_UNIT_ID, payload)

        # The student goes to the review dashboard and requests an assignment
        # to review -- but there is nothing to review.
        response = actions.request_new_review(
            self, LEGACY_REVIEW_UNIT_ID, expected_status_code=200)
        actions.assert_does_not_contain('Assignment to review', response.body)
        actions.assert_contains(
            'Sorry, there are no new submissions ', response.body)
        actions.assert_contains('disabled="true"', response.body)

        actions.logout()
    def test_student_cannot_see_reviews_prematurely(self):
        """Test that students cannot see others' reviews prematurely."""

        email = '*****@*****.**'
        name = 'Student 1'
        submission = transforms.dumps([
            {'index': 0, 'type': 'regex', 'value': 'S1-1', 'correct': True},
            {'index': 1, 'type': 'choices', 'value': 3, 'correct': False},
            {'index': 2, 'type': 'regex', 'value': 'is-S1', 'correct': True},
        ])
        payload = {
            'answers': submission, 'assessment_type': LEGACY_REVIEW_UNIT_ID}

        actions.login(email)
        actions.register(self, name)
        response = actions.submit_assessment(
            self, LEGACY_REVIEW_UNIT_ID, payload)

        # Student 1 cannot see the reviews for his assignment yet, because he
        # has not submitted the two required reviews.
        response = self.get('assessment?name=%s' % LEGACY_REVIEW_UNIT_ID)
        actions.assert_equals(response.status_int, 200)
        actions.assert_contains('Due date for this assignment', response.body)
        actions.assert_contains(
            'After you have completed the required number of peer reviews',
            response.body)

        actions.logout()
    def setUp(self):
        super(StudentRedirectTestBase, self).setUp()
        context = actions.simple_add_course(COURSE_NAME, ADMIN_EMAIL,
                                            COURSE_TITLE)
        course = courses.Course(None, context)
        self.unit = course.add_unit()
        self.unit.title = 'The Unit'
        self.unit.availability = courses.AVAILABILITY_AVAILABLE
        self.lesson_one = course.add_lesson(self.unit)
        self.lesson_one.title = 'Lesson One'
        self.lesson_one.availability = courses.AVAILABILITY_AVAILABLE
        self.lesson_two = course.add_lesson(self.unit)
        self.lesson_two.title = 'Lesson Two'
        self.lesson_two.availability = courses.AVAILABILITY_AVAILABLE
        self.assessment = course.add_assessment()
        self.assessment.title = 'The Assessment'
        self.assessment.availability = courses.AVAILABILITY_AVAILABLE
        course.save()

        actions.login(REGISTERED_STUDENT_EMAIL)
        actions.register(self, REGISTERED_STUDENT_NAME, COURSE_NAME)
        # Actions.register views the student's profile page; clear this out.
        with common_utils.Namespace(NAMESPACE):
            prefs = models.StudentPreferencesDAO.load_or_default()
            prefs.last_location = None
            models.StudentPreferencesDAO.save(prefs)
    def setUp(self):
        super(ManualProgressTest, self).setUp()

        # Add a course that will show up.
        context = actions.simple_add_course(COURSE_NAME, ADMIN_EMAIL,
                                            COURSE_TITLE)

        # Register a student for that course.
        actions.login(REGISTERED_STUDENT_EMAIL)
        actions.register(self, REGISTERED_STUDENT_NAME, COURSE_NAME)

        # Add content to course
        self._course = courses.Course(None, context)

        self._unit_one = self._course.add_unit()
        self._unit_one.title = 'Unit Labels: Foo'
        self._unit_one.availability = courses.AVAILABILITY_AVAILABLE
        self._lesson_1_1 = self._course.add_lesson(self._unit_one)
        self._lesson_1_1.title = 'Unit One, Lesson One'
        self._lesson_1_1.availability = courses.AVAILABILITY_AVAILABLE
        self._lesson_1_1.manual_progress = True
        self._lesson_1_2 = self._course.add_lesson(self._unit_one)
        self._lesson_1_2.title = 'Unit One, Lesson Two'
        self._lesson_1_2.availability = courses.AVAILABILITY_AVAILABLE
        self._lesson_1_2.manual_progress = True

        self._unit_two = self._course.add_unit()
        self._unit_two.title = 'Unit Labels: Foo'
        self._unit_two.availability = courses.AVAILABILITY_AVAILABLE
        self._unit_two.manual_progress = True
        self._lesson_2_1 = self._course.add_lesson(self._unit_two)
        self._lesson_2_1.title = 'Unit Two, Lesson One'
        self._lesson_2_1.availability = courses.AVAILABILITY_AVAILABLE
        self._lesson_2_2 = self._course.add_lesson(self._unit_two)
        self._lesson_2_2.title = 'Unit Two, Lesson Two'
        self._lesson_2_2.availability = courses.AVAILABILITY_AVAILABLE

        self._sub_assessment = self._course.add_assessment()
        self._sub_assessment.availability = courses.AVAILABILITY_AVAILABLE

        self._toplevel_assessment = self._course.add_assessment()
        self._sub_assessment.availability = courses.AVAILABILITY_AVAILABLE

        self._unit_three = self._course.add_unit()
        self._unit_three.pre_assessment = self._sub_assessment.unit_id

        self._course.save()

        with common_utils.Namespace(NAMESPACE):
            self.foo_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Foo',
                       'descripton': 'foo',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))
            self.bar_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Bar',
                       'descripton': 'bar',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))
        self.overridden_environment = actions.OverriddenEnvironment(
            {'course': {analytics.CAN_RECORD_STUDENT_EVENTS: 'true'}})
        self.overridden_environment.__enter__()
Beispiel #5
0
    def setUp(self):
        super(CertificateCriteriaTestCase, self).setUp()
        self.base = '/' + self.COURSE_NAME
        context = actions.simple_add_course(
            self.COURSE_NAME, self.ADMIN_EMAIL, 'Certificate Criteria')

        self.old_namespace = namespace_manager.get_namespace()
        namespace_manager.set_namespace('ns_%s' % self.COURSE_NAME)

        self.course = courses.Course(None, context)
        self.course.save()
        self.TEST_USER = actions.login('*****@*****.**')
        actions.register(self, self.TEST_USER.email())
        self.student = (
            models.StudentProfileDAO.get_enrolled_student_by_user_for(
                self.TEST_USER, context))

        # Override course.yaml settings by patching app_context.
        self.get_environ_old = sites.ApplicationContext.get_environ
        self.certificate_criteria = []

        def get_environ_new(app_context):
            environ = self.get_environ_old(app_context)
            environ['certificate_criteria'] = self.certificate_criteria
            return environ

        sites.ApplicationContext.get_environ = get_environ_new
    def test_unenroll_commanded_only_unenrolls_student(self):
        # Register user with profile enabled, so as to trigger call to
        # sites.get_course_for_current_request() when profile is updated
        # from lifecycle queue callback handler.
        user = actions.login(self.STUDENT_EMAIL)
        actions.register(self, self.STUDENT_EMAIL)

        # Verify user is really there.
        with common_utils.Namespace(self.NAMESPACE):
            self.assertIsNotNone(models.Student.get_by_user_id(user.user_id()))

            # Add taskqueue task so that queue callback happens w/o 'self.base'
            # being added to the URL and implicitly getting the course context
            # set.
            task = taskqueue.Task(
                params={
                    'event':
                    models.StudentLifecycleObserver.EVENT_UNENROLL_COMMANDED,
                    'user_id': user.user_id(),
                    'timestamp': '2015-05-14T10:02:09.758704Z',
                    'callbacks': appengine_config.CORE_MODULE_NAME
                },
                target=taskqueue.DEFAULT_APP_VERSION)
            task.add('user-lifecycle')

            # Taskqueue add should not have updated student.
            student = models.Student.get_by_user_id(user.user_id())
            self.assertTrue(student.is_enrolled)

            self.execute_all_deferred_tasks(
                models.StudentLifecycleObserver.QUEUE_NAME)

            # User should still be there, but now marked unenrolled.
            student = models.Student.get_by_user_id(user.user_id())
            self.assertFalse(student.is_enrolled)
Beispiel #7
0
    def test_unenroll_commanded_with_delete_requested(self):
        user = actions.login(self.STUDENT_EMAIL)
        actions.register(self, self.STUDENT_EMAIL, course=self.COURSE)

        # Verify user is really there.
        with common_utils.Namespace(self.NAMESPACE):
            self.assertIsNotNone(models.Student.get_by_user_id(user.user_id()))

            # Mark user for data deletion upon unenroll
            removal_models.ImmediateRemovalState.set_deletion_pending(
                user.user_id())

            response = self.post(
                models.StudentLifecycleObserver.URL,
                {'user_id': user.user_id(),
                 'event':
                     models.StudentLifecycleObserver.EVENT_UNENROLL_COMMANDED,
                 'timestamp': '2015-05-14T10:02:09.758704Z',
                 'callbacks': appengine_config.CORE_MODULE_NAME},
                headers={'X-AppEngine-QueueName':
                         models.StudentLifecycleObserver.QUEUE_NAME})
            self.assertEquals(response.status_int, 200)
            self.assertEquals('', self.get_log())

            # User should still be there, but now marked unenrolled.
            student = models.Student.get_by_user_id(user.user_id())
            self.assertFalse(student.is_enrolled)

            # Running lifecycle queue should cause data removal to delete user.
            self.execute_all_deferred_tasks(
                models.StudentLifecycleObserver.QUEUE_NAME)

            # User should now be gone.
            self.assertIsNone(models.Student.get_by_user_id(user.user_id()))
    def test_announcement_i18n_title(self):
        locale = 'de'
        announcement = self._add_announcement_and_translation(locale)
        actions.login('*****@*****.**')
        actions.register(self, 'John Doe')

        # Verify that one-off title translation also works.
        try:
            sites.set_path_info('/' + self.COURSE)
            ctx = sites.get_course_for_current_request()
            save_locale = ctx.get_current_locale()
            key = announcements.TranslatableResourceAnnouncement.key_for_entity(
                announcement)

            # Untranslated
            ctx.set_current_locale(None)
            i18n_title = str(
                announcements.TranslatableResourceAnnouncement.get_i18n_title(
                    key))
            self.assertEquals('Test Announcement', i18n_title)

            # Translated
            ctx.set_current_locale(locale)
            i18n_title = str(
                announcements.TranslatableResourceAnnouncement.get_i18n_title(
                    key))
            self.assertEquals('TEST ANNOUNCEMENT', i18n_title)
        finally:
            ctx.set_current_locale(save_locale)
            sites.unset_path_info()
Beispiel #9
0
    def test_multiple_students(self):
        # Register two students
        user = actions.login(self.STUDENT_EMAIL)
        actions.register(self, user.email(), course=self.COURSE)

        other_user = actions.login('*****@*****.**')
        actions.register(self, other_user.email(), course=self.COURSE)

        # Get IDs of those students; make an event for each.
        with common_utils.Namespace(self.NAMESPACE):
            student1_id = (
                models.Student.get_by_user(user).user_id)
            student2_id = (
                models.Student.get_by_user(other_user).user_id)
            models.EventEntity(user_id=student1_id, source='test').put()
            models.EventEntity(user_id=student2_id, source='test').put()

        # Unregister one of them.
        actions.login(self.STUDENT_EMAIL)
        self._unregister_and_request_data_removal(self.COURSE)
        self._complete_removal()

        # Unregistered student and his data are gone; still-registered
        # student's data is still present.
        with common_utils.Namespace(self.NAMESPACE):
            self.assertIsNone(models.Student.get_by_user(user))
            self.assertIsNotNone(models.Student.get_by_user(other_user))
            entities = list(models.EventEntity.all().run())
            self.assertEquals(1, len(entities))
            self.assertEquals(student2_id, entities[0].user_id)
Beispiel #10
0
    def test_multiple_courses(self):
        COURSE_TWO = 'course_two'
        COURSE_TWO_NS = 'ns_' + COURSE_TWO

        # Slight cheat: Register gitkit data remover manually, rather than
        # enabling the entire module, which disrupts normal functional test
        # user login handling
        gitkit.EmailMapping.register_for_data_removal()

        actions.simple_add_course(
            COURSE_TWO, self.ADMIN_EMAIL, 'Data Removal Test Two')
        user = actions.login(self.STUDENT_EMAIL)

        actions.register(self, user.email(), course=self.COURSE)
        actions.register(self, user.email(), course=COURSE_TWO)
        # Slight cheat: Rather than enabling gitkit module, just call
        # the method that will insert the EmailMapping row.
        gitkit.EmailUpdatePolicy.apply(user)

        # Global profile object(s) should now exist.
        profile = models.StudentProfileDAO.get_profile_by_user_id(
            user.user_id())
        self.assertIsNotNone(profile)
        email_policy = gitkit.EmailMapping.get_by_user_id(user.user_id())
        self.assertIsNotNone(email_policy)

        # Unregister from 'data_removal_test' course.
        self._unregister_and_request_data_removal(self.COURSE)
        self._complete_removal()

        # Student object should be gone from data_removal_test course, but
        # not from course_two.
        with common_utils.Namespace(self.NAMESPACE):
            self.assertIsNone(models.Student.get_by_user(user))
        with common_utils.Namespace(COURSE_TWO_NS):
            self.assertIsNotNone(models.Student.get_by_user(user))

        # Global profile object(s) should still exist.
        profile = models.StudentProfileDAO.get_profile_by_user_id(
            user.user_id())
        self.assertIsNotNone(profile)
        email_policy = gitkit.EmailMapping.get_by_user_id(user.user_id())
        self.assertIsNotNone(email_policy)

        # Unregister from other course.
        self._unregister_and_request_data_removal(COURSE_TWO)
        self._complete_removal()

        # Both Student objects should now be gone.
        with common_utils.Namespace(self.NAMESPACE):
            self.assertIsNone(models.Student.get_by_user(user))
        with common_utils.Namespace(COURSE_TWO_NS):
            self.assertIsNone(models.Student.get_by_user(user))

        # Global profile object(s) should also be gone.
        profile = models.StudentProfileDAO.get_profile_by_user_id(
            user.user_id())
        self.assertIsNone(profile)
        email_policy = gitkit.EmailMapping.get_by_user_id(user.user_id())
        self.assertIsNone(email_policy)
    def setUp(self):
        super(ManualProgressTest, self).setUp()

        # Add a course that will show up.
        context = actions.simple_add_course(COURSE_NAME, ADMIN_EMAIL, COURSE_TITLE)

        # Register a student for that course.
        actions.login(REGISTERED_STUDENT_EMAIL)
        actions.register(self, REGISTERED_STUDENT_NAME, COURSE_NAME)

        # Add content to course
        self._course = courses.Course(None, context)

        self._unit_one = self._course.add_unit()
        self._unit_one.title = "Unit Labels: Foo"
        self._unit_one.now_available = True
        self._lesson_1_1 = self._course.add_lesson(self._unit_one)
        self._lesson_1_1.title = "Unit One, Lesson One"
        self._lesson_1_1.now_available = True
        self._lesson_1_1.manual_progress = True
        self._lesson_1_2 = self._course.add_lesson(self._unit_one)
        self._lesson_1_2.title = "Unit One, Lesson Two"
        self._lesson_1_2.now_available = True
        self._lesson_1_2.manual_progress = True

        self._unit_two = self._course.add_unit()
        self._unit_two.title = "Unit Labels: Foo"
        self._unit_two.now_available = True
        self._unit_two.manual_progress = True
        self._lesson_2_1 = self._course.add_lesson(self._unit_two)
        self._lesson_2_1.title = "Unit Two, Lesson One"
        self._lesson_2_1.now_available = True
        self._lesson_2_2 = self._course.add_lesson(self._unit_two)
        self._lesson_2_2.title = "Unit Two, Lesson Two"
        self._lesson_2_2.now_available = True

        self._sub_assessment = self._course.add_assessment()
        self._sub_assessment.now_available = True

        self._toplevel_assessment = self._course.add_assessment()
        self._sub_assessment.now_available = True

        self._unit_three = self._course.add_unit()
        self._unit_three.pre_assessment = self._sub_assessment.unit_id

        self._course.save()

        with common_utils.Namespace(NAMESPACE):
            self.foo_id = models.LabelDAO.save(
                models.LabelDTO(
                    None, {"title": "Foo", "descripton": "foo", "type": models.LabelDTO.LABEL_TYPE_COURSE_TRACK}
                )
            )
            self.bar_id = models.LabelDAO.save(
                models.LabelDTO(
                    None, {"title": "Bar", "descripton": "bar", "type": models.LabelDTO.LABEL_TYPE_COURSE_TRACK}
                )
            )

        config.Registry.test_overrides[utils.CAN_PERSIST_ACTIVITY_EVENTS.name] = True
Beispiel #12
0
    def test_enrollment(self):
        actions.logout()

        response = self.get_response(
            '{course(id: "%s") {enrollment {email enrolled}}}' % (
                self.course_id))
        enrollment = response['data']['course']['enrollment']
        self.assertEquals({'enrolled': False, 'email': None}, enrollment)

        actions.login(STUDENT_EMAIL)

        response = self.get_response(
            '{course(id: "%s") {enrollment {email enrolled}}}' % (
                self.course_id))
        enrollment = response['data']['course']['enrollment']
        self.assertEquals({'enrolled': False, 'email': None}, enrollment)

        actions.register(self, STUDENT_NAME)

        response = self.get_response(
            '{course (id: "%s") { enrollment { email enrolled}}}' % (
                self.course_id))
        enrollment = response['data']['course']['enrollment']
        self.assertEquals(
            {'enrolled': True, 'email': STUDENT_EMAIL}, enrollment)
Beispiel #13
0
    def test_notifications_succeed(self):
        actions.login(self.STUDENT_EMAIL)
        user_id = None

        actions.register(self, self.STUDENT_EMAIL)
        self.assertIsNone(self._user_id)
        self.execute_all_deferred_tasks(
            models.StudentLifecycleObserver.QUEUE_NAME)
        self.assertIsNotNone(self._user_id)
        user_id = self._user_id
        self.assertEquals(1, self._num_add_calls)
        self.assertEquals(0, self._num_unenroll_calls)
        self.assertEquals(0, self._num_reenroll_calls)

        actions.unregister(self)
        self.execute_all_deferred_tasks(
            models.StudentLifecycleObserver.QUEUE_NAME)
        self.assertEquals(1, self._num_add_calls)
        self.assertEquals(1, self._num_unenroll_calls)
        self.assertEquals(0, self._num_reenroll_calls)


        with common_utils.Namespace(self.NAMESPACE):
            models.StudentProfileDAO.update(
                user_id, self.STUDENT_EMAIL, is_enrolled=True)
        self.execute_all_deferred_tasks(
            models.StudentLifecycleObserver.QUEUE_NAME)
        self.assertEquals(1, self._num_add_calls)
        self.assertEquals(1, self._num_unenroll_calls)
        self.assertEquals(1, self._num_reenroll_calls)
    def setUp(self):
        super(PeerReviewDashboardStudentTest, self).setUp()
        self.base = '/' + self.COURSE_NAME
        context = actions.simple_add_course(
            self.COURSE_NAME, '*****@*****.**', 'Peer Back Button Child')
        self.course = courses.Course(None, context)

        self.assessment = self.course.add_assessment()
        self.assessment.title = 'Assessment'
        self.assessment.html_content = 'assessment content'
        self.assessment.workflow_yaml = (
            '{grader: human,'
            'matcher: peer,'
            'review_due_date: \'2034-07-01 12:00\','
            'review_min_count: 1,'
            'review_window_mins: 20,'
            'submission_due_date: \'2034-07-01 12:00\'}')
        self.assessment.availability = courses.AVAILABILITY_AVAILABLE

        self.course.save()
        actions.login(self.STUDENT_EMAIL)
        actions.register(self, self.STUDENT_EMAIL)

        actions.submit_assessment(
            self,
            self.assessment.unit_id,
            {'answers': '', 'score': 0,
             'assessment_type': self.assessment.unit_id},
            presubmit_checks=False
        )
Beispiel #15
0
 def test_delete_link_when_registered_then_cancel_deletion(self):
     actions.login(self.STUDENT_EMAIL)
     actions.register(self, self.STUDENT_EMAIL)
     response = self.get('course')
     response = self.click(response, 'Delete My Data')
     self._unregister_flow(response, with_deletion_checked=True,
                           cancel_on_deletion=True)
Beispiel #16
0
    def test_notifications_succeed(self):
        actions.login(self.STUDENT_EMAIL)
        user_id = None

        actions.register(self, self.STUDENT_EMAIL)
        self.assertIsNone(self._user_id)
        self.execute_all_deferred_tasks(
            models.StudentLifecycleObserver.QUEUE_NAME)
        self.assertIsNotNone(self._user_id)
        user_id = self._user_id
        self.assertEquals(1, self._num_add_calls)
        self.assertEquals(0, self._num_unenroll_calls)
        self.assertEquals(0, self._num_reenroll_calls)

        actions.unregister(self)
        self.execute_all_deferred_tasks(
            models.StudentLifecycleObserver.QUEUE_NAME)
        self.assertEquals(1, self._num_add_calls)
        self.assertEquals(1, self._num_unenroll_calls)
        self.assertEquals(0, self._num_reenroll_calls)

        with common_utils.Namespace(self.NAMESPACE):
            models.StudentProfileDAO.update(user_id,
                                            self.STUDENT_EMAIL,
                                            is_enrolled=True)
        self.execute_all_deferred_tasks(
            models.StudentLifecycleObserver.QUEUE_NAME)
        self.assertEquals(1, self._num_add_calls)
        self.assertEquals(1, self._num_unenroll_calls)
        self.assertEquals(1, self._num_reenroll_calls)
Beispiel #17
0
    def test_multiple_course(self):
        """Tests when multiple courses are available."""
        sites.setup_courses('course:/test::ns_test, course:/:/')
        name = 'Test completed course'
        email = 'Student'

        # Make the course available.
        get_environ_old = sites.ApplicationContext.get_environ

        def get_environ_new(self):
            environ = get_environ_old(self)
            environ['course']['now_available'] = True
            return environ

        sites.ApplicationContext.get_environ = get_environ_new

        actions.login(email)
        actions.register(self, name)
        response = self.get('/explorer/courses')
        # Assert if 'View course list' text is shown on my course page.
        actions.assert_contains('View course list', response.body)

        # Clean up app_context.
        sites.ApplicationContext.get_environ = get_environ_old
        sites.reset_courses()
    def test_jobs_run(self):
        COURSE = 'test'
        app_context = actions.simple_add_course(COURSE, ADMIN_EMAIL, 'Test')
        actions.register(self, 'Joe Admin', COURSE)
        config.set_report_allowed(True)
        response = self.get(usage_reporting.StartReportingJobs.URL,
                            headers={'X-AppEngine-Cron': 'True'})
        self.assertEquals(200, response.status_int)
        self.assertEquals('OK.', response.body)
        now = int(time.time())
        self.execute_all_deferred_tasks(
            models.StudentLifecycleObserver.QUEUE_NAME)
        self.execute_all_deferred_tasks()

        expected = [{
            messaging.Message._INSTALLATION: FAKE_INSTALLATION_ID,
            messaging.Message._COURSE: FAKE_COURSE_ID,
            messaging.Message._TIMESTAMP: FAKE_TIMESTAMP,
            messaging.Message._VERSION: os.environ['GCB_PRODUCT_VERSION'],
            messaging.Message._METRIC: messaging.Message.METRIC_STUDENT_COUNT,
            messaging.Message._VALUE: 1,
        }, {
            messaging.Message._INSTALLATION: FAKE_INSTALLATION_ID,
            messaging.Message._COURSE: FAKE_COURSE_ID,
            messaging.Message._TIMESTAMP: now - (now % 3600),
            messaging.Message._VERSION: os.environ['GCB_PRODUCT_VERSION'],
            messaging.Message._METRIC: messaging.Message.METRIC_ENROLLED,
            messaging.Message._VALUE: 1,
        }]
        actual = MockSender.get_sent()
        actual.sort(key=lambda x: x['timestamp'])
        self.assertEquals(expected, actual)
        sites.reset_courses()
    def test_announcement_i18n_title(self):
        locale = 'de'
        announcement = self._add_announcement_and_translation(locale)
        actions.login('*****@*****.**')
        actions.register(self, 'John Doe')

        # Verify that one-off title translation also works.
        try:
            sites.set_path_info('/' + self.COURSE)
            ctx = sites.get_course_for_current_request()
            save_locale = ctx.get_current_locale()
            key = announcements.TranslatableResourceAnnouncement.key_for_entity(
                announcement)

            # Untranslated
            ctx.set_current_locale(None)
            i18n_title = str(
                announcements.TranslatableResourceAnnouncement.get_i18n_title(
                    key))
            self.assertEquals('Test Announcement', i18n_title)

            # Translated
            ctx.set_current_locale(locale)
            i18n_title = str(
                announcements.TranslatableResourceAnnouncement.get_i18n_title(
                    key))
            self.assertEquals('TEST ANNOUNCEMENT', i18n_title)
        finally:
            ctx.set_current_locale(save_locale)
            sites.unset_path_info()
Beispiel #20
0
    def test_get_news_some_old_some_new(self):
        user = actions.login(self.STUDENT_EMAIL)
        actions.register(self, 'John Smith')

        # Newsworthy thing happened beyond newsworthy time limit,
        then_ts = utc.now_as_timestamp() - news.NEWSWORTHINESS_SECONDS - 1
        then = utc.timestamp_to_datetime(then_ts)
        self._set_student_enroll_date(user, then)
        news_item = news.NewsItem('test:one', 'url_one', then)
        news.CourseNewsDao.add_news_item(news_item)
        news_item = news.NewsItem('test:two', 'url_two', then)
        news.CourseNewsDao.add_news_item(news_item)
        # But student has seen the thing, so it's marked as non-new.
        news.StudentNewsDao.mark_item_seen('test:one')

        response = self.get('course')
        soup = self.parse_html_string_to_soup(response.body)
        self.assertEquals(['has_new_news'],
                          self._get_news_title_styles(soup))
        self.assertEquals(
            [
                news_tests_lib.NewsItem('Test Item two', 'url_two', True),
                news_tests_lib.NewsItem('Test Item one', 'url_one', False),
            ],
            news_tests_lib.extract_news_items_from_soup(soup))
    def test_unenroll_commanded_with_delete_requested(self):
        user = actions.login(self.STUDENT_EMAIL)
        actions.register(self, self.STUDENT_EMAIL, course=self.COURSE)

        # Verify user is really there.
        with common_utils.Namespace(self.NAMESPACE):
            self.assertIsNotNone(models.Student.get_by_user_id(user.user_id()))

            # Mark user for data deletion upon unenroll
            removal_models.ImmediateRemovalState.set_deletion_pending(
                user.user_id())

            response = self.post(
                models.StudentLifecycleObserver.URL,
                {'user_id': user.user_id(),
                 'event':
                     models.StudentLifecycleObserver.EVENT_UNENROLL_COMMANDED,
                 'timestamp': '2015-05-14T10:02:09.758704Z',
                 'callbacks': appengine_config.CORE_MODULE_NAME},
                headers={'X-AppEngine-QueueName':
                         models.StudentLifecycleObserver.QUEUE_NAME})
            self.assertEquals(response.status_int, 200)
            self.assertEquals('', self.get_log())

            # User should still be there, but now marked unenrolled.
            student = models.Student.get_by_user_id(user.user_id())
            self.assertFalse(student.is_enrolled)

            # Running lifecycle queue should cause data removal to delete user.
            self.execute_all_deferred_tasks(
                models.StudentLifecycleObserver.QUEUE_NAME)

            # User should now be gone.
            self.assertIsNone(models.Student.get_by_user_id(user.user_id()))
    def setUp(self):
        super(StudentRedirectTestBase, self).setUp()
        context = actions.simple_add_course(COURSE_NAME, ADMIN_EMAIL,
                                            COURSE_TITLE)
        course = courses.Course(None, context)
        self.unit = course.add_unit()
        self.unit.title = 'The Unit'
        self.unit.now_available = True
        self.lesson_one = course.add_lesson(self.unit)
        self.lesson_one.title = 'Lesson One'
        self.lesson_one.now_available = True
        self.lesson_two = course.add_lesson(self.unit)
        self.lesson_two.title = 'Lesson Two'
        self.lesson_two.now_available = True
        self.assessment = course.add_assessment()
        self.assessment.title = 'The Assessment'
        self.assessment.now_available = True
        course.save()

        actions.login(REGISTERED_STUDENT_EMAIL)
        actions.register(self, REGISTERED_STUDENT_NAME, COURSE_NAME)
        # Actions.register views the student's profile page; clear this out.
        with common_utils.Namespace(NAMESPACE):
            prefs = models.StudentPreferencesDAO.load_or_default()
            prefs.last_location = None
            models.StudentPreferencesDAO.save(prefs)
    def test_remove_by_email(self):
        user = actions.login(self.STUDENT_EMAIL)
        actions.register(self, user.email(), course=self.COURSE)

        with common_utils.Namespace(self.NAMESPACE):
            sse = unsubscribe.SubscriptionStateEntity(
                key_name=user.email())
            sse.is_subscribed = True
            sse.save()

            notifications.Manager.send_async(
                user.email(), self.ADMIN_EMAIL, 'testemail',
                'Mary had a little lamb.  She fed it beans and buns.',
                'Pets for Mary', '{"audit_trail": "yes"}',
                retention_policy=notifications.RetainAll)
            # Finish deferred tasks so notifications subsystem would have
            # deleted items if it were going to.  It shouldn't based on our
            # use of RetainAll above, but belt-and-suspenders.
            self.execute_all_deferred_tasks()
            l = list(notifications.Notification.all().run())
            self.assertEquals(1, len(l))
            l = list(notifications.Payload.all().run())
            self.assertEquals(1, len(l))

        self._unregister_and_request_data_removal(self.COURSE)
        self._complete_removal()

        with common_utils.Namespace(self.NAMESPACE):
            l = list(unsubscribe.SubscriptionStateEntity.all().run())
            self.assertEquals(0, len(l))
            l = list(notifications.Notification.all().run())
            self.assertEquals(0, len(l))
            l = list(notifications.Payload.all().run())
            self.assertEquals(0, len(l))
    def setUp(self):
        super(CertificateCriteriaTestCase, self).setUp()
        self.base = '/' + self.COURSE_NAME
        context = actions.simple_add_course(
            self.COURSE_NAME, self.ADMIN_EMAIL, 'Certificate Criteria')

        self.old_namespace = namespace_manager.get_namespace()
        namespace_manager.set_namespace('ns_%s' % self.COURSE_NAME)

        self.course = courses.Course(None, context)
        self.course.save()
        self.TEST_USER = actions.login('*****@*****.**')
        actions.register(self, self.TEST_USER.email())
        self.student = (
            models.StudentProfileDAO.get_enrolled_student_by_user_for(
                self.TEST_USER, context))

        # Override course.yaml settings by patching app_context.
        self.get_environ_old = sites.ApplicationContext.get_environ
        self.certificate_criteria = []

        def get_environ_new(app_context):
            environ = self.get_environ_old(app_context)
            environ['certificate_criteria'] = self.certificate_criteria
            return environ

        sites.ApplicationContext.get_environ = get_environ_new
    def setUp(self):
        super(StudentLabelsTest, self).setUp()
        actions.simple_add_course(COURSE_NAME, ADMIN_EMAIL, COURSE_TITLE)

        with common_utils.Namespace(NAMESPACE):
            self.foo_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Foo',
                       'descripton': 'foo',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))
            self.bar_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Bar',
                       'descripton': 'bar',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))
            self.baz_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Baz',
                       'descripton': 'baz',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))
            self.quux_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Quux',
                       'descripton': 'quux',
                       'type': models.LabelDTO.LABEL_TYPE_GENERAL}))

        actions.login(REGISTERED_STUDENT_EMAIL)
        actions.register(self, REGISTERED_STUDENT_NAME, COURSE_NAME)
        actions.logout()
 def test_delete_link_when_registered_then_cancel_deletion(self):
     actions.login(self.STUDENT_EMAIL)
     actions.register(self, self.STUDENT_EMAIL)
     response = self.get('course')
     response = self.click(response, 'Delete My Data')
     self._unregister_flow(response, with_deletion_checked=True,
                           cancel_on_deletion=True)
    def test_single_completed_course(self):
        """Tests when a single completed course is present."""
        name = 'Test Assessments'

        # Register.
        user = actions.login('*****@*****.**')
        actions.register(self, name)

        response = self.get('/explorer')
        # Before a course is not completed,
        # explorer page should not show 'view score' button.
        actions.assert_does_not_contain('View score', response.body)

        # Assign a grade to the course enrolled to mark it complete.
        profile = PersonalProfile.get_by_key_name(user.user_id())
        info = {'final_grade': 'A'}
        course_info_dict = {'': info}
        profile.course_info = transforms.dumps(course_info_dict)
        profile.put()

        # Check if 'View score' text is visible on profile page.
        response = self.get('/explorer/profile')
        actions.assert_contains('View score', response.body)

        # Check if 'Go to course' button is not visible on explorer page.
        response = self.get('/explorer')
        actions.assert_does_not_contain('Go to course', response.body)

        # Check if 'View score' button is visible on explorer page.
        response = self.get('/explorer')
        actions.assert_contains('View score', response.body)
Beispiel #28
0
    def setUp(self):
        super(StudentLabelsTest, self).setUp()
        actions.simple_add_course(COURSE_NAME, ADMIN_EMAIL, COURSE_TITLE)

        with common_utils.Namespace(NAMESPACE):
            self.foo_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Foo',
                       'descripton': 'foo',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))
            self.bar_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Bar',
                       'descripton': 'bar',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))
            self.baz_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Baz',
                       'descripton': 'baz',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))
            self.quux_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Quux',
                       'descripton': 'quux',
                       'type': models.LabelDTO.LABEL_TYPE_GENERAL}))

        actions.login(REGISTERED_STUDENT_EMAIL)
        actions.register(self, REGISTERED_STUDENT_NAME, COURSE_NAME)
        actions.logout()
    def test_multiple_students(self):
        # Register two students
        user = actions.login(self.STUDENT_EMAIL)
        actions.register(self, user.email(), course=self.COURSE)

        other_user = actions.login('*****@*****.**')
        actions.register(self, other_user.email(), course=self.COURSE)

        # Get IDs of those students; make an event for each.
        with common_utils.Namespace(self.NAMESPACE):
            student1_id = (
                models.Student.get_by_user(user).user_id)
            student2_id = (
                models.Student.get_by_user(other_user).user_id)
            models.EventEntity(user_id=student1_id, source='test').put()
            models.EventEntity(user_id=student2_id, source='test').put()

        # Unregister one of them.
        actions.login(self.STUDENT_EMAIL)
        self._unregister_and_request_data_removal(self.COURSE)
        self._complete_removal()

        # Unregistered student and his data are gone; still-registered
        # student's data is still present.
        with common_utils.Namespace(self.NAMESPACE):
            self.assertIsNone(models.Student.get_by_user(user))
            self.assertIsNotNone(models.Student.get_by_user(other_user))
            entities = list(models.EventEntity.all().run())
            self.assertEquals(1, len(entities))
            self.assertEquals(student2_id, entities[0].user_id)
Beispiel #30
0
    def test_single_completed_course(self):
        """Tests when a single completed course is present."""
        name = 'Test Assessments'

        # Register.
        user = actions.login('*****@*****.**')
        actions.register(self, name)

        response = self.get('/explorer')
        # Before a course is not completed,
        # explorer page should not show 'view score' button.
        actions.assert_does_not_contain('View score', response.body)

        # Assign a grade to the course enrolled to mark it complete.
        profile = PersonalProfile.get_by_key_name(user.user_id())
        info = {'final_grade': 'A'}
        course_info_dict = {'': info}
        profile.course_info = transforms.dumps(course_info_dict)
        profile.put()

        # Check if 'View score' text is visible on profile page.
        response = self.get('/explorer/profile')
        actions.assert_contains('View score', response.body)

        # Check if 'Go to course' button is not visible on explorer page.
        response = self.get('/explorer')
        actions.assert_does_not_contain('Go to course', response.body)

        # Check if 'View score' button is visible on explorer page.
        response = self.get('/explorer')
        actions.assert_contains('View score', response.body)
    def test_multiple_courses(self):
        user = self.make_test_user(self.STUDENT_EMAIL)

        COURSE_TWO = 'course_two'
        COURSE_TWO_NS = 'ns_' + COURSE_TWO

        actions.simple_add_course(
            COURSE_TWO, self.ADMIN_EMAIL, 'Data Removal Test Two')

        actions.login(user.email())
        actions.register(self, user.email(), course=self.COURSE)
        actions.register(self, user.email(), course=COURSE_TWO)
        actions.unregister(self, self.COURSE, do_data_removal=True)

        self.execute_all_deferred_tasks(
            models.StudentLifecycleObserver.QUEUE_NAME)
        self.get(
            data_removal.DataRemovalCronHandler.URL,
            headers={'X-AppEngine-Cron': 'True'})
        self.execute_all_deferred_tasks()

        with common_utils.Namespace(self.NAMESPACE):
            self.assertIsNone(models.Student.get_by_user(user))
        with common_utils.Namespace(COURSE_TWO_NS):
            self.assertIsNotNone(
                models.Student.get_by_user(user))
    def test_multiple_courses(self):
        COURSE_TWO = 'course_two'
        COURSE_TWO_NS = 'ns_' + COURSE_TWO

        # Slight cheat: Register gitkit data remover manually, rather than
        # enabling the entire module, which disrupts normal functional test
        # user login handling
        gitkit.EmailMapping.register_for_data_removal()

        actions.simple_add_course(
            COURSE_TWO, self.ADMIN_EMAIL, 'Data Removal Test Two')
        user = actions.login(self.STUDENT_EMAIL)

        actions.register(self, user.email(), course=self.COURSE)
        actions.register(self, user.email(), course=COURSE_TWO)
        # Slight cheat: Rather than enabling gitkit module, just call
        # the method that will insert the EmailMapping row.
        gitkit.EmailUpdatePolicy.apply(user)

        # Global profile object(s) should now exist.
        profile = models.StudentProfileDAO.get_profile_by_user_id(
            user.user_id())
        self.assertIsNotNone(profile)
        email_policy = gitkit.EmailMapping.get_by_user_id(user.user_id())
        self.assertIsNotNone(email_policy)

        # Unregister from 'data_removal_test' course.
        self._unregister_and_request_data_removal(self.COURSE)
        self._complete_removal()

        # Student object should be gone from data_removal_test course, but
        # not from course_two.
        with common_utils.Namespace(self.NAMESPACE):
            self.assertIsNone(models.Student.get_by_user(user))
        with common_utils.Namespace(COURSE_TWO_NS):
            self.assertIsNotNone(models.Student.get_by_user(user))

        # Global profile object(s) should still exist.
        profile = models.StudentProfileDAO.get_profile_by_user_id(
            user.user_id())
        self.assertIsNotNone(profile)
        email_policy = gitkit.EmailMapping.get_by_user_id(user.user_id())
        self.assertIsNotNone(email_policy)

        # Unregister from other course.
        self._unregister_and_request_data_removal(COURSE_TWO)
        self._complete_removal()

        # Both Student objects should now be gone.
        with common_utils.Namespace(self.NAMESPACE):
            self.assertIsNone(models.Student.get_by_user(user))
        with common_utils.Namespace(COURSE_TWO_NS):
            self.assertIsNone(models.Student.get_by_user(user))

        # Global profile object(s) should also be gone.
        profile = models.StudentProfileDAO.get_profile_by_user_id(
            user.user_id())
        self.assertIsNone(profile)
        email_policy = gitkit.EmailMapping.get_by_user_id(user.user_id())
        self.assertIsNone(email_policy)
    def test_multiple_course(self):
        """Tests when multiple courses are available."""
        sites.setup_courses('course:/test::ns_test, course:/:/')
        name = 'Test completed course'
        email = 'Student'

        # Make the course available.
        get_environ_old = sites.ApplicationContext.get_environ

        def get_environ_new(self):
            environ = get_environ_old(self)
            environ['course']['now_available'] = True
            return environ

        sites.ApplicationContext.get_environ = get_environ_new

        actions.login(email)
        actions.register(self, name)
        response = self.get('/explorer/courses')
        # Assert if 'View course list' text is shown on my course page.
        actions.assert_contains('View course list', response.body)

        # Clean up app_context.
        sites.ApplicationContext.get_environ = get_environ_old
        sites.reset_courses()
Beispiel #34
0
    def test_jobs_run(self):
        COURSE = 'test'
        app_context = actions.simple_add_course(COURSE, ADMIN_EMAIL, 'Test')
        actions.register(self, 'Joe Admin', COURSE)
        config.set_report_allowed(True)
        response = self.get(usage_reporting.StartReportingJobs.URL,
                            headers={'X-AppEngine-Cron': 'True'})
        self.assertEquals(200, response.status_int)
        self.assertEquals('OK.', response.body)
        now = int(time.time())
        self.execute_all_deferred_tasks(
            models.StudentLifecycleObserver.QUEUE_NAME)
        self.execute_all_deferred_tasks()

        expected = [{
            messaging.Message._INSTALLATION: FAKE_INSTALLATION_ID,
            messaging.Message._COURSE: FAKE_COURSE_ID,
            messaging.Message._TIMESTAMP: FAKE_TIMESTAMP,
            messaging.Message._VERSION: os.environ['GCB_PRODUCT_VERSION'],
            messaging.Message._METRIC: messaging.Message.METRIC_STUDENT_COUNT,
            messaging.Message._VALUE: 1,
        }, {
            messaging.Message._INSTALLATION: FAKE_INSTALLATION_ID,
            messaging.Message._COURSE: FAKE_COURSE_ID,
            messaging.Message._TIMESTAMP: now - (now % 3600),
            messaging.Message._VERSION: os.environ['GCB_PRODUCT_VERSION'],
            messaging.Message._METRIC: messaging.Message.METRIC_ENROLLED,
            messaging.Message._VALUE: 1,
        }]
        actual = MockSender.get_sent()
        actual.sort(key=lambda x: x['timestamp'])
        self.assertEquals(expected, actual)
        sites.reset_courses()
Beispiel #35
0
    def test_enrollment(self):
        actions.logout()

        response = self.get_response(
            '{course(id: "%s") {enrollment {email enrolled}}}' % (
                self.course_id))
        enrollment = response['data']['course']['enrollment']
        self.assertEquals({'enrolled': False, 'email': None}, enrollment)

        actions.login(STUDENT_EMAIL)

        response = self.get_response(
            '{course(id: "%s") {enrollment {email enrolled}}}' % (
                self.course_id))
        enrollment = response['data']['course']['enrollment']
        self.assertEquals({'enrolled': False, 'email': None}, enrollment)

        actions.register(self, STUDENT_NAME)

        response = self.get_response(
            '{course (id: "%s") { enrollment { email enrolled}}}' % (
                self.course_id))
        enrollment = response['data']['course']['enrollment']
        self.assertEquals(
            {'enrolled': True, 'email': STUDENT_EMAIL}, enrollment)
    def setUp(self):
        super(PeerReviewDashboardStudentTest, self).setUp()
        self.base = '/' + self.COURSE_NAME
        context = actions.simple_add_course(self.COURSE_NAME, '*****@*****.**',
                                            'Peer Back Button Child')
        self.course = courses.Course(None, context)

        self.assessment = self.course.add_assessment()
        self.assessment.title = 'Assessment'
        self.assessment.html_content = 'assessment content'
        self.assessment.workflow_yaml = (
            '{grader: human,'
            'matcher: peer,'
            'review_due_date: \'2034-07-01 12:00\','
            'review_min_count: 1,'
            'review_window_mins: 20,'
            'submission_due_date: \'2034-07-01 12:00\'}')
        self.assessment.availability = courses.AVAILABILITY_AVAILABLE

        self.course.save()
        actions.login(self.STUDENT_EMAIL)
        actions.register(self, self.STUDENT_EMAIL)

        actions.submit_assessment(
            self,
            self.assessment.unit_id, {
                'answers': '',
                'score': 0,
                'assessment_type': self.assessment.unit_id
            },
            presubmit_checks=False)
    def setUp(self):
        super(ExtraTabsTests, self).setUp()

        self.base = '/' + COURSE_NAME
        app_context = actions.simple_add_course(
            COURSE_NAME, ADMIN_EMAIL, 'Extra Tabs Course')
        self.old_namespace = namespace_manager.get_namespace()
        namespace_manager.set_namespace('ns_%s' % COURSE_NAME)

        self.course = courses.Course(None, app_context)

        courses.Course.ENVIRON_TEST_OVERRIDES = {
            'course': {
                'extra_tabs': [
                    {
                        'label': 'FAQ',
                        'position': 'left',
                        'visibility': 'all',
                        'url': '',
                        'content': 'Frequently asked questions'},
                    {
                        'label': 'Resources',
                        'position': 'right',
                        'visibility': 'student',
                        'url': 'http://www.example.com',
                        'content': 'Links to resources'}]
            }
        }
        self.faq_url = 'modules/extra_tabs/render?index=0'

        actions.login(STUDENT_EMAIL, is_admin=False)
        actions.register(self, STUDENT_NAME)
    def setUp(self):
        super(ExtraTabsTests, self).setUp()

        self.base = '/' + COURSE_NAME
        app_context = actions.simple_add_course(
            COURSE_NAME, ADMIN_EMAIL, 'Extra Tabs Course')
        self.old_namespace = namespace_manager.get_namespace()
        namespace_manager.set_namespace('ns_%s' % COURSE_NAME)

        self.course = courses.Course(None, app_context)

        courses.Course.ENVIRON_TEST_OVERRIDES = {
            'course': {
                'extra_tabs': [
                    {
                        'label': 'FAQ',
                        'position': 'left',
                        'visibility': 'all',
                        'url': '',
                        'content': 'Frequently asked questions'},
                    {
                        'label': 'Resources',
                        'position': 'right',
                        'visibility': 'student',
                        'url': 'http://www.example.com',
                        'content': 'Links to resources'}]
            }
        }
        self.faq_url = 'modules/extra_tabs/render?index=0'

        actions.login(STUDENT_EMAIL, is_admin=False)
        actions.register(self, STUDENT_NAME)
    def test_reregistration_blocked_during_deletion(self):
        def assert_cannot_register():
            response = self.get('register')
            self.assertIn('You cannot re-register for this course',
                          response.body)
            self.assertNotIn('What is your name?', response.body)

        user_id = None
        user = actions.login(self.STUDENT_EMAIL)
        actions.register(self, user.email())
        with common_utils.Namespace(self.NAMESPACE):
            # After registration, we should have a student object, and
            # a ImmediateRemovalState instance.
            student = models.Student.get_by_user(user)
            self.assertIsNotNone(student)
            user_id = student.user_id

        self._unregister_and_request_data_removal(self.COURSE)

        # On submitting the unregister form, the user's ImmediateRemovalState
        # will have been marked as deltion-in-progress, and so user cannot
        # re-register yet.
        assert_cannot_register()

        # Run the queue to do the cleanup of indexed items, and add the
        # work-to-do items for batched cleanup.
        self.execute_all_deferred_tasks(
            models.StudentLifecycleObserver.QUEUE_NAME)
        assert_cannot_register()

        # Run the cron job that launches the map/reduce jobs to clean up
        # bulk items.  Still not able to re-register.
        self.get(data_removal.DataRemovalCronHandler.URL,
                 headers={'X-AppEngine-Cron': 'True'})
        assert_cannot_register()

        # Run the map/reduce jobs.  Bulk items should now be cleaned.
        self.execute_all_deferred_tasks()
        with common_utils.Namespace(self.NAMESPACE):
            student = models.Student.get_by_user(user)
            self.assertIsNone(student)
            removal_state = removal_models.ImmediateRemovalState.get_by_user_id(
                user_id)
            self.assertIsNotNone(removal_state)
        assert_cannot_register()

        # Run the cron job one more time.  When no bulk to-do items remain,
        # we then clean up the ImmediateRemovalState.  Re-registration should
        # now be possible.
        self.get(data_removal.DataRemovalCronHandler.URL,
                 headers={'X-AppEngine-Cron': 'True'})
        with common_utils.Namespace(self.NAMESPACE):
            student = models.Student.get_by_user(user)
            self.assertIsNone(student)
            removal_state = removal_models.ImmediateRemovalState.get_by_user_id(
                user_id)
            self.assertIsNone(removal_state)

        actions.register(self, self.STUDENT_EMAIL)
    def test_tracked_lessons(self):
        context = actions.simple_add_course('test', '*****@*****.**',
                                            'Test Course')
        course = courses.Course(None, context)
        actions.login('*****@*****.**')
        actions.register(self, 'Some Admin', 'test')

        with common_utils.Namespace('ns_test'):
            foo_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Foo',
                       'descripton': 'foo',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))
            bar_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Bar',
                       'descripton': 'bar',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))

        unit1 = course.add_unit()
        unit1.availability = courses.AVAILABILITY_AVAILABLE
        unit1.labels = str(foo_id)
        lesson11 = course.add_lesson(unit1)
        lesson11.objectives = 'common plugh <gcb-youtube videoid="glados">'
        lesson11.availability = courses.AVAILABILITY_AVAILABLE
        lesson11.notes = search_unit_tests.VALID_PAGE_URL
        lesson11.video = 'portal'
        course.update_unit(unit1)
        unit2 = course.add_unit()
        unit2.availability = courses.AVAILABILITY_AVAILABLE
        unit1.labels = str(bar_id)
        lesson21 = course.add_lesson(unit2)
        lesson21.objectives = 'common plover'
        lesson21.availability = courses.AVAILABILITY_AVAILABLE
        course.update_unit(unit2)
        course.save()
        self.index_test_course()

        # Registered, un-tracked student sees all.
        response = self.get('/test/search?query=common')
        self.assertIn('common', response.body)
        self.assertIn('plugh', response.body)
        self.assertIn('plover', response.body)
        response = self.get('/test/search?query=link')  # Do see followed links
        self.assertIn('Partial', response.body)
        self.assertIn('Absolute', response.body)
        response = self.get('/test/search?query=lemon')  # Do see video refs
        self.assertIn('v=glados', response.body)

        # Student with tracks sees filtered view.
        with common_utils.Namespace('ns_test'):
            models.Student.set_labels_for_current(str(foo_id))
        response = self.get('/test/search?query=common')
        self.assertIn('common', response.body)
        self.assertNotIn('plugh', response.body)
        self.assertIn('plover', response.body)
        response = self.get('/test/search?query=link')  # Links are filtered
        self.assertNotIn('Partial', response.body)
        self.assertNotIn('Absolute', response.body)
        response = self.get('/test/search?query=lemon')  # Don't see video refs
        self.assertNotIn('v=glados', response.body)
    def test_announcement_news(self):
        actions.login('*****@*****.**')
        actions.register(self, 'John Doe')
        time.sleep(1)
        locale = 'de'
        announcement = self._add_announcement_and_translation(
            locale, is_draft=True)
        sent_data = {
            'key': str(announcement.key()),
            'title': 'Test Announcement',
            'date': utc.to_text(seconds=utc.now_as_timestamp()),
            'is_draft': False,
        }
        actions.login(self.ADMIN_EMAIL)
        response = self._put_announcement(sent_data)
        actions.login('*****@*****.**')

        # Verify announcement news item using news API directly
        news_items = news.CourseNewsDao.get_news_items()
        self.assertEquals(1, len(news_items))
        item = news_items[0]
        now_timestamp = utc.now_as_timestamp()
        self.assertEquals(
            announcements.AnnouncementsStudentHandler.URL.lstrip('/'), item.url)
        self.assertEquals(
            str(announcements.TranslatableResourceAnnouncement.key_for_entity(
                announcement)),
            item.resource_key)
        self.assertAlmostEqual(
            now_timestamp, utc.datetime_to_timestamp(item.when), delta=10)

        # Verify announcement news item looking at HTTP response to /course
        response = self.get('course')
        soup = self.parse_html_string_to_soup(response.body)
        self.assertEquals(
            [news_tests_lib.NewsItem(
                'Test Announcement',
                announcements.AnnouncementsStudentHandler.URL.lstrip('/'),
                True)],
            news_tests_lib.extract_news_items_from_soup(soup))

        # Verify announcement news item translated title.
        self._set_prefs_locale(locale)
        response = self.get('course')
        soup = self.parse_html_string_to_soup(response.body)
        self.assertEquals(
            [news_tests_lib.NewsItem(
                'TEST ANNOUNCEMENT',
                announcements.AnnouncementsStudentHandler.URL.lstrip('/'),
                True)],
            news_tests_lib.extract_news_items_from_soup(soup))

        # Delete the announcement; news item should also go away.
        actions.login(self.ADMIN_EMAIL)
        self._delete_announcement(str(announcement.key()))
        actions.login('*****@*****.**')
        response = self.get('course')
        soup = self.parse_html_string_to_soup(response.body)
        self.assertEquals([], news_tests_lib.extract_news_items_from_soup(soup))
    def setUp(self):
        super(StudentTracksTest, self).setUp()

        # Add a course that will show up.
        actions.simple_add_course(COURSE_NAME, ADMIN_EMAIL, COURSE_TITLE)

        # Add labels
        with common_utils.Namespace(NAMESPACE):
            self.foo_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Foo',
                       'descripton': 'foo',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))
            self.bar_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Bar',
                       'descripton': 'bar',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))
            self.baz_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Baz',
                       'descripton': 'baz',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))
            self.quux_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Quux',
                       'descripton': 'quux',
                       'type': models.LabelDTO.LABEL_TYPE_GENERAL}))

        # Register a student for that course.
        actions.login(REGISTERED_STUDENT_EMAIL)
        actions.register(self, REGISTERED_STUDENT_NAME, COURSE_NAME)
        actions.logout()

        # Add some units to the course.
        self._course = courses.Course(
            None, app_context=sites.get_all_courses()[0])
        self._unit_no_labels = self._course.add_unit()
        self._unit_no_labels.title = 'Unit No Labels'
        self._unit_no_labels.availability = courses.AVAILABILITY_AVAILABLE
        self._course.add_lesson(self._unit_no_labels)
        self._unit_labels_foo = self._course.add_unit()
        self._unit_labels_foo.title = 'Unit Labels: Foo'
        self._unit_labels_foo.availability = courses.AVAILABILITY_AVAILABLE
        self._unit_labels_foo.labels = str(self.foo_id)
        self._course.add_lesson(self._unit_labels_foo)
        self._unit_labels_foo_bar = self._course.add_unit()
        self._unit_labels_foo_bar.title = 'Unit Labels: Bar, Foo'
        self._unit_labels_foo_bar.availability = courses.AVAILABILITY_AVAILABLE
        self._unit_labels_foo_bar.labels = '%s %s' % (self.bar_id, self.foo_id)
        self._course.add_lesson(self._unit_labels_foo_bar)
        self._unit_labels_quux = self._course.add_unit()
        self._unit_labels_quux.title = 'Unit Labels: Quux'
        self._unit_labels_quux.availability = courses.AVAILABILITY_AVAILABLE
        self._unit_labels_quux.labels = str(self.quux_id)
        self._course.add_lesson(self._unit_labels_quux)
        self._unit_labels_foo_quux = self._course.add_unit()
        self._unit_labels_foo_quux.title = 'Unit Labels: Foo Quux'
        self._unit_labels_foo_quux.availability = courses.AVAILABILITY_AVAILABLE
        self._unit_labels_foo_quux.labels = '%s %s' % (str(self.foo_id),
                                                       str(self.quux_id))
        self._course.add_lesson(self._unit_labels_foo_quux)
        self._course.save()
Beispiel #43
0
 def test_get_news_no_news(self):
     actions.login(self.STUDENT_EMAIL)
     actions.register(self, 'John Smith')
     response = self.get('course')
     soup = self.parse_html_string_to_soup(response.body)
     self.assertEquals(['has_only_old_news'],
                       self._get_news_title_styles(soup))
     self.assertEquals([], news_tests_lib.extract_news_items_from_soup(soup))
    def test_end_to_end(self):
        """Actually enroll and unenroll students; verify reporting counts."""

        COURSE_NAME_BASE = 'test'
        NUM_COURSES = 2
        NUM_STUDENTS = 3
        THE_TIMESTAMP = 1427245200

        for course_num in range(NUM_COURSES):
            course_name = '%s_%d' % (COURSE_NAME_BASE, course_num)
            actions.simple_add_course(course_name, ADMIN_EMAIL, course_name)
            actions.update_course_config(course_name, {
                'course': {
                    'now_available': True,
                    'browsable': True,
                },
            })
            for student_num in range(NUM_STUDENTS):
                name = '%s_%d_%d' % (COURSE_NAME_BASE, course_num, student_num)
                actions.login(name + '@foo.com')
                actions.register(self, name, course_name)
                if student_num == 0:
                    actions.unregister(self, course_name)
                actions.logout()

        # Expect no messages yet; haven't run job.
        self.assertEquals([], MockSender.get_sent())

        # Run all counting jobs.
        usage_reporting.StartReportingJobs._submit_jobs()
        self.execute_all_deferred_tasks()

        # Verify counts.  (Ignore dates, these are fickle and subject to
        # weirdness on hour boundaries.  Also ignore course/instance IDs;
        # they are non-random and thus all the same.)
        num_enrolled_msgs = 0
        num_unenrolled_msgs = 0
        num_student_count_msgs = 0
        for message in MockSender.get_sent():
            if (message[messaging.Message._METRIC] ==
                    messaging.Message.METRIC_STUDENT_COUNT):
                num_student_count_msgs += 1
                self.assertEquals(NUM_STUDENTS,
                                  message[messaging.Message._VALUE])
            elif (message[messaging.Message._METRIC] ==
                  messaging.Message.METRIC_ENROLLED):
                num_enrolled_msgs += 1
                self.assertEquals(NUM_STUDENTS,
                                  message[messaging.Message._VALUE])
            elif (message[messaging.Message._METRIC] ==
                  messaging.Message.METRIC_UNENROLLED):
                num_unenrolled_msgs += 1
                self.assertEquals(1, message[messaging.Message._VALUE])

        self.assertEquals(NUM_COURSES, num_enrolled_msgs)
        self.assertEquals(NUM_COURSES, num_unenrolled_msgs)
        self.assertEquals(NUM_COURSES, num_student_count_msgs)
        sites.reset_courses()
Beispiel #45
0
 def test_delete_link_when_unregistered_then_proceed(self):
     user = actions.login(self.STUDENT_EMAIL)
     actions.register(self, self.STUDENT_EMAIL)
     actions.unregister(self)
     response = self.get('course')
     response = self.click(response, 'Delete My Data')
     self._deletion_flow_for_unregistered_student(response, cancel=False)
     response = self.get('course')
     self.assertNotIn('Delete My Data', response.body)
Beispiel #46
0
 def test_unregister_hides_deletion_option_when_no_deletion_policy(self):
     actions.login(self.STUDENT_EMAIL)
     actions.register(self, self.STUDENT_EMAIL)
     with actions.OverriddenEnvironment({
         data_removal.DATA_REMOVAL_SETTINGS_SECTION: {
             data_removal.REMOVAL_POLICY:
             data_removal.IndefiniteRetentionPolicy.get_name()}}):
         response = self.get('student/unenroll')
     self.assertNotIn('Remove all my data from the course', response.body)
 def test_unregister_hides_deletion_option_when_no_deletion_policy(self):
     actions.login(self.STUDENT_EMAIL)
     actions.register(self, self.STUDENT_EMAIL)
     with actions.OverriddenEnvironment({
         data_removal.DATA_REMOVAL_SETTINGS_SECTION: {
             data_removal.REMOVAL_POLICY:
             data_removal.IndefiniteRetentionPolicy.get_name()}}):
         response = self.get('student/unenroll')
     self.assertNotIn('Remove all my data from the course', response.body)
 def test_delete_link_when_unregistered_then_proceed(self):
     user = actions.login(self.STUDENT_EMAIL)
     actions.register(self, self.STUDENT_EMAIL)
     actions.unregister(self)
     response = self.get('course')
     response = self.click(response, 'Delete My Data')
     self._deletion_flow_for_unregistered_student(response, cancel=False)
     response = self.get('course')
     self.assertNotIn('Delete My Data', response.body)
    def test_student_property_removed(self):
        """Test a sampling of types whose index contains user ID.

        Here, indices start with the user ID, but are suffixed with the name
        of a specific property sub-type.  Verify that these are removed.
        """
        user = self.make_test_user(self.STUDENT_EMAIL)


        user_id = None
        actions.login(user.email())
        actions.register(self, self.STUDENT_EMAIL, course=self.COURSE)

        # Get IDs of those students; make an event for each.
        with common_utils.Namespace(self.NAMESPACE):
            student = models.Student.get_by_user(user)
            user_id = student.user_id
            p = models.StudentPropertyEntity.create(student, 'foo')
            p.value = 'foo'
            p.put()
            invitation.InvitationStudentProperty.load_or_create(student)
            questionnaire.StudentFormEntity.load_or_create(student, 'a_form')
            cm = competency.BaseCompetencyMeasure(user_id)
            cm.load(123)
            cm.save()

        # Assure ourselves that we have exactly one of the items we just added.
        with common_utils.Namespace(self.NAMESPACE):
            l = list(models.StudentPropertyEntity.all().run())
            self.assertEquals(2, len(l))  # 'foo', 'linear-course-completion'
            l = list(invitation.InvitationStudentProperty.all().run())
            self.assertEquals(1, len(l))
            l = list(questionnaire.StudentFormEntity.all().run())
            self.assertEquals(1, len(l))
            l = list(competency.CompetencyMeasureEntity.all().run())
            self.assertEquals(1, len(l))


        actions.unregister(self, self.COURSE, do_data_removal=True)
        self.execute_all_deferred_tasks(
            models.StudentLifecycleObserver.QUEUE_NAME)
        self.get(
            data_removal.DataRemovalCronHandler.URL,
            headers={'X-AppEngine-Cron': 'True'})
        self.execute_all_deferred_tasks()

        # Assure ourselves that all added items are now gone.
        with common_utils.Namespace(self.NAMESPACE):
            l = list(models.StudentPropertyEntity.all().run())
            self.assertEquals(0, len(l))
            l = list(invitation.InvitationStudentProperty.all().run())
            self.assertEquals(0, len(l))
            l = list(questionnaire.StudentFormEntity.all().run())
            self.assertEquals(0, len(l))
            l = list(competency.CompetencyMeasureEntity.all().run())
            self.assertEquals(0, len(l))
    def test_rest_handler_discretely_does_not_mail_registered_users(self):
        # To reduce spam the service should NOT email registered users, but for
        # privacy reasons should report to the requestor that it did.
        registered_student = '*****@*****.**'
        actions.login(registered_student)
        actions.register(self, 'Test User')

        response = self._do_valid_email_list_post([registered_student])
        self.assertEquals(200, response['status'])
        self.assertEquals('OK, 1 messages sent', response['message'])
        self.assertEqual(0, self.send_async_count)
    def setUp(self):
        super(StudentTracksTest, self).setUp()

        # Add a course that will show up.
        actions.simple_add_course(COURSE_NAME, ADMIN_EMAIL, COURSE_TITLE)

        # Add labels
        with common_utils.Namespace(NAMESPACE):
            self.foo_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Foo',
                       'descripton': 'foo',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))
            self.bar_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Bar',
                       'descripton': 'bar',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))
            self.baz_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Baz',
                       'descripton': 'baz',
                       'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK}))
            self.quux_id = models.LabelDAO.save(models.LabelDTO(
                None, {'title': 'Quux',
                       'descripton': 'quux',
                       'type': models.LabelDTO.LABEL_TYPE_GENERAL}))

        # Register a student for that course.
        actions.login(REGISTERED_STUDENT_EMAIL)
        actions.register(self, REGISTERED_STUDENT_NAME, COURSE_NAME)
        actions.logout()

        # Add some units to the course.
        self._course = courses.Course(
            None, app_context=sites.get_all_courses()[0])
        self._unit_no_labels = self._course.add_unit()
        self._unit_no_labels.title = 'Unit No Labels'
        self._unit_no_labels.now_available = True
        self._unit_labels_foo = self._course.add_unit()
        self._unit_labels_foo.title = 'Unit Labels: Foo'
        self._unit_labels_foo.now_available = True
        self._unit_labels_foo.labels = str(self.foo_id)
        self._unit_labels_foo_bar = self._course.add_unit()
        self._unit_labels_foo_bar.title = 'Unit Labels: Bar, Foo'
        self._unit_labels_foo_bar.now_available = True
        self._unit_labels_foo_bar.labels = '%s %s' % (self.bar_id, self.foo_id)
        self._unit_labels_quux = self._course.add_unit()
        self._unit_labels_quux.title = 'Unit Labels: Quux'
        self._unit_labels_quux.now_available = True
        self._unit_labels_quux.labels = str(self.quux_id)
        self._unit_labels_foo_quux = self._course.add_unit()
        self._unit_labels_foo_quux.title = 'Unit Labels: Foo Quux'
        self._unit_labels_foo_quux.now_available = True
        self._unit_labels_foo_quux.labels = '%s %s' % (str(self.foo_id),
                                                       str(self.quux_id))
        self._course.save()
Beispiel #52
0
    def test_student_property_removed(self):
        """Test a sampling of types whose index contains user ID.

        Here, indices start with the user ID, but are suffixed with the name
        of a specific property sub-type.  Verify that these are removed.
        """
        user = self.make_test_user(self.STUDENT_EMAIL)

        user_id = None
        actions.login(user.email())
        actions.register(self, self.STUDENT_EMAIL, course=self.COURSE)

        # Get IDs of those students; make an event for each.
        with common_utils.Namespace(self.NAMESPACE):
            student = models.Student.get_by_user(user)
            user_id = student.user_id
            p = models.StudentPropertyEntity.create(student, 'foo')
            p.value = 'foo'
            p.put()
            invitation.InvitationStudentProperty.load_or_create(student)
            questionnaire.StudentFormEntity.load_or_create(student, 'a_form')
            cm = competency.BaseCompetencyMeasure(user_id)
            cm.load(123)
            cm.save()

        # Assure ourselves that we have exactly one of the items we just added.
        with common_utils.Namespace(self.NAMESPACE):
            l = list(models.StudentPropertyEntity.all().run())
            self.assertEquals(2, len(l))  # 'foo', 'linear-course-completion'
            l = list(invitation.InvitationStudentProperty.all().run())
            self.assertEquals(1, len(l))
            l = list(questionnaire.StudentFormEntity.all().run())
            self.assertEquals(1, len(l))
            l = list(competency.CompetencyMeasureEntity.all().run())
            self.assertEquals(1, len(l))

        actions.unregister(self, self.COURSE, do_data_removal=True)
        self.execute_all_deferred_tasks(
            models.StudentLifecycleObserver.QUEUE_NAME)
        self.get(data_removal.DataRemovalCronHandler.URL,
                 headers={'X-AppEngine-Cron': 'True'})
        self.execute_all_deferred_tasks()

        # Assure ourselves that all added items are now gone.
        with common_utils.Namespace(self.NAMESPACE):
            l = list(models.StudentPropertyEntity.all().run())
            self.assertEquals(0, len(l))
            l = list(invitation.InvitationStudentProperty.all().run())
            self.assertEquals(0, len(l))
            l = list(questionnaire.StudentFormEntity.all().run())
            self.assertEquals(0, len(l))
            l = list(competency.CompetencyMeasureEntity.all().run())
            self.assertEquals(0, len(l))
Beispiel #53
0
        def add_course_and_register_student(admin_email):
            google_app_context = actions.simple_add_course(
                course_name, admin_email, course_title)
            actions.update_course_config(
                course_name,
                {'course': {'now_available': True, 'browsable': True,},})
            actions.register(self, 'John Smith', course_name)

            with actions.OverriddenConfig(config.REPORT_ALLOWED.name, True):
                usage_reporting.StartReportingJobs._for_testing_only_get()
            self.execute_all_deferred_tasks(
                models.StudentLifecycleObserver.QUEUE_NAME)
            self.execute_all_deferred_tasks()
    def setUp(self):
        super(UnitPrePostAssessmentTest, self).setUp()

        context = actions.simple_add_course(COURSE_NAME, ADMIN_EMAIL,
                                            COURSE_TITLE)
        self.course = courses.Course(None, context)

        self.unit_no_lessons = self.course.add_unit()
        self.unit_no_lessons.title = 'No Lessons'
        self.unit_no_lessons.now_available = True

        self.unit_one_lesson = self.course.add_unit()
        self.unit_one_lesson.title = 'One Lesson'
        self.unit_one_lesson.now_available = True
        self.lesson = self.course.add_lesson(self.unit_one_lesson)
        self.lesson.title = 'Lesson One'
        self.lesson.objectives = 'body of lesson'
        self.lesson.now_available = True

        self.assessment_one = self.course.add_assessment()
        self.assessment_one.title = 'Assessment One'
        self.assessment_one.html_content = 'assessment one content'
        self.assessment_one.now_available = True

        self.assessment_two = self.course.add_assessment()
        self.assessment_two.title = 'Assessment Two'
        self.assessment_two.html_content = 'assessment two content'
        self.assessment_two.now_available = True

        self.course.save()
        actions.login(STUDENT_EMAIL)
        actions.register(self, STUDENT_EMAIL, COURSE_NAME)
        config.Registry.test_overrides[
            utils.CAN_PERSIST_ACTIVITY_EVENTS.name] = True

        with common_utils.Namespace(NAMESPACE):
            self.track_one_id = models.LabelDAO.save(
                models.LabelDTO(
                    None, {
                        'title': 'Track One',
                        'descripton': 'track_one',
                        'type': models.LabelDTO.LABEL_TYPE_COURSE_TRACK
                    }))
            self.general_one_id = models.LabelDAO.save(
                models.LabelDTO(
                    None, {
                        'title': 'Track One',
                        'descripton': 'track_one',
                        'type': models.LabelDTO.LABEL_TYPE_GENERAL
                    }))
    def test_remove_by_email(self):
        user = actions.login(self.STUDENT_EMAIL)
        actions.register(self, user.email(), course=self.COURSE)

        # Get IDs of those students; make an event for each.
        with common_utils.Namespace(self.NAMESPACE):
            sse = unsubscribe.SubscriptionStateEntity(key_name=user.email())
            sse.save()

        self._unregister_and_request_data_removal(self.COURSE)
        self._complete_removal()

        with common_utils.Namespace(self.NAMESPACE):
            l = list(unsubscribe.SubscriptionStateEntity.all().run())
            self.assertEquals(0, len(l))