def test_editable_collection_handler_put_can_access(self): """Check that editors can access put handler""" whitelisted_usernames = [self.EDITOR_USERNAME, self.VIEWER_USERNAME] self.set_config_property( config_domain.WHITELISTED_COLLECTION_EDITOR_USERNAMES, whitelisted_usernames) rights_manager.create_new_collection_rights( self.COLLECTION_ID, self.owner_id) rights_manager.assign_role_for_collection( self.admin_id, self.COLLECTION_ID, self.editor_id, rights_manager.ROLE_EDITOR) rights_manager.publish_collection(self.owner_id, self.COLLECTION_ID) self.login(self.EDITOR_EMAIL) # Call get handler to return the csrf token. response = self.testapp.get( '%s/%s' % (feconf.COLLECTION_URL_PREFIX, self.COLLECTION_ID)) csrf_token = self.get_csrf_token_from_response(response) json_response = self.put_json( '%s/%s' % (feconf.EDITABLE_COLLECTION_DATA_URL_PREFIX, self.COLLECTION_ID), self.json_dict, csrf_token=csrf_token) self.assertEqual(self.COLLECTION_ID, json_response['collection']['id']) self.assertEqual(2, json_response['collection']['version']) self.logout()
def test_editable_collection_handler_put_can_access(self): """Check that editors can access put handler.""" whitelisted_usernames = [self.EDITOR_USERNAME, self.VIEWER_USERNAME] self.set_collection_editors(whitelisted_usernames) rights_manager.create_new_collection_rights( self.COLLECTION_ID, self.owner_id) rights_manager.assign_role_for_collection( self.moderator, self.COLLECTION_ID, self.editor_id, rights_domain.ROLE_EDITOR) rights_manager.publish_collection(self.owner, self.COLLECTION_ID) self.login(self.EDITOR_EMAIL) # Call get handler to return the csrf token. csrf_token = self.get_new_csrf_token() json_response = self.put_json( '%s/%s' % ( feconf.COLLECTION_EDITOR_DATA_URL_PREFIX, self.COLLECTION_ID), self.json_dict, csrf_token=csrf_token) self.assertEqual(self.COLLECTION_ID, json_response['collection']['id']) self.assertEqual(2, json_response['collection']['version']) self.logout()
def test_editable_collection_handler_put_cannot_access(self): """Check that non-editors cannot access editable put handler""" whitelisted_usernames = [self.EDITOR_USERNAME, self.VIEWER_USERNAME] self.set_config_property( config_domain.WHITELISTED_COLLECTION_EDITOR_USERNAMES, whitelisted_usernames) # Assign viewer role to collection. rights_manager.create_new_collection_rights( self.COLLECTION_ID, self.owner_id) rights_manager.assign_role_for_collection( self.admin_id, self.COLLECTION_ID, self.viewer_id, rights_manager.ROLE_VIEWER) rights_manager.publish_collection(self.owner_id, self.COLLECTION_ID) self.login(self.VIEWER_EMAIL) # Call get handler to return the csrf token. response = self.testapp.get( '%s/%s' % (feconf.COLLECTION_URL_PREFIX, self.COLLECTION_ID)) csrf_token = self.get_csrf_token_from_response(response) # Ensure viewers do not have access to the PUT Handler. json_response = self.put_json( '%s/%s' % (feconf.EDITABLE_COLLECTION_DATA_URL_PREFIX, self.COLLECTION_ID), self.json_dict, expect_errors=True, csrf_token=csrf_token, expected_status_int=401) self.assertEqual(json_response['code'], 401) self.logout()
def _create_collection(committer_id, collection, commit_message, commit_cmds): """Ensures that rights for a new collection are saved first. This is because _save_collection() depends on the rights object being present to tell it whether to do strict validation or not. """ # This line is needed because otherwise a rights object will be created, # but the creation of an collection object will fail. collection.validate(strict=False) rights_manager.create_new_collection_rights(collection.id, committer_id) model = collection_models.CollectionModel( id=collection.id, category=collection.category, title=collection.title, objective=collection.objective, language_code=collection.language_code, tags=collection.tags, schema_version=collection.schema_version, nodes=[ collection_node.to_dict() for collection_node in collection.nodes ], ) model.commit(committer_id, commit_message, commit_cmds) collection.version += 1 create_collection_summary(collection.id, committer_id)
def test_migrate_colections_failing_strict_validation(self): """Tests that the collection migration job migrates collections which do not pass strict validation. """ # Save a collection without an objective or explorations in version 1. collection_title = 'A title' collection_category = 'A category' rights_manager.create_new_collection_rights(self.COLLECTION_ID, self.albert_id) model = collection_models.CollectionModel( id=self.COLLECTION_ID, category=collection_title, title=collection_category, objective='', tags=[], schema_version=2, ) model.commit(self.albert_id, 'Made a new collection!', [{ 'cmd': collection_services.CMD_CREATE_NEW, 'title': collection_title, 'category': collection_category, }]) # Start migration job on sample collection. job_id = (collection_jobs_one_off.CollectionMigrationJob.create_new()) collection_jobs_one_off.CollectionMigrationJob.enqueue(job_id) # This running without errors indicates the collection is migrated. self.process_and_flush_pending_tasks() # Check the version number of the new model new_model = collection_models.CollectionModel.get(self.COLLECTION_ID) self.assertEqual(new_model.schema_version, feconf.CURRENT_COLLECTION_SCHEMA_VERSION)
def _create_collection(committer_id, collection, commit_message, commit_cmds): """Ensures that rights for a new collection are saved first. This is because _save_collection() depends on the rights object being present to tell it whether to do strict validation or not. """ # This line is needed because otherwise a rights object will be created, # but the creation of an collection object will fail. collection.validate(strict=False) rights_manager.create_new_collection_rights(collection.id, committer_id) model = collection_models.CollectionModel( id=collection.id, category=collection.category, title=collection.title, objective=collection.objective, language_code=collection.language_code, tags=collection.tags, schema_version=collection.schema_version, nodes=[ collection_node.to_dict() for collection_node in collection.nodes ], ) model.commit(committer_id, commit_message, commit_cmds) collection.version += 1 create_collection_summary(collection.id, committer_id)
def test_editable_collection_handler_put_cannot_access(self): """Check that non-editors cannot access editable put handler.""" whitelisted_usernames = [self.EDITOR_USERNAME, self.VIEWER_USERNAME] self.set_collection_editors(whitelisted_usernames) # Assign viewer role to collection. rights_manager.create_new_collection_rights( self.COLLECTION_ID, self.owner_id) rights_manager.assign_role_for_collection( self.moderator, self.COLLECTION_ID, self.viewer_id, rights_domain.ROLE_VIEWER) rights_manager.publish_collection(self.owner, self.COLLECTION_ID) self.login(self.VIEWER_EMAIL) # Call get handler to return the csrf token. csrf_token = self.get_new_csrf_token() # Ensure viewers do not have access to the PUT Handler. self.put_json( '%s/%s' % ( feconf.COLLECTION_EDITOR_DATA_URL_PREFIX, self.COLLECTION_ID), self.json_dict, csrf_token=csrf_token, expected_status_int=401) self.logout()
def test_cannot_put_long_commit_message(self): """Check that putting a long commit message is denied.""" rights_manager.create_new_collection_rights( self.COLLECTION_ID, self.owner_id) rights_manager.publish_collection(self.owner, self.COLLECTION_ID) self.login(self.OWNER_EMAIL) long_message_dict = self.json_dict.copy() long_message_dict['commit_message'] = ( 'a' * (feconf.MAX_COMMIT_MESSAGE_LENGTH + 1)) # Call get handler to return the csrf token. csrf_token = self.get_new_csrf_token() json_response = self.put_json( '%s/%s' % ( feconf.COLLECTION_EDITOR_DATA_URL_PREFIX, self.COLLECTION_ID), long_message_dict, csrf_token=csrf_token, expected_status_int=400) self.assertEqual( json_response['error'], 'Commit messages must be at most 1000 characters long.' ) self.logout()
def test_migrate_colections_failing_strict_validation(self): """Tests that the collection migration job migrates collections which do not pass strict validation. """ # Save a collection without an objective or explorations in version 1. collection_title = 'A title' collection_category = 'A category' rights_manager.create_new_collection_rights( self.COLLECTION_ID, self.albert_id) model = collection_models.CollectionModel( id=self.COLLECTION_ID, category=collection_title, title=collection_category, objective='', tags=[], schema_version=2, ) model.commit(self.albert_id, 'Made a new collection!', [{ 'cmd': collection_services.CMD_CREATE_NEW, 'title': collection_title, 'category': collection_category, }]) # Start migration job on sample collection. job_id = ( collection_jobs_one_off.CollectionMigrationJob.create_new()) collection_jobs_one_off.CollectionMigrationJob.enqueue(job_id) # This running without errors indicates the collection is migrated. self.process_and_flush_pending_tasks() # Check the version number of the new model new_model = collection_models.CollectionModel.get(self.COLLECTION_ID) self.assertEqual( new_model.schema_version, feconf.CURRENT_COLLECTION_SCHEMA_VERSION)
def _create_collection(committer_id, collection, commit_message, commit_cmds): """Creates a new collection, and ensures that rights for a new collection are saved first. This is because _save_collection() depends on the rights object being present to tell it whether to do strict validation or not. Args: committer_id: str. ID of the committer. collection: Collection. collection domain object. commit_message: str. A description of changes made to the collection. commit_cmds: list(dict). A list of change commands made to the given collection. """ # This line is needed because otherwise a rights object will be created, # but the creation of an collection object will fail. collection.validate(strict=False) rights_manager.create_new_collection_rights(collection.id, committer_id) model = collection_models.CollectionModel( id=collection.id, category=collection.category, title=collection.title, objective=collection.objective, language_code=collection.language_code, tags=collection.tags, schema_version=collection.schema_version, collection_contents={ 'nodes': [ collection_node.to_dict() for collection_node in collection.nodes ] }, ) model.commit(committer_id, commit_message, commit_cmds) collection.version += 1 create_collection_summary(collection.id, committer_id)
def test_editable_collection_handler_put_cannot_access(self): """Check that non-editors cannot access editable put handler""" whitelisted_usernames = [self.EDITOR_USERNAME, self.VIEWER_USERNAME] self.set_collection_editors(whitelisted_usernames) # Assign viewer role to collection. rights_manager.create_new_collection_rights(self.COLLECTION_ID, self.owner_id) rights_manager.assign_role_for_collection(self.admin, self.COLLECTION_ID, self.viewer_id, rights_manager.ROLE_VIEWER) rights_manager.publish_collection(self.owner, self.COLLECTION_ID) self.login(self.VIEWER_EMAIL) # Call get handler to return the csrf token. response = self.testapp.get( '%s/%s' % (feconf.COLLECTION_URL_PREFIX, self.COLLECTION_ID)) csrf_token = self.get_csrf_token_from_response(response) # Ensure viewers do not have access to the PUT Handler. json_response = self.put_json( '%s/%s' % (feconf.EDITABLE_COLLECTION_DATA_URL_PREFIX, self.COLLECTION_ID), self.json_dict, expect_errors=True, csrf_token=csrf_token, expected_status_int=401) self.assertEqual(json_response['code'], 401) self.logout()
def test_editable_collection_handler_put_can_access(self): """Check that editors can access put handler""" whitelisted_usernames = [self.EDITOR_USERNAME, self.VIEWER_USERNAME] self.set_config_property( config_domain.WHITELISTED_COLLECTION_EDITOR_USERNAMES, whitelisted_usernames) rights_manager.create_new_collection_rights(self.COLLECTION_ID, self.owner_id) rights_manager.assign_role_for_collection(self.admin_id, self.COLLECTION_ID, self.editor_id, rights_manager.ROLE_EDITOR) rights_manager.publish_collection(self.owner_id, self.COLLECTION_ID) self.login(self.EDITOR_EMAIL) # Call get handler to return the csrf token. response = self.testapp.get( '%s/%s' % (feconf.COLLECTION_URL_PREFIX, self.COLLECTION_ID)) csrf_token = self.get_csrf_token_from_response(response) json_response = self.put_json( '%s/%s' % (feconf.EDITABLE_COLLECTION_DATA_URL_PREFIX, self.COLLECTION_ID), self.json_dict, csrf_token=csrf_token) self.assertEqual(self.COLLECTION_ID, json_response['collection']['id']) self.assertEqual(2, json_response['collection']['version']) self.logout()
def test_migration_job_migrates_collection_nodes(self): """Tests that the collection migration job migrates content from nodes to collection_contents if collection_contents is empty. """ with self.swap(collection_models, 'CollectionModel', MockCollectionModel): # Create an exploration to put in the collection. self.save_new_default_exploration(self.EXP_ID, self.albert_id) node = collection_domain.CollectionNode.create_default_node( self.EXP_ID) # Create a collection directly using the model, so that the # collection nodes are stored in the 'nodes' property rather # than the 'collection_contents' property. collection_title = 'A title' collection_category = 'A category' rights_manager.create_new_collection_rights( self.COLLECTION_ID, self.albert_id) model = collection_models.CollectionModel( id=self.COLLECTION_ID, category=collection_category, title=collection_title, objective='An objective', tags=[], schema_version=2, nodes=[{ 'exploration_id': self.EXP_ID, 'prerequisite_skills': [], 'acquired_skills': [] }], ) model.commit(self.albert_id, 'Made a new collection!', [{ 'cmd': collection_services.CMD_CREATE_NEW, 'title': collection_title, 'category': collection_category, }]) # Save a collection summary object for indexing. The explicit commit # does not create a summary object, which is needed for the # job to update the index after updating the collection. (collection_services. regenerate_collection_summary_with_new_contributor( model.id, self.albert_id)) # Check that collection_contents is empty. self.assertEqual(model.collection_contents, {}) # Run the job. This should populate collection_contents. job_id = (collection_jobs_one_off.CollectionMigrationOneOffJob. create_new()) collection_jobs_one_off.CollectionMigrationOneOffJob.enqueue( job_id) self.process_and_flush_pending_mapreduce_tasks() new_model = collection_models.CollectionModel.get( self.COLLECTION_ID) self.assertEqual(new_model.collection_contents, {'nodes': [node.to_dict()]})
def test_migrate_collections_failing_strict_validation(self): """Tests that the collection migration job migrates collections which do not pass strict validation. """ # Save a collection without an objective or explorations in version 1. with self.swap( collection_models, 'CollectionModel', MockCollectionModel): collection_title = 'A title' collection_category = 'A category' # Create an exploration to put in the collection. self.save_new_default_exploration(self.EXP_ID, self.albert_id) rights_manager.create_new_collection_rights( self.COLLECTION_ID, self.albert_id) model = collection_models.CollectionModel( id=self.COLLECTION_ID, category=collection_title, title=collection_category, objective='', tags=[], nodes=[{ 'exploration_id': self.EXP_ID, 'prerequisite_skills': [], 'acquired_skills': [] }], schema_version=2, ) model.commit(self.albert_id, 'Made a new collection!', [{ 'cmd': collection_services.CMD_CREATE_NEW, 'title': collection_title, 'category': collection_category, }]) # Save a collection summary object for indexing. The explicit commit # does not create a summary object, which is needed for the # job to update the index after updating the collection. ( collection_services .regenerate_collection_summary_with_new_contributor( model.id, self.albert_id)) # Start migration job on sample collection. job_id = ( collection_jobs_one_off.CollectionMigrationOneOffJob .create_new()) collection_jobs_one_off.CollectionMigrationOneOffJob.enqueue(job_id) # This running without errors indicates the collection is migrated. self.process_and_flush_pending_mapreduce_tasks() # Check the version number of the new model. new_model = collection_models.CollectionModel.get( self.COLLECTION_ID) self.assertEqual( new_model.schema_version, feconf.CURRENT_COLLECTION_SCHEMA_VERSION)
def test_editable_collection_handler_put_with_invalid_payload_version( self): whitelisted_usernames = [self.EDITOR_USERNAME, self.VIEWER_USERNAME] self.set_collection_editors(whitelisted_usernames) rights_manager.create_new_collection_rights(self.COLLECTION_ID, self.owner_id) rights_manager.assign_role_for_collection(self.moderator, self.COLLECTION_ID, self.editor_id, rights_domain.ROLE_EDITOR) rights_manager.publish_collection(self.owner, self.COLLECTION_ID) self.login(self.EDITOR_EMAIL) # Call get handler to return the csrf token. csrf_token = self.get_new_csrf_token() # Raises error as version is None. sample_change_list = [{ 'cmd': 'edit_collection_property', 'property_name': 'title', 'new_value': 'A new title' }] json_response = self.put_json( '%s/%s' % (feconf.COLLECTION_EDITOR_DATA_URL_PREFIX, self.COLLECTION_ID), { 'version': None, 'change_list': sample_change_list }, csrf_token=csrf_token, expected_status_int=400) self.assertEqual(json_response['error'], 'Invalid POST request: a version must be specified.') # Raises error as version from payload does not match the collection # version. json_response = self.put_json( '%s/%s' % (feconf.COLLECTION_EDITOR_DATA_URL_PREFIX, self.COLLECTION_ID), { 'version': 2, 'change_list': sample_change_list }, csrf_token=csrf_token, expected_status_int=400) self.assertEqual( json_response['error'], 'Trying to update version 1 of collection from version 2, ' 'which is too old. Please reload the page and try again.') self.logout()
def test_migration_job_skips_collection_failing_validation(self): """Tests that the collection migration job skips the collection failing validation and does not attempt to migrate. """ # Create a collection directly using the model and with an # invalid language code. collection_title = 'A title' collection_category = 'A category' collection_language_code = 'abc' collection_schema_version = 2 rights_manager.create_new_collection_rights(self.COLLECTION_ID, self.albert_id) model = collection_models.CollectionModel( id=self.COLLECTION_ID, category=collection_title, title=collection_category, language_code=collection_language_code, objective='An objective', tags=[], schema_version=collection_schema_version) model.commit(self.albert_id, 'Made a new collection!', [{ 'cmd': collection_services.CMD_CREATE_NEW, 'title': collection_title, 'category': collection_category, }]) # Start migration job on sample collection. job_id = ( collection_jobs_one_off.CollectionMigrationOneOffJob.create_new()) collection_jobs_one_off.CollectionMigrationOneOffJob.enqueue(job_id) # This running without errors indicates the collection failing # validation is being ignored. self.process_and_flush_pending_tasks() # Check that the version number of the new model is same as old model. new_model = collection_models.CollectionModel.get(self.COLLECTION_ID) self.assertEqual(new_model.schema_version, collection_schema_version) output = (collection_jobs_one_off.CollectionMigrationOneOffJob. get_output(job_id)) expected = [[ u'validation_error', [ u'Collection %s failed validation: Invalid ' u'language code: %s' % (self.COLLECTION_ID, collection_language_code) ] ]] self.assertEqual(expected, [ast.literal_eval(x) for x in output])
def test_migration_job_migrates_collection_nodes(self): """Tests that the collection migration job migrates content from nodes to collection_contents if collection_contents is empty. """ # Create an exploration to put in the collection. self.save_new_default_exploration(self.EXP_ID, self.albert_id) node = collection_domain.CollectionNode.create_default_node( self.EXP_ID) # Create a collection directly using the model, so that the collection # nodes are stored in the 'nodes' property rather than the # 'collection_contents' property. collection_title = 'A title' collection_category = 'A category' rights_manager.create_new_collection_rights(self.COLLECTION_ID, self.albert_id) model = collection_models.CollectionModel( id=self.COLLECTION_ID, category=collection_category, title=collection_title, objective='An objective', tags=[], schema_version=2, nodes=[{ 'exploration_id': self.EXP_ID, 'prerequisite_skills': [], 'acquired_skills': [] }], ) model.commit(self.albert_id, 'Made a new collection!', [{ 'cmd': collection_services.CMD_CREATE_NEW, 'title': collection_title, 'category': collection_category, }]) # Check that collection_contents is empty self.assertEqual(model.collection_contents, {}) # Run the job. This should populate collection_contents. job_id = (collection_jobs_one_off.CollectionMigrationJob.create_new()) collection_jobs_one_off.CollectionMigrationJob.enqueue(job_id) self.process_and_flush_pending_tasks() new_model = collection_models.CollectionModel.get(self.COLLECTION_ID) self.assertEqual(new_model.collection_contents, { 'nodes': [node.to_dict()], 'skills': {}, 'next_skill_id': 0 })
def test_one_collection_model_with_nodes(self): with self.swap(collection_models, 'CollectionModel', MockCollectionModel): self.save_new_default_exploration(self.EXP_ID, self.albert_id) rights_manager.create_new_collection_rights( self.COLLECTION_ID, self.albert_id) original_collection_model = (collection_models.CollectionModel( id=self.COLLECTION_ID, category='category1', title='title1', objective='An objective', tags=[], schema_version=2, nodes=[{ 'prerequisite_skills': [], 'acquired_skills': [], 'exploration_id': self.EXP_ID, }], )) original_collection_model.commit( self.albert_id, 'Made a new collection!', [{ 'cmd': collection_services.CMD_CREATE_NEW, 'title': 'title1', 'category': 'category1', }]) self.assertIsNotNone(original_collection_model.nodes) self.assertIn('nodes', original_collection_model._values) # pylint: disable=protected-access self.assertIn('nodes', original_collection_model._properties) # pylint: disable=protected-access output = self._run_one_off_job() self.assertItemsEqual([['SUCCESS_REMOVED - CollectionModel', 1]], output) migrated_collection_model = ( collection_models.CollectionModel.get_by_id( self.COLLECTION_ID)) self.assertNotIn('nodes', migrated_collection_model._values) # pylint: disable=protected-access self.assertNotIn('nodes', migrated_collection_model._properties) # pylint: disable=protected-access output = self._run_one_off_job() self.assertItemsEqual( [['SUCCESS_ALREADY_REMOVED - CollectionModel', 1]], output)
def test_migration_job_migrates_collection_nodes(self): """Tests that the collection migration job migrates content from nodes to collection_contents if collection_contents is empty. """ # Create an exploration to put in the collection. self.save_new_default_exploration(self.EXP_ID, self.albert_id) node = collection_domain.CollectionNode.create_default_node(self.EXP_ID) # Create a collection directly using the model, so that the collection # nodes are stored in the 'nodes' property rather than the # 'collection_contents' property. collection_title = 'A title' collection_category = 'A category' rights_manager.create_new_collection_rights( self.COLLECTION_ID, self.albert_id) model = collection_models.CollectionModel( id=self.COLLECTION_ID, category=collection_category, title=collection_title, objective='An objective', tags=[], schema_version=2, nodes=[node.to_dict()], ) model.commit(self.albert_id, 'Made a new collection!', [{ 'cmd': collection_services.CMD_CREATE_NEW, 'title': collection_title, 'category': collection_category, }]) # Check that collection_contents is empty self.assertEqual(model.collection_contents, {}) # Run the job. This should populate collection_contents. job_id = ( collection_jobs_one_off.CollectionMigrationJob.create_new()) collection_jobs_one_off.CollectionMigrationJob.enqueue(job_id) self.process_and_flush_pending_tasks() new_model = collection_models.CollectionModel.get(self.COLLECTION_ID) self.assertEqual( new_model.collection_contents, {'nodes': [node.to_dict()]})
def test_editable_collection_handler_put_can_access(self): """Check that editors can access put handler.""" whitelisted_usernames = [self.EDITOR_USERNAME, self.VIEWER_USERNAME] self.set_collection_editors(whitelisted_usernames) rights_manager.create_new_collection_rights( self.COLLECTION_ID, self.owner_id) rights_manager.assign_role_for_collection( self.admin, self.COLLECTION_ID, self.editor_id, rights_manager.ROLE_EDITOR) rights_manager.publish_collection(self.owner, self.COLLECTION_ID) self.login(self.EDITOR_EMAIL) # Call get handler to return the csrf token. response = self.get_html_response( '%s/%s' % ( feconf.COLLECTION_URL_PREFIX, self.COLLECTION_ID)) csrf_token = self.get_csrf_token_from_response(response) json_response = self.put_json( '%s/%s' % ( feconf.COLLECTION_EDITOR_DATA_URL_PREFIX, self.COLLECTION_ID), self.json_dict, csrf_token=csrf_token) self.assertEqual(self.COLLECTION_ID, json_response['collection']['id']) self.assertEqual(2, json_response['collection']['version']) # Check that title is changed. response = self.get_html_response( '%s/%s' % ( feconf.COLLECTION_EDITOR_URL_PREFIX, self.COLLECTION_ID)) response.mustcontain(self.json_dict['change_list'][0]['new_value']) self.logout()