/
githubmirror
/
oppia
Обзор
Документация
Войти
/
githubmirror
/
oppia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
core/controllers/topic_viewer_test.py
1 102 строки
43 KB
Kartik Suryavanshi
[GSoC 2026] M1.7 - Fix part of #19614: Implement arc-based chapter groupings in story editor (#26393)
04 июл 2026, 22:03
Не верифицирован
04 июл 2026, 22:03
f9e0be9
Код
Авторство
О чём код?
# Copyright 2018 The Oppia Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Tests for the topic viewer page.""" from __future__ import annotations from core import feature_flag_list, feconf from core.constants import constants from core.domain import ( classroom_config_services, platform_parameter_list, question_services, skill_services, state_domain, story_domain, story_services, topic_domain, topic_services, translation_domain, user_services, voiceover_domain, voiceover_services, ) from core.platform import models from core.tests import test_utils MYPY = False if MYPY: # pragma: no cover from mypy_imports import translation_models (translation_models,) = models.Registry.import_models( [models.Names.TRANSLATION] ) class BaseTopicViewerControllerTests(test_utils.GenericTestBase): def setUp(self) -> None: """Completes the sign-up process for the various users.""" super().setUp() self.signup(self.NEW_USER_EMAIL, self.NEW_USER_USERNAME) self.user_id = self.get_user_id_from_email(self.NEW_USER_EMAIL) self.signup(self.CURRICULUM_ADMIN_EMAIL, self.CURRICULUM_ADMIN_USERNAME) self.admin_id = self.get_user_id_from_email(self.CURRICULUM_ADMIN_EMAIL) self.set_curriculum_admins([self.CURRICULUM_ADMIN_USERNAME]) self.admin = user_services.get_user_actions_info(self.admin_id) self.topic_id = 'topic' self.story_id_1 = 'story_id_1' self.story_id_2 = 'story_id_2' self.skill_id_1 = skill_services.get_new_skill_id() self.skill_id_2 = skill_services.get_new_skill_id() self.story_1 = story_domain.Story.create_default_story( self.story_id_1, 'story_title', 'description', self.topic_id, 'story-frag-one', ) self.story_1.description = 'story_description' self.story_2 = story_domain.Story.create_default_story( self.story_id_2, 'story_title', 'description', self.topic_id, 'story-frag-two', ) self.story_2.description = 'story_description' self.topic = topic_domain.Topic.create_default_topic( self.topic_id, 'public_topic_name', 'public', 'description', 'fragm' ) self.topic.uncategorized_skill_ids.append(self.skill_id_1) self.topic.subtopics.append( topic_domain.Subtopic( 1, 'subtopic_name', [self.skill_id_2], 'image.svg', constants.ALLOWED_THUMBNAIL_BG_COLORS['subtopic'][0], 21131, 'subtopic-name', ) ) self.topic.next_subtopic_id = 2 self.topic.thumbnail_filename = 'Image.svg' self.topic.thumbnail_bg_color = constants.ALLOWED_THUMBNAIL_BG_COLORS[ 'topic' ][0] self.topic.canonical_story_references.append( topic_domain.StoryReference.create_default_story_reference( self.story_id_1 ) ) self.topic.additional_story_references.append( topic_domain.StoryReference.create_default_story_reference( self.story_id_2 ) ) self.topic.meta_tag_content = 'topic meta content' self.topic.page_title_fragment_for_web = 'topic page title' self.topic.skill_ids_for_diagnostic_test = [self.skill_id_2] topic_services.save_new_topic(self.admin_id, self.topic) story_services.save_new_story(self.admin_id, self.story_1) story_services.save_new_story(self.admin_id, self.story_2) topic_services.publish_topic(self.topic_id, self.admin_id) topic_services.publish_story( self.topic_id, self.story_id_1, self.admin_id ) topic_services.publish_story( self.topic_id, self.story_id_2, self.admin_id ) self.save_new_skill( self.skill_id_1, self.user_id, description='Skill Description 1' ) self.save_new_skill( self.skill_id_2, self.user_id, description='Skill Description 2' ) skill_services.create_user_skill_mastery( self.user_id, self.skill_id_1, 0.3 ) skill_services.create_user_skill_mastery( self.user_id, self.skill_id_2, 0.5 ) self.math_classroom = self.save_new_valid_classroom() class TopicPageDataHandlerTests( BaseTopicViewerControllerTests, test_utils.EmailTestBase ): def test_get_with_no_user_logged_in(self) -> None: self.math_classroom.topic_id_to_prerequisite_topic_ids = { self.topic_id: [] } classroom_config_services.update_classroom(self.math_classroom) json_response = self.get_json( '%s/math/%s' % (feconf.TOPIC_DATA_HANDLER, 'public') ) expected_dict = { 'topic_name': 'public_topic_name', 'topic_id': self.topic_id, 'canonical_story_dicts': [ { 'id': self.story_1.id, 'title': self.story_1.title, 'description': self.story_1.description, 'node_titles': [], 'thumbnail_filename': None, 'thumbnail_bg_color': None, 'story_is_published': True, 'completed_node_titles': [], 'url_fragment': 'story-frag-one', 'all_node_dicts': [], } ], 'additional_story_dicts': [ { 'id': self.story_2.id, 'title': self.story_2.title, 'description': self.story_2.description, 'node_titles': [], 'thumbnail_filename': None, 'thumbnail_bg_color': None, 'story_is_published': True, 'completed_node_titles': [], 'url_fragment': 'story-frag-two', 'all_node_dicts': [], } ], 'uncategorized_skill_ids': [self.skill_id_1], 'subtopics': [ { 'thumbnail_filename': 'image.svg', 'thumbnail_bg_color': '#FFFFFF', 'thumbnail_size_in_bytes': 21131, 'skill_ids': [self.skill_id_2], 'id': 1, 'title': 'subtopic_name', 'url_fragment': 'subtopic-name', } ], 'degrees_of_mastery': { self.skill_id_1: None, self.skill_id_2: None, }, 'skill_descriptions': { self.skill_id_1: 'Skill Description 1', self.skill_id_2: 'Skill Description 2', }, 'practice_tab_is_displayed': False, 'classroom_name': 'math', } self.assertDictContainsSubset(expected_dict, json_response) # Test with no classroom assigned. self.math_classroom.topic_id_to_prerequisite_topic_ids = {} expected_dict['classroom_name'] = None classroom_config_services.update_classroom(self.math_classroom) json_response = self.get_json( '%s/staging/%s' % (feconf.TOPIC_DATA_HANDLER, 'public') ) self.assertDictContainsSubset(expected_dict, json_response) @test_utils.set_platform_parameters( [ (platform_parameter_list.ParamName.SERVER_CAN_SEND_EMAILS, True), ( platform_parameter_list.ParamName.ADMIN_EMAIL_ADDRESS, 'testadmin@example.com', ), ( platform_parameter_list.ParamName.SYSTEM_EMAIL_ADDRESS, 'system@example.com', ), (platform_parameter_list.ParamName.SYSTEM_EMAIL_NAME, '.'), ] ) def test_get_with_user_logged_in(self) -> None: skill_services.delete_skill(self.admin_id, self.skill_id_1) self.login(self.NEW_USER_EMAIL) admin_email_address = 'testadmin@example.com' messages = self._get_sent_email_messages(admin_email_address) self.assertEqual(len(messages), 0) json_response = self.get_json( '%s/staging/%s' % (feconf.TOPIC_DATA_HANDLER, 'public') ) messages = self._get_sent_email_messages(admin_email_address) expected_email_html_body = ( 'The deleted skills: %s are still' ' present in topic with id %s' % (self.skill_id_1, self.topic_id) ) self.assertEqual(len(messages), 1) self.assertIn(expected_email_html_body, messages[0].html) expected_dict = { 'topic_name': 'public_topic_name', 'topic_id': self.topic_id, 'canonical_story_dicts': [ { 'id': self.story_1.id, 'title': self.story_1.title, 'description': self.story_1.description, 'node_titles': [], 'thumbnail_filename': None, 'thumbnail_bg_color': None, 'story_is_published': True, 'completed_node_titles': [], 'url_fragment': 'story-frag-one', 'all_node_dicts': [], } ], 'additional_story_dicts': [ { 'id': self.story_2.id, 'title': self.story_2.title, 'description': self.story_2.description, 'node_titles': [], 'thumbnail_filename': None, 'thumbnail_bg_color': None, 'story_is_published': True, 'completed_node_titles': [], 'url_fragment': 'story-frag-two', 'all_node_dicts': [], } ], 'uncategorized_skill_ids': [self.skill_id_1], 'subtopics': [ { 'thumbnail_filename': 'image.svg', 'thumbnail_bg_color': '#FFFFFF', 'thumbnail_size_in_bytes': 21131, 'skill_ids': [self.skill_id_2], 'id': 1, 'title': 'subtopic_name', 'url_fragment': 'subtopic-name', } ], 'degrees_of_mastery': {self.skill_id_1: 0.3, self.skill_id_2: 0.5}, 'skill_descriptions': {self.skill_id_2: 'Skill Description 2'}, 'practice_tab_is_displayed': False, 'classroom_name': None, } self.assertDictContainsSubset(expected_dict, json_response) self.logout() def test_get_with_story_node_explorations(self) -> None: exploration_id = 'exp_id' self.save_new_valid_exploration( exploration_id, self.admin_id, end_state_name='End' ) self.publish_exploration(self.admin_id, exploration_id) no_translation_exploration_id = 'exp_id_2' self.save_new_valid_exploration( no_translation_exploration_id, self.admin_id, end_state_name='End' ) self.publish_exploration(self.admin_id, no_translation_exploration_id) voiceover_services.save_language_accent_support( language_codes_mapping={'en': {'en-US': True, 'en-UK': True}} ) manual_voiceover_dict: state_domain.VoiceoverDict = { 'filename': 'filename1.mp3', 'file_size_bytes': 3000, 'needs_update': False, 'duration_secs': 6.1, } entity_voiceovers = voiceover_domain.EntityVoiceovers( entity_id=exploration_id, entity_type=feconf.ENTITY_TYPE_EXPLORATION, entity_version=1, language_accent_code='en-US', voiceovers_mapping={ 'content_id_0': { 'manual': state_domain.Voiceover.from_dict( manual_voiceover_dict ), 'auto': None, } }, automated_voiceovers_audio_offsets_msecs={}, ) voiceover_services.save_entity_voiceovers(entity_voiceovers) second_entity_voiceovers = voiceover_domain.EntityVoiceovers( entity_id=exploration_id, entity_type=feconf.ENTITY_TYPE_EXPLORATION, entity_version=1, language_accent_code='en-UK', voiceovers_mapping={ 'content_id_0': { 'manual': state_domain.Voiceover.from_dict( manual_voiceover_dict ), 'auto': None, } }, automated_voiceovers_audio_offsets_msecs={}, ) voiceover_services.save_entity_voiceovers(second_entity_voiceovers) translation_models.EntityTranslationsModel.create_new( 'exploration', exploration_id, 1, 'en', {} ).put() change_list = [ story_domain.StoryChange( { 'cmd': story_domain.CMD_ADD_STORY_NODE, 'node_id': 'node_1', 'title': 'Node 1', } ), story_domain.StoryChange( { 'cmd': story_domain.CMD_UPDATE_STORY_NODE_PROPERTY, 'property_name': story_domain.STORY_NODE_PROPERTY_EXPLORATION_ID, 'node_id': 'node_1', 'old_value': None, 'new_value': exploration_id, } ), story_domain.StoryChange( { 'cmd': story_domain.CMD_UPDATE_STORY_NODE_PROPERTY, 'property_name': story_domain.STORY_NODE_PROPERTY_STATUS, 'node_id': 'node_1', 'old_value': constants.STORY_NODE_STATUS_DRAFT, 'new_value': constants.STORY_NODE_STATUS_PUBLISHED, } ), story_domain.StoryChange( { 'cmd': story_domain.CMD_ADD_STORY_NODE, 'node_id': 'node_2', 'title': 'Node 2', } ), story_domain.StoryChange( { 'cmd': story_domain.CMD_UPDATE_STORY_NODE_PROPERTY, 'property_name': story_domain.STORY_NODE_PROPERTY_EXPLORATION_ID, 'node_id': 'node_2', 'old_value': None, 'new_value': no_translation_exploration_id, } ), story_domain.StoryChange( { 'cmd': story_domain.CMD_UPDATE_STORY_NODE_PROPERTY, 'property_name': story_domain.STORY_NODE_PROPERTY_STATUS, 'node_id': 'node_2', 'old_value': constants.STORY_NODE_STATUS_DRAFT, 'new_value': constants.STORY_NODE_STATUS_PUBLISHED, } ), ] story_services.update_story( self.admin_id, self.story_id_1, change_list, 'Added nodes.' ) self.login(self.NEW_USER_EMAIL) json_response = self.get_json( '%s/staging/%s' % (feconf.TOPIC_DATA_HANDLER, 'public') ) canonical_stories = json_response['canonical_story_dicts'] story_with_nodes = [ s for s in canonical_stories if s['id'] == self.story_id_1 ] self.assertEqual(len(story_with_nodes), 1) story_dict = story_with_nodes[0] self.assertEqual(len(story_dict['all_node_dicts']), 2) node_dict = story_dict['all_node_dicts'][0] self.assertEqual(node_dict['id'], 'node_1') self.assertEqual(node_dict['exploration_id'], exploration_id) self.assertEqual(node_dict['available_text_language_codes'], ['en']) self.assertEqual( set(node_dict['available_voiceover_language_codes']), {'en-US', 'en-UK'}, ) accent_descriptions = node_dict[ 'available_voiceover_language_accent_descriptions' ] language_accent_codes_to_descriptions = ( voiceover_services.get_language_accent_codes_to_descriptions() ) self.assertEqual(set(accent_descriptions.keys()), {'en-US', 'en-UK'}) self.assertEqual( accent_descriptions['en-US'], language_accent_codes_to_descriptions['en-US'], ) self.assertEqual( accent_descriptions['en-UK'], language_accent_codes_to_descriptions.get('en-UK', 'en-UK'), ) second_node_dict = story_dict['all_node_dicts'][1] self.assertEqual(second_node_dict['id'], 'node_2') self.assertEqual( second_node_dict['exploration_id'], no_translation_exploration_id ) self.assertEqual( second_node_dict['available_text_language_codes'], ['en'] ) self.assertEqual( second_node_dict['available_voiceover_language_codes'], [] ) self.logout() def test_get_with_story_node_voiceover_skips_empty_voiceovers_mapping( self, ) -> None: exploration_id = 'exp_id_empty_vo' self.save_new_valid_exploration( exploration_id, self.admin_id, end_state_name='End' ) self.publish_exploration(self.admin_id, exploration_id) voiceover_services.save_language_accent_support( language_codes_mapping={'en': {'en-US': True}} ) # Entity voiceovers with an empty voiceovers_mapping should be skipped. empty_entity_voiceovers = voiceover_domain.EntityVoiceovers( entity_id=exploration_id, entity_type=feconf.ENTITY_TYPE_EXPLORATION, entity_version=1, language_accent_code='en-US', voiceovers_mapping={}, automated_voiceovers_audio_offsets_msecs={}, ) voiceover_services.save_entity_voiceovers(empty_entity_voiceovers) change_list = [ story_domain.StoryChange( { 'cmd': story_domain.CMD_ADD_STORY_NODE, 'node_id': 'node_1', 'title': 'Node 1', } ), story_domain.StoryChange( { 'cmd': story_domain.CMD_UPDATE_STORY_NODE_PROPERTY, 'property_name': ( story_domain.STORY_NODE_PROPERTY_EXPLORATION_ID ), 'node_id': 'node_1', 'old_value': None, 'new_value': exploration_id, } ), story_domain.StoryChange( { 'cmd': story_domain.CMD_UPDATE_STORY_NODE_PROPERTY, 'property_name': story_domain.STORY_NODE_PROPERTY_STATUS, 'node_id': 'node_1', 'old_value': constants.STORY_NODE_STATUS_DRAFT, 'new_value': constants.STORY_NODE_STATUS_PUBLISHED, } ), ] story_services.update_story( self.admin_id, self.story_id_1, change_list, 'Added node.' ) self.login(self.NEW_USER_EMAIL) json_response = self.get_json( '%s/staging/%s' % (feconf.TOPIC_DATA_HANDLER, 'public') ) canonical_stories = json_response['canonical_story_dicts'] story_with_nodes = [ s for s in canonical_stories if s['id'] == self.story_id_1 ] self.assertEqual(len(story_with_nodes), 1) node_dict = story_with_nodes[0]['all_node_dicts'][0] self.assertEqual(node_dict['id'], 'node_1') # Because voiceovers_mapping was empty, no accent code should appear. self.assertEqual(node_dict['available_voiceover_language_codes'], []) self.logout() def test_get_with_story_node_voiceover_cross_language_accent_mapping( self, ) -> None: exploration_id = 'exp_id_cross_accent' self.save_new_valid_exploration( exploration_id, self.admin_id, end_state_name='End' ) self.publish_exploration(self.admin_id, exploration_id) # The exploration's displayable language is 'en', so the root set will # be {'en'}. We register 'zh-Hans' as an accent supported under 'en' # in the language_accent mapping so that it is reachable only through # the fallback loop (its root code 'zh' is NOT in {'en'}). voiceover_services.save_language_accent_support( language_codes_mapping={ 'en': {'en-US': True, 'zh-Hans': True}, } ) manual_voiceover_dict: state_domain.VoiceoverDict = { 'filename': 'filename_cross.mp3', 'file_size_bytes': 2000, 'needs_update': False, 'duration_secs': 4.0, } # 'zh-Hans' accent has actual voiceovers; its root 'zh' != 'en' so the # primary `if accent_root_code in displayable_language_roots` check # fails and the fallback `for language_code` loop must handle it. cross_accent_voiceovers = voiceover_domain.EntityVoiceovers( entity_id=exploration_id, entity_type=feconf.ENTITY_TYPE_EXPLORATION, entity_version=1, language_accent_code='zh-Hans', voiceovers_mapping={ 'content_id_0': { 'manual': state_domain.Voiceover.from_dict( manual_voiceover_dict ), 'auto': None, } }, automated_voiceovers_audio_offsets_msecs={}, ) voiceover_services.save_entity_voiceovers(cross_accent_voiceovers) change_list = [ story_domain.StoryChange( { 'cmd': story_domain.CMD_ADD_STORY_NODE, 'node_id': 'node_1', 'title': 'Node 1', } ), story_domain.StoryChange( { 'cmd': story_domain.CMD_UPDATE_STORY_NODE_PROPERTY, 'property_name': ( story_domain.STORY_NODE_PROPERTY_EXPLORATION_ID ), 'node_id': 'node_1', 'old_value': None, 'new_value': exploration_id, } ), story_domain.StoryChange( { 'cmd': story_domain.CMD_UPDATE_STORY_NODE_PROPERTY, 'property_name': story_domain.STORY_NODE_PROPERTY_STATUS, 'node_id': 'node_1', 'old_value': constants.STORY_NODE_STATUS_DRAFT, 'new_value': constants.STORY_NODE_STATUS_PUBLISHED, } ), ] story_services.update_story( self.admin_id, self.story_id_1, change_list, 'Added node.' ) self.login(self.NEW_USER_EMAIL) json_response = self.get_json( '%s/staging/%s' % (feconf.TOPIC_DATA_HANDLER, 'public') ) canonical_stories = json_response['canonical_story_dicts'] story_with_nodes = [ s for s in canonical_stories if s['id'] == self.story_id_1 ] self.assertEqual(len(story_with_nodes), 1) node_dict = story_with_nodes[0]['all_node_dicts'][0] self.assertEqual(node_dict['id'], 'node_1') # 'zh-Hans' was reached through the fallback language_code mapping loop. self.assertIn( 'zh-Hans', node_dict['available_voiceover_language_codes'] ) self.logout() def test_get_with_meta_tag_content(self) -> None: self.topic = topic_domain.Topic.create_default_topic( self.topic_id, 'topic_with_meta', 'topic-with-meta', 'description', 'fragm', ) self.topic.meta_tag_content = 'meta content' topic_services.save_new_topic(self.admin_id, self.topic) topic_services.publish_topic(self.topic_id, self.admin_id) json_response = self.get_json( '%s/staging/%s' % (feconf.TOPIC_DATA_HANDLER, 'topic-with-meta') ) expected_meta_tag_content = 'meta content' self.assertEqual( expected_meta_tag_content, json_response['meta_tag_content'] ) def test_get_with_page_title_fragment_for_web(self) -> None: self.topic = topic_domain.Topic.create_default_topic( self.topic_id, 'topic_with_page_title_fragment_for_web', 'topic-page-title', 'description', 'topic page title', ) topic_services.save_new_topic(self.admin_id, self.topic) topic_services.publish_topic(self.topic_id, self.admin_id) json_response = self.get_json( '%s/staging/%s' % (feconf.TOPIC_DATA_HANDLER, 'topic-page-title') ) expected_page_title_fragment_for_web = 'topic page title' self.assertEqual( expected_page_title_fragment_for_web, json_response['page_title_fragment_for_web'], ) def test_get_with_no_skills_ids(self) -> None: self.topic = topic_domain.Topic.create_default_topic( self.topic_id, 'topic_with_no_skills', 'topic-with-no-skills', 'description', 'fragm', ) topic_services.save_new_topic(self.admin_id, self.topic) topic_services.publish_topic(self.topic_id, self.admin_id) json_response = self.get_json( '%s/staging/%s' % (feconf.TOPIC_DATA_HANDLER, 'topic-with-no-skills') ) expected_dict = { 'topic_name': 'topic_with_no_skills', 'topic_id': self.topic_id, 'canonical_story_dicts': [], 'additional_story_dicts': [], 'uncategorized_skill_ids': [], 'subtopics': [], 'degrees_of_mastery': {}, 'skill_descriptions': {}, 'practice_tab_is_displayed': False, 'classroom_name': None, } self.assertDictContainsSubset(expected_dict, json_response) def test_get_with_five_or_more_questions(self) -> None: number_of_questions = 6 self.topic_id = 'new_topic' self.skill_id_1 = skill_services.get_new_skill_id() self.skill_id_2 = skill_services.get_new_skill_id() self.topic = topic_domain.Topic.create_default_topic( self.topic_id, 'new_topic', 'new-topic', 'description', 'fragm' ) self.topic.uncategorized_skill_ids.append(self.skill_id_1) self.topic.thumbnail_filename = 'Image.svg' self.topic.thumbnail_bg_color = constants.ALLOWED_THUMBNAIL_BG_COLORS[ 'topic' ][0] self.topic.practice_tab_is_displayed = True subtopic_1 = topic_domain.Subtopic.create_default_subtopic( 1, 'Subtopic Title 1', 'url-frag-one' ) subtopic_1.skill_ids = [self.skill_id_2] subtopic_1.url_fragment = 'sub-one-frag' self.topic.subtopics = [subtopic_1] self.topic.next_subtopic_id = 2 self.topic.skill_ids_for_diagnostic_test = [self.skill_id_2] topic_services.save_new_topic(self.admin_id, self.topic) topic_services.publish_topic(self.topic_id, self.admin_id) self.save_new_skill( self.skill_id_1, self.admin_id, description='Skill Description 1' ) for index in range(number_of_questions): question_id = question_services.get_new_question_id() content_id_generator = translation_domain.ContentIdGenerator() default_dest_state_name = '%s' % index self.save_new_question( question_id, self.admin_id, self._create_valid_question_data( default_dest_state_name, content_id_generator ), [self.skill_id_1], content_id_generator.next_content_id_index, ) question_services.create_new_question_skill_link( self.admin_id, question_id, self.skill_id_1, 0.5 ) json_response = self.get_json( '%s/staging/%s' % (feconf.TOPIC_DATA_HANDLER, 'new-topic') ) expected_dict = { 'topic_name': 'new_topic', 'topic_id': self.topic_id, 'canonical_story_dicts': [], 'additional_story_dicts': [], 'uncategorized_skill_ids': [self.skill_id_1], 'subtopics': [subtopic_1.to_dict()], 'degrees_of_mastery': { self.skill_id_1: None, self.skill_id_2: None, }, 'skill_descriptions': {self.skill_id_1: 'Skill Description 1'}, 'practice_tab_is_displayed': True, 'classroom_name': None, } self.assertDictContainsSubset(expected_dict, json_response) self.logout() def test_get_with_twenty_or_more_questions(self) -> None: number_of_questions = 50 self.topic_id = 'new_topic' self.skill_id_1 = skill_services.get_new_skill_id() self.skill_id_2 = skill_services.get_new_skill_id() self.topic = topic_domain.Topic.create_default_topic( self.topic_id, 'new_topic', 'new-topic', 'description', 'fragm' ) self.topic.uncategorized_skill_ids.append(self.skill_id_1) self.topic.thumbnail_filename = 'Image.svg' self.topic.thumbnail_bg_color = constants.ALLOWED_THUMBNAIL_BG_COLORS[ 'topic' ][0] self.topic.practice_tab_is_displayed = True subtopic_1 = topic_domain.Subtopic.create_default_subtopic( 1, 'Subtopic Title 1', 'url-frag-one' ) subtopic_1.skill_ids = [self.skill_id_2] subtopic_1.url_fragment = 'sub-one-frag' self.topic.subtopics = [subtopic_1] self.topic.next_subtopic_id = 2 self.topic.skill_ids_for_diagnostic_test = [self.skill_id_2] topic_services.save_new_topic(self.admin_id, self.topic) topic_services.publish_topic(self.topic_id, self.admin_id) self.save_new_skill( self.skill_id_1, self.admin_id, description='Skill Description 1' ) for index in range(number_of_questions): default_dest_state_name = '%s' % index question_id = question_services.get_new_question_id() content_id_generator = translation_domain.ContentIdGenerator() self.save_new_question( question_id, self.admin_id, self._create_valid_question_data( default_dest_state_name, content_id_generator ), [self.skill_id_1], content_id_generator.next_content_id_index, ) question_services.create_new_question_skill_link( self.admin_id, question_id, self.skill_id_1, 0.5 ) json_response = self.get_json( '%s/staging/%s' % (feconf.TOPIC_DATA_HANDLER, 'new-topic') ) expected_dict = { 'topic_name': 'new_topic', 'topic_id': self.topic_id, 'canonical_story_dicts': [], 'additional_story_dicts': [], 'uncategorized_skill_ids': [self.skill_id_1], 'subtopics': [subtopic_1.to_dict()], 'degrees_of_mastery': { self.skill_id_1: None, self.skill_id_2: None, }, 'skill_descriptions': { self.skill_id_1: 'Skill Description 1', }, 'practice_tab_is_displayed': True, 'classroom_name': None, } self.assertDictContainsSubset(expected_dict, json_response) self.logout() def test_get_with_twenty_or_more_questions_with_multiple_skills( self, ) -> None: number_of_skills = 3 number_of_questions = [1, 2, 2] self.topic_id = 'new_topic' skill_ids = [ skill_services.get_new_skill_id() for _ in range(number_of_skills) ] self.topic = topic_domain.Topic.create_default_topic( self.topic_id, 'new_topic', 'new-topic', 'description', 'fragm' ) for index in range(number_of_skills): self.topic.uncategorized_skill_ids.append(skill_ids[index]) self.topic.thumbnail_filename = 'Image.svg' self.topic.thumbnail_bg_color = constants.ALLOWED_THUMBNAIL_BG_COLORS[ 'topic' ][0] self.topic.practice_tab_is_displayed = True subtopic_1 = topic_domain.Subtopic.create_default_subtopic( 1, 'Subtopic Title 1', 'url-frag-one' ) subtopic_1.skill_ids = ['skill_id_1'] subtopic_1.url_fragment = 'sub-one-frag' self.topic.subtopics = [subtopic_1] self.topic.next_subtopic_id = 2 self.topic.skill_ids_for_diagnostic_test = ['skill_id_1'] topic_services.save_new_topic(self.admin_id, self.topic) topic_services.publish_topic(self.topic_id, self.admin_id) for i in range(number_of_skills): self.save_new_skill( skill_ids[i], self.admin_id, description='Skill Description' ) for i in range(number_of_skills): for j in range(number_of_questions[i]): question_id = question_services.get_new_question_id() content_id_generator = translation_domain.ContentIdGenerator() default_dest_state_name = '%s' % j self.save_new_question( question_id, self.admin_id, self._create_valid_question_data( default_dest_state_name, content_id_generator ), [skill_ids[i]], content_id_generator.next_content_id_index, ) question_services.create_new_question_skill_link( self.admin_id, question_id, skill_ids[i], 0.5 ) json_response = self.get_json( '%s/staging/%s' % (feconf.TOPIC_DATA_HANDLER, 'new-topic') ) expected_dict = { 'topic_name': 'new_topic', 'topic_id': self.topic_id, 'canonical_story_dicts': [], 'additional_story_dicts': [], 'practice_tab_is_displayed': True, 'classroom_name': None, } self.assertDictContainsSubset(expected_dict, json_response) self.logout() def test_get_with_lesser_questions_with_fifty_or_more_skills(self) -> None: number_of_skills = 60 number_of_questions = [0] * 60 number_of_questions[46] = 2 self.topic_id = 'new_topic' skill_ids = [ skill_services.get_new_skill_id() for _ in range(number_of_skills) ] self.topic = topic_domain.Topic.create_default_topic( self.topic_id, 'new_topic', 'new-topic', 'description', 'fragm' ) for index in range(number_of_skills): self.topic.uncategorized_skill_ids.append(skill_ids[index]) self.topic.thumbnail_filename = 'Image.svg' self.topic.thumbnail_bg_color = constants.ALLOWED_THUMBNAIL_BG_COLORS[ 'topic' ][0] self.topic.practice_tab_is_displayed = False subtopic_1 = topic_domain.Subtopic.create_default_subtopic( 1, 'Subtopic Title 1', 'url-frag-one' ) subtopic_1.skill_ids = ['skill_id_1'] subtopic_1.url_fragment = 'sub-one-frag' self.topic.subtopics = [subtopic_1] self.topic.next_subtopic_id = 2 self.topic.skill_ids_for_diagnostic_test = ['skill_id_1'] topic_services.save_new_topic(self.admin_id, self.topic) topic_services.publish_topic(self.topic_id, self.admin_id) for i in range(number_of_skills): self.save_new_skill( skill_ids[i], self.admin_id, description='Skill Description' ) for i in range(number_of_skills): for j in range(number_of_questions[i]): default_dest_state_name = '%s' % j question_id = question_services.get_new_question_id() content_id_generator = translation_domain.ContentIdGenerator() self.save_new_question( question_id, self.admin_id, self._create_valid_question_data( default_dest_state_name, content_id_generator ), [skill_ids[i]], content_id_generator.next_content_id_index, ) question_services.create_new_question_skill_link( self.admin_id, question_id, skill_ids[i], 0.5 ) json_response = self.get_json( '%s/staging/%s' % (feconf.TOPIC_DATA_HANDLER, 'new-topic') ) expected_dict = { 'topic_name': 'new_topic', 'topic_id': self.topic_id, 'canonical_story_dicts': [], 'additional_story_dicts': [], 'practice_tab_is_displayed': False, 'classroom_name': None, } self.assertDictContainsSubset(expected_dict, json_response) self.logout() def test_get_with_more_questions_with_fifty_or_more_skills(self) -> None: number_of_skills = 60 number_of_questions = [0] * 60 number_of_questions[46] = 2 number_of_questions[20] = 3 number_of_questions[29] = 10 self.topic_id = 'new_topic' skill_ids = [ skill_services.get_new_skill_id() for _ in range(number_of_skills) ] self.topic = topic_domain.Topic.create_default_topic( self.topic_id, 'new_topic', 'new-topic', 'description', 'fragm' ) for index in range(number_of_skills): self.topic.uncategorized_skill_ids.append(skill_ids[index]) self.topic.thumbnail_filename = 'Image.svg' self.topic.thumbnail_bg_color = constants.ALLOWED_THUMBNAIL_BG_COLORS[ 'topic' ][0] self.topic.practice_tab_is_displayed = True subtopic_1 = topic_domain.Subtopic.create_default_subtopic( 1, 'Subtopic Title 1', 'url-frag-one' ) subtopic_1.skill_ids = ['skill_id_1'] subtopic_1.url_fragment = 'sub-one-frag' self.topic.subtopics = [subtopic_1] self.topic.next_subtopic_id = 2 self.topic.skill_ids_for_diagnostic_test = ['skill_id_1'] topic_services.save_new_topic(self.admin_id, self.topic) topic_services.publish_topic(self.topic_id, self.admin_id) for i in range(number_of_skills): self.save_new_skill( skill_ids[i], self.admin_id, description='Skill Description' ) for i in range(number_of_skills): for j in range(number_of_questions[i]): default_dest_state_name = '%s' % j question_id = question_services.get_new_question_id() content_id_generator = translation_domain.ContentIdGenerator() self.save_new_question( question_id, self.admin_id, self._create_valid_question_data( default_dest_state_name, content_id_generator ), [skill_ids[i]], content_id_generator.next_content_id_index, ) question_services.create_new_question_skill_link( self.admin_id, question_id, skill_ids[i], 0.5 ) json_response = self.get_json( '%s/staging/%s' % (feconf.TOPIC_DATA_HANDLER, 'new-topic') ) expected_dict = { 'topic_name': 'new_topic', 'topic_id': self.topic_id, 'canonical_story_dicts': [], 'additional_story_dicts': [], 'practice_tab_is_displayed': True, 'classroom_name': None, } self.assertDictContainsSubset(expected_dict, json_response) self.logout() @test_utils.enable_feature_flags( [feature_flag_list.FeatureNames.STORY_EDITOR_ARCS] ) def test_get_with_are_story_arcs_enabled(self) -> None: self.login(self.NEW_USER_EMAIL) json_response = self.get_json( '%s/staging/%s' % (feconf.TOPIC_DATA_HANDLER, 'public') ) for canonical_story_dict in json_response['canonical_story_dicts']: self.assertIn('arcs', canonical_story_dict) for additional_story_dict in json_response['additional_story_dicts']: self.assertIn('arcs', additional_story_dict) self.logout() class TopicNameHandlerTest(test_utils.GenericTestBase): """Tests for TopicNameHandler.""" def setUp(self) -> None: """Completes the sign-up process for the various users.""" super().setUp() self.signup(self.NEW_USER_EMAIL, self.NEW_USER_USERNAME) self.user_id = self.get_user_id_from_email(self.NEW_USER_EMAIL) self.signup(self.CURRICULUM_ADMIN_EMAIL, self.CURRICULUM_ADMIN_USERNAME) self.admin_id = self.get_user_id_from_email(self.CURRICULUM_ADMIN_EMAIL) self.set_curriculum_admins([self.CURRICULUM_ADMIN_USERNAME]) self.admin = user_services.get_user_actions_info(self.admin_id) self.topic_id = 'topic' self.topic_name = 'Topic Name' self.topic_url_fragment = 'topic-name' self.description = 'Topic Description' topic = topic_domain.Topic.create_default_topic( self.topic_id, self.topic_name, self.topic_url_fragment, description=self.description, page_title_frag=self.topic_name, ) topic_services.save_new_topic(self.admin_id, topic) def test_get_with_existing_topic_name(self) -> None: """Test that the handler returns True when topic name exists.""" self.login(self.NEW_USER_EMAIL) response = self.get_json( '%s/%s' % (feconf.TOPIC_NAME_HANDLER, self.topic_name) ) self.assertEqual(response['topic_name_exists'], True) self.logout() def test_get_with_nonexistent_topic_name(self) -> None: """Test that the handler returns False when topic name doesn't exist.""" self.login(self.NEW_USER_EMAIL) response = self.get_json( '%s/%s' % (feconf.TOPIC_NAME_HANDLER, 'nonexistent_topic') ) self.assertEqual(response['topic_name_exists'], False) self.logout() def test_get_with_guest_user(self) -> None: """Test that guest users can check topic name existence.""" response = self.get_json( '%s/%s' % (feconf.TOPIC_NAME_HANDLER, self.topic_name) ) self.assertEqual(response['topic_name_exists'], True) self.logout()