Esempio n. 1
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)
Esempio n. 2
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)
Esempio n. 3
0
    def test_update_wont_clobber_status(self):
        # This fixture should already have a sync time
        with utils.Namespace(self.app_context.namespace):
            dto = drive_models.DriveSyncDAO.load('6')
            self.assertIsNotNone(dto.last_synced)

        # update existing record
        self.assertRestStatus(self.put('rest/modules/drive/item', {
            'request': transforms.dumps({
                'xsrf_token':
                    crypto.XsrfTokenManager.create_xsrf_token(
                        'drive-item-rest'),
                'key': '6',
                'payload': transforms.dumps({
                    'sync_interval': 'hour',
                    'version': '1.0',
                    'availability': 'public',
                }),
            }),
        }), 200)

        # The sync time should still exist
        with utils.Namespace(self.app_context.namespace):
            dto = drive_models.DriveSyncDAO.load('6')
            self.assertIsNotNone(dto.last_synced)
    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.now_available = True
        unit1.labels = str(foo_id)
        lesson11 = course.add_lesson(unit1)
        lesson11.objectives = 'common plugh <gcb-youtube videoid="glados">'
        lesson11.now_available = True
        lesson11.notes = search_unit_test.VALID_PAGE_URL
        lesson11.video = 'portal'
        course.update_unit(unit1)
        unit2 = course.add_unit()
        unit2.now_available = True
        unit1.labels = str(bar_id)
        lesson21 = course.add_lesson(unit2)
        lesson21.objectives = 'common plover'
        lesson21.now_available = True
        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_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_hook_i18n(self):
        actions.update_course_config(
            COURSE_NAME,
            {
                'html_hooks': {'base': {'after_body_tag_begins': 'foozle'}},
                'extra_locales': [
                    {'locale': 'de', 'availability': 'available'},
                ]
            })

        hook_bundle = {
            'content': {
                'type': 'html',
                'source_value': '',
                'data': [{
                    'source_value': 'foozle',
                    'target_value': 'FUZEL',
                }],
            }
        }
        hook_key = i18n_dashboard.ResourceBundleKey(
            utils.ResourceHtmlHook.TYPE, 'base.after_body_tag_begins', 'de')
        with common_utils.Namespace(NAMESPACE):
            i18n_dashboard.ResourceBundleDAO.save(
                i18n_dashboard.ResourceBundleDTO(str(hook_key), hook_bundle))

        # Verify non-translated version.
        response = self.get(BASE_URL)
        dom = self.parse_html_string(response.body)
        html_hook = dom.find('.//div[@id="base-after-body-tag-begins"]')
        self.assertEquals('foozle', html_hook.text)

        # Set preference to translated language, and check that that's there.
        with common_utils.Namespace(NAMESPACE):
            prefs = models.StudentPreferencesDAO.load_or_default()
            prefs.locale = 'de'
            models.StudentPreferencesDAO.save(prefs)

        response = self.get(BASE_URL)
        dom = self.parse_html_string(response.body)
        html_hook = dom.find('.//div[@id="base-after-body-tag-begins"]')
        self.assertEquals('FUZEL', html_hook.text)

        # With no translation present, but preference set to foreign language,
        # verify that we fall back to the original language.

        # Remove translation bundle, and clear cache.
        with common_utils.Namespace(NAMESPACE):
            i18n_dashboard.ResourceBundleDAO.delete(
                i18n_dashboard.ResourceBundleDTO(str(hook_key), hook_bundle))
        model_caching.CacheFactory.get_cache_instance(
            i18n_dashboard.RESOURCE_BUNDLE_CACHE_NAME).clear()

        response = self.get(BASE_URL)
        dom = self.parse_html_string(response.body)
        html_hook = dom.find('.//div[@id="base-after-body-tag-begins"]')
        self.assertEquals('foozle', html_hook.text)
Esempio n. 7
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))
Esempio n. 8
0
 def test_dashboard_access_method(self):
     with utils.Namespace(self.course_with_access.app_context.namespace):
         self.assertFalse(dashboard.DashboardHandler.current_user_has_access(
             self.course_with_access.app_context))
     with utils.Namespace(self.course_without_access.app_context.namespace):
         self.assertFalse(dashboard.DashboardHandler.current_user_has_access(
             self.course_without_access.app_context))
     actions.login(self.USER_EMAIL, is_admin=False)
     with utils.Namespace(self.course_with_access.app_context.namespace):
         self.assertTrue(dashboard.DashboardHandler.current_user_has_access(
             self.course_with_access.app_context))
     with utils.Namespace(self.course_without_access.app_context.namespace):
         self.assertFalse(dashboard.DashboardHandler.current_user_has_access(
             self.course_without_access.app_context))
     actions.logout()
    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))
Esempio n. 10
0
 def _save(cls, dto):
     # The "save" operation is not public because clients of the enrollments
     # module should cause Datastore mutations only via set() and inc().
     with common_utils.Namespace(appengine_config.DEFAULT_NAMESPACE_NAME):
         entity = cls.ENTITY(key_name=dto.id)
         entity.json = dto.marshal(dto.dict)
         entity.put()
Esempio n. 11
0
    def on_all_data_removed(cls, user_id):
        """Called back from DataRemovalCronHandler when batch deletion done."""

        # Any user_id we are called for has had all wipeout batch jobs run.
        # This means that all un-indexed items have been removed for that
        # user.  However, analysis map/reduce jobs may have been running in
        # parallel with wipeout and re-added items indexed by user ID.  Do one
        # more pass of removing indexed items before we declare the user to be
        # done.
        cls._remove_per_course_indexed_items(user_id)

        # Look through peer courses to see if the user is registered in any.
        # If not, we can also remove any global settings items.
        in_other_courses = False
        for app_context in sites.get_course_index().get_all_courses():
            with common_utils.Namespace(app_context.get_namespace_name()):
                student = models.Student.get_by_user_id(user_id)
                if student is not None:
                    in_other_courses = True
        if not in_other_courses:
            cls._remove_sitewide_indexed_items(user_id)

        # When the foregoing deletion has completed w/o raising any
        # exceptions, clean up the final two items that have any user-related
        # PII.  If this fails, the BatchRemovalState record will not be
        # removed, and the next call to the cron handler will again see the
        # user has no more batch items to remove, and call us again.
        @db.transactional(xg=True)
        def remove_deletion_state_records(user_id):
            removal_models.ImmediateRemovalState.delete_by_user_id(user_id)
            removal_models.BatchRemovalState.delete_by_user_id(user_id)
        remove_deletion_state_records(user_id)
Esempio n. 12
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 main(self):
        # By the time main() is invoked, arguments are parsed and available as
        # self.args. If you need more complicated argument validation than
        # argparse gives you, do it here:
        if self.args.batch_size < 1:
            sys.exit('--batch size must be positive')
        if os.path.exists(self.args.path):
            sys.exit('Cannot download to %s; file exists' % self.args.path)

        # Arguments passed to etl.py are also parsed and available as
        # self.etl_args. Here we use them to figure out the requested course's
        # namespace.
        namespace = etl_lib.get_context(
            self.etl_args.course_url_prefix).get_namespace_name()

        # Because our models are namespaced, we need to change to the requested
        # course's namespace when doing datastore reads.
        with common_utils.Namespace(namespace):

            # This base query can be modified to add whatever filters you need.
            query = models.Student.all()
            students = common_utils.iter_all(query, self.args.batch_size)

            # Write the results. Done!
            with open(self.args.path, 'w') as f:
                for student in students:
                    f.write(student.email)
                    f.write('\n')
Esempio n. 14
0
 def get_singleton(cls):
     with common_utils.Namespace(appengine_config.DEFAULT_NAMESPACE_NAME):
         entity = cls.get_by_key_name(cls.SINGLETON_KEY)
         if not entity:
             entity = cls(key_name=cls.SINGLETON_KEY)
             entity.last_run = datetime.datetime(1970, 1, 1)
         return entity
 def test_modified_blacklist_contents(self):
     save_blacklist = models.Student._PROPERTY_EXPORT_BLACKLIST
     models.Student._PROPERTY_EXPORT_BLACKLIST = [
         'name',
         'additional_fields.age',
         'additional_fields.gender',
     ]
     blacklisted = [
         {'name': 'age', 'value': '22'},
         {'name': 'gender', 'value': 'female'},
     ]
     permitted = [
         {'name': 'goal', 'value': 'complete_course'},
         {'name': 'timezone', 'value': 'America/Los_Angeles'},
     ]
     additional_fields = transforms.dumps(
         [[x['name'], x['value']] for x in blacklisted + permitted])
     with utils.Namespace('ns_test'):
         models.Student(
             user_id='123456', additional_fields=additional_fields).put()
         response = transforms.loads(self.get(
             '/test/rest/data/students/items').body)
     self.assertEquals(permitted,
                       response['data'][0]['additional_fields'])
     models.Student._PROPERTY_EXPORT_BLACKLIST = save_blacklist
Esempio n. 16
0
    def post_config_override(self):
        """Handles 'override' property action."""
        name = self.request.get('name')

        # Find item in registry.
        item = None
        if name and name in config.Registry.registered.keys():
            item = config.Registry.registered[name]
        if not item:
            self.redirect('?action=settings' % self.LINK_URL)

        with common_utils.Namespace(appengine_config.DEFAULT_NAMESPACE_NAME):
            # Add new entity if does not exist.
            try:
                entity = config.ConfigPropertyEntity.get_by_key_name(name)
            except db.BadKeyError:
                entity = None
            if not entity:
                entity = config.ConfigPropertyEntity(key_name=name)
                entity.value = str(item.value)
                entity.is_draft = True
                entity.put()

            models.EventEntity.record(
                'override-property', users.get_current_user(),
                transforms.dumps({
                    'name': name, 'value': str(entity.value)}))

        self.redirect('%s?%s' % (self.URL, urllib.urlencode(
            {'action': 'config_edit', 'name': name})))
Esempio n. 17
0
    def setUp(self):
        super(DriveTestBase, self).setUp()
        actions.login(self.ADMIN_EMAIL, is_admin=True)
        self.app_context = actions.simple_add_course(
            self.COURSE_NAME, self.ADMIN_EMAIL, 'Drive Course')
        self.base = '/{}'.format(self.COURSE_NAME)

        # have a syncing policy in place already
        with utils.Namespace(self.app_context.namespace):
            self.setup_schedule_for_file('3')
            self.setup_schedule_for_file('5')
            self.setup_schedule_for_file('6', synced=True)

        # remove all hooks
        for handler, hooks in self.HANDLER_HOOKS:
            for hook in hooks:
                self.swap(handler, hook, [])

        # Prevent it from consulting the settings or calling out

        self.swap(
            drive_api_manager._DriveManager, 'from_app_context',
            classmethod(mocks.manager_from_mock))

        self.swap(
            drive_api_manager._DriveManager, 'from_code',
            classmethod(mocks.manager_from_mock))

        self.swap(
            handlers.AbstractDriveDashboardHandler, 'setup_drive',
            mocks.setup_drive)

        self.swap(
            drive_settings, 'get_secrets', mocks.get_secrets)
    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_create()
            prefs.last_location = None
            models.StudentPreferencesDAO.save(prefs)
    def test_large_volume_of_random_bytes_is_sharded(self):
        r = random.Random()
        r.seed(0)
        orig_data = ''.join([
            chr(r.randrange(256))
            for x in xrange(int(vfs._MAX_VFS_SHARD_SIZE + 1))
        ])
        namespace = 'ns_foo'
        fs = vfs.DatastoreBackedFileSystem(namespace, '/')
        filename = '/foo'
        fs.put(filename, StringIO.StringIO(orig_data))

        # fs.put() clears cache, so this will do a direct read.
        actual = fs.get(filename).read()
        self.assertEquals(orig_data, actual)

        # And again, this time from cache.
        actual = fs.get(filename).read()
        self.assertEquals(orig_data, actual)

        # Verify that sharded items exist with appropriate sizes.
        file_key_names = vfs.DatastoreBackedFileSystem._generate_file_key_names(
            filename, vfs._MAX_VFS_SHARD_SIZE + 1)
        self.assertEquals(
            2, len(file_key_names),
            'Verify attempting to store a too-large file makes multiple shards'
        )

        with common_utils.Namespace(namespace):
            shard_0 = vfs.FileDataEntity.get_by_key_name(file_key_names[0])
            self.assertEquals(vfs._MAX_VFS_SHARD_SIZE, len(shard_0.data))

            shard_1 = vfs.FileDataEntity.get_by_key_name(file_key_names[1])
            self.assertEquals(1, len(shard_1.data))
Esempio n. 20
0
 def test_set_then_unset_subscription_state(self):
     with utils.Namespace(self.namespace):
         self.assertSubscribed(self.EMAIL, self.namespace)
         unsubscribe.set_subscribed(self.EMAIL, True)
         self.assertSubscribed(self.EMAIL, self.namespace)
         unsubscribe.set_subscribed(self.EMAIL, False)
         self.assertUnsubscribed(self.EMAIL, self.namespace)
Esempio n. 21
0
    def test_assessments_with_tracks_not_settable_as_pre_post(self):
        self.assessment_one.labels = str(self.track_one_id)
        self.assessment_two.labels = str(self.track_one_id)
        self.course.save()
        unit_rest_handler = unit_lesson_editor.UnitRESTHandler()
        unit_rest_handler.app_context = self.course.app_context

        with common_utils.Namespace(NAMESPACE):
            errors = []
            unit_rest_handler.apply_updates(
                self.unit_no_lessons, {
                    'title': self.unit_no_lessons.title,
                    'now_available': self.unit_no_lessons.now_available,
                    'label_groups': [],
                    'pre_assessment': self.assessment_one.unit_id,
                    'post_assessment': self.assessment_two.unit_id,
                    'show_contents_on_one_page': False,
                    'manual_progress': False,
                    'description': None,
                    'unit_header': None,
                    'unit_footer': None,
                }, errors)
            self.assertEquals([
                'Assessment "Assessment One" has track labels, so it '
                'cannot be used as a pre/post unit element',
                'Assessment "Assessment Two" has track labels, so it '
                'cannot be used as a pre/post unit element'
            ], errors)
Esempio n. 22
0
    def setUp(self):
        super(MultipleChoiceTagTests, self).setUp()

        self.base = '/' + COURSE_NAME
        self.app_context = actions.simple_add_course(COURSE_NAME, ADMIN_EMAIL,
                                                     'Assessment Tags')
        self.namespace = 'ns_%s' % COURSE_NAME

        with utils.Namespace(self.namespace):
            dto = models.QuestionDTO(None, transforms.loads(self.MC_1_JSON))
            self.mc_1_id = models.QuestionDAO.save(dto)
            dto = models.QuestionDTO(None, transforms.loads(self.MC_2_JSON))
            self.mc_2_id = models.QuestionDAO.save(dto)
            dto = models.QuestionGroupDTO(
                None,
                transforms.loads(self.QG_1_JSON_TEMPLATE %
                                 (self.mc_1_id, self.mc_2_id)))
            self.qg_1_id = models.QuestionGroupDAO.save(dto)

        self.course = courses.Course(None, self.app_context)
        self.assessment = self.course.add_assessment()
        self.assessment.availability = courses.AVAILABILITY_AVAILABLE
        self.assessment.html_content = (
            '<question quid="%s" weight="1" instanceid="q1"></question>'
            '<question-group qgid="%s" instanceid="qg1"></question-group' %
            (self.mc_1_id, self.qg_1_id))
        self.course.save()
 def test_cant_override_reserved_url(self):
     with common_utils.Namespace(self.app_context.namespace):
         with self.assertRaises(user_routes.URLReservedError):
             router = (user_routes.UserCourseRouteManager.
                       from_current_appcontext())
             router.add('course', 'test_handler')
             router.save()
Esempio n. 24
0
def update_course_config(name, settings):
    """Merge settings into the saved course.yaml configuration.

    Args:
      name: Name of the course.  E.g., 'my_test_course'.
      settings: A nested dict of name/value settings.  Names for items here
          can be found in modules/dashboard/course_settings.py in
          create_course_registry.  See below in simple_add_course()
          for an example.
    Returns:
      Context object for the modified course.
    """
    site_type = 'course'
    namespace = 'ns_%s' % name
    slug = '/%s' % name
    rule = '%s:%s::%s' % (site_type, slug, namespace)

    context = sites.get_all_courses(rule)[0]
    environ = courses.deep_dict_merge(settings,
                                      courses.Course.get_environ(context))
    content = yaml.safe_dump(environ)
    with common_utils.Namespace(namespace):
        context.fs.put(
            context.get_config_filename(),
            vfs.string_to_stream(unicode(content)))
    course_config = config.Registry.test_overrides.get(
        sites.GCB_COURSES_CONFIG.name, 'course:/:/')
    if rule not in course_config:
        course_config = '%s, %s' % (rule, course_config)
        sites.setup_courses(course_config)
    return context
Esempio n. 25
0
 def test_modified_blacklist_contents(self):
     # pylint: disable-msg=protected-access
     save_blacklist = models.Student._PROPERTY_EXPORT_BLACKLIST
     models.Student._PROPERTY_EXPORT_BLACKLIST = [
         'name',
         'additional_fields.age',
         'additional_fields.gender',
     ]
     blacklisted_fields = [
         ['age', 22],
         ['gender', 'female'],
     ]
     permitted_fields = [['goal', 'complete_course'],
                         ['timezone', 'America/Los_Angeles']]
     additional_fields = transforms.dumps(blacklisted_fields +
                                          permitted_fields)
     with utils.Namespace('ns_test'):
         models.Student(user_id='123456',
                        additional_fields=additional_fields).put()
         response = transforms.loads(
             self.get('/test/rest/data/students/items').body)
     self.assertEquals({k: v
                        for k, v in permitted_fields},
                       response['data'][0]['additional_fields'])
     models.Student._PROPERTY_EXPORT_BLACKLIST = save_blacklist
    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)
Esempio n. 27
0
    def test_tag_before_and_after_submission(self):
        assessment = self.course.add_assessment()
        assessment.html_content = (
            '<text-file-upload-tag '
            '    display_length="100" instanceid="this-tag-id">'
            '</text-file-upload-tag>')
        self.course.save()

        response = self.get('assessment?name=%s' % assessment.unit_id)
        dom = self.parse_html_string(response.body)
        warning = dom.find(
            './/*[@class="user-upload-form-warning"]').text.strip()
        self.assertEquals('Maximum file size is 1MB.', warning)

        with common_utils.Namespace('ns_' + self._COURSE_NAME):
            student, _ = models.Student.get_first_by_email(self._STUDENT_EMAIL)
            student_work.Submission.write(assessment.unit_id,
                                          student.get_key(),
                                          'contents',
                                          instance_id='this-tag-id')

        response = self.get('assessment?name=%s' % assessment.unit_id)
        dom = self.parse_html_string(response.body)
        warning = dom.find(
            './/*[@class="user-upload-form-warning"]').text.strip()
        self.assertEquals(
            'You have already submitted; submit again to replace your previous '
            'entry.', warning)
Esempio n. 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()
Esempio n. 29
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)
Esempio n. 30
0
    def post_config_reset(self):
        """Handles 'reset' property action."""
        name = self.request.get('name')

        # Find item in registry.
        item = None
        if name and name in config.Registry.registered.keys():
            item = config.Registry.registered[name]
        if not item:
            self.redirect('%s?action=settings' % self.LINK_URL)

        with common_utils.Namespace(appengine_config.DEFAULT_NAMESPACE_NAME):
            # Delete if exists.
            try:
                entity = config.ConfigPropertyEntity.get_by_key_name(name)
                if entity:
                    old_value = entity.value
                    entity.delete()

                    models.EventEntity.record(
                        'delete-property', users.get_current_user(),
                        transforms.dumps({
                            'name': name,
                            'value': str(old_value)
                        }))

            except db.BadKeyError:
                pass

        self.redirect('%s?action=settings' % self.URL)