def test_set_summary_accepts_validation_message(self): """ Test that `set_summary` accepts a ValidationMessage. """ validation = StudioValidation("id") validation.set_summary(ValidationMessage(ValidationMessage.WARNING, u"Summary message")) self.assertEqual(ValidationMessage.WARNING, validation.summary.type) self.assertEqual(u"Summary message", validation.summary.text)
def test_set_summary_accepts_validation_message(self): """ Test that `set_summary` accepts a ValidationMessage. """ validation = StudioValidation("id") validation.set_summary( ValidationMessage(ValidationMessage.WARNING, "Summary message")) assert ValidationMessage.WARNING == validation.summary.type assert 'Summary message' == validation.summary.text
def verify_validation_add_usage_info(self, expected_result, mocked_message, mocked_validation_messages): """ Helper method for testing validation information present after add_usage_info. """ self._add_user_partitions() split_test = self._create_content_experiment(cid=0, name_suffix='0')[1] validation = StudioValidation(split_test.location) validation.add(mocked_message) mocked_validation_messages.return_value = validation group_configuration = GroupConfiguration.add_usage_info(self.course, self.store)[0] self.assertEqual(expected_result.to_json(), group_configuration['usage'][0]['validation'])
def verify_validation_add_usage_info(self, expected_result, mocked_message, mocked_validation_messages): """ Helper method for testing validation information present after add_usage_info. """ self._add_user_partitions() split_test = self._create_content_experiment(cid=0, name_suffix='0')[1] validation = StudioValidation(split_test.location) validation.add(mocked_message) mocked_validation_messages.return_value = validation group_configuration = GroupConfiguration.get_split_test_partitions_with_usage(self.store, self.course)[0] self.assertEqual(expected_result.to_json(), group_configuration['usage'][0]['validation'])
def test_to_json(self): """ Test the ability to serialize a `StudioValidation` instance. """ validation = StudioValidation("id") expected = {"xblock_id": "id", "messages": [], "empty": True} self.assertEqual(expected, validation.to_json()) validation.add( StudioValidationMessage(StudioValidationMessage.ERROR, u"Error message", action_label=u"Action label", action_class="edit-button")) validation.add( StudioValidationMessage(StudioValidationMessage.NOT_CONFIGURED, u"Not configured message", action_label=u"Action label", action_runtime_event="make groups")) validation.set_summary( StudioValidationMessage(StudioValidationMessage.WARNING, u"Summary message", action_label=u"Summary label", action_runtime_event="fix everything")) # Note: it is important to test all the expected strings here because the client-side model depends on them # (for instance, "warning" vs. using the xblock constant ValidationMessageTypes.WARNING). expected = { "xblock_id": "id", "messages": [{ "type": "error", "text": u"Error message", "action_label": u"Action label", "action_class": "edit-button" }, { "type": "not-configured", "text": u"Not configured message", "action_label": u"Action label", "action_runtime_event": "make groups" }], "summary": { "type": "warning", "text": u"Summary message", "action_label": u"Summary label", "action_runtime_event": "fix everything" }, "empty": False } self.assertEqual(expected, validation.to_json())
def validate(self): validation = super(ConditionalDescriptor, self).validate() if not self.sources_list: conditional_validation = StudioValidation(self.location) conditional_validation.add( StudioValidationMessage( StudioValidationMessage.NOT_CONFIGURED, _(u"This component has no source components configured yet." ), action_class='edit-button', action_label=_(u"Configure list of sources"))) validation = StudioValidation.copy(validation) validation.summary = conditional_validation.messages[0] return validation
def validate(self): validation = super(ConditionalDescriptor, self).validate() if not self.sources_list: conditional_validation = StudioValidation(self.location) conditional_validation.add( StudioValidationMessage( StudioValidationMessage.NOT_CONFIGURED, _(u"This component has no source components configured yet."), action_class='edit-button', action_label=_(u"Configure list of sources") ) ) validation = StudioValidation.copy(validation) validation.summary = conditional_validation.messages[0] return validation
def test_copy_studio_validation(self): validation = StudioValidation("id") validation.add( StudioValidationMessage(StudioValidationMessage.WARNING, u"Warning message", action_label=u"Action Label") ) validation_copy = StudioValidation.copy(validation) self.assertFalse(validation_copy) self.assertEqual(1, len(validation_copy.messages)) expected = { "type": StudioValidationMessage.WARNING, "text": u"Warning message", "action_label": u"Action Label" } self.assertEqual(expected, validation_copy.messages[0].to_json())
def validate(self): validation = super(ConditionalBlock, self).validate() # lint-amnesty, pylint: disable=super-with-arguments if not self.sources_list: conditional_validation = StudioValidation(self.location) conditional_validation.add( StudioValidationMessage( StudioValidationMessage.NOT_CONFIGURED, _(u"This component has no source components configured yet."), action_class='edit-button', action_label=_(u"Configure list of sources") ) ) validation = StudioValidation.copy(validation) validation.summary = conditional_validation.messages[0] return validation
def test_copy_studio_validation(self): validation = StudioValidation("id") validation.add( StudioValidationMessage(StudioValidationMessage.WARNING, "Warning message", action_label="Action Label")) validation_copy = StudioValidation.copy(validation) assert not validation_copy assert 1 == len(validation_copy.messages) expected = { "type": StudioValidationMessage.WARNING, "text": "Warning message", "action_label": "Action Label" } assert expected == validation_copy.messages[0].to_json()
def validate(self): """ Validates the state of this video Module Instance. This is the override of the general XBlock method, and it will also ask its superclass to validate. """ validation = super(VideoDescriptor, self).validate() if not isinstance(validation, StudioValidation): validation = StudioValidation.copy(validation) no_transcript_lang = [] for lang_code, transcript in self.transcripts.items(): if not transcript: no_transcript_lang.append([label for code, label in settings.ALL_LANGUAGES if code == lang_code][0]) if no_transcript_lang: ungettext = self.runtime.service(self, "i18n").ungettext validation.set_summary( StudioValidationMessage( StudioValidationMessage.WARNING, ungettext( 'There is no transcript file associated with the {lang} language.', 'There are no transcript files associated with the {lang} languages.', len(no_transcript_lang) ).format(lang=', '.join(no_transcript_lang)) ) ) return validation
def test_to_json(self): """ Test the ability to serialize a `StudioValidation` instance. """ validation = StudioValidation("id") expected = { "xblock_id": "id", "messages": [], "empty": True } self.assertEqual(expected, validation.to_json()) validation.add( StudioValidationMessage( StudioValidationMessage.ERROR, u"Error message", action_label=u"Action label", action_class="edit-button" ) ) validation.add( StudioValidationMessage( StudioValidationMessage.NOT_CONFIGURED, u"Not configured message", action_label=u"Action label", action_runtime_event="make groups" ) ) validation.set_summary( StudioValidationMessage( StudioValidationMessage.WARNING, u"Summary message", action_label=u"Summary label", action_runtime_event="fix everything" ) ) # Note: it is important to test all the expected strings here because the client-side model depends on them # (for instance, "warning" vs. using the xblock constant ValidationMessageTypes.WARNING). expected = { "xblock_id": "id", "messages": [ {"type": "error", "text": u"Error message", "action_label": u"Action label", "action_class": "edit-button"}, {"type": "not-configured", "text": u"Not configured message", "action_label": u"Action label", "action_runtime_event": "make groups"} ], "summary": {"type": "warning", "text": u"Summary message", "action_label": u"Summary label", "action_runtime_event": "fix everything"}, "empty": False } self.assertEqual(expected, validation.to_json())
def validate(self): """ Validates the state of this Library Content Module Instance. This is the override of the general XBlock method, and it will also ask its superclass to validate. """ validation = super(LibraryContentDescriptor, self).validate() if not isinstance(validation, StudioValidation): validation = StudioValidation.copy(validation) if not self.source_libraries: validation.set_summary( StudioValidationMessage( StudioValidationMessage.NOT_CONFIGURED, _(u"A library has not yet been selected."), action_class='edit-button', action_label=_(u"Select a Library."))) return validation lib_tools = self.runtime.service(self, 'library_tools') for library_key, version in self.source_libraries: if not self._validate_library_version(validation, lib_tools, version, library_key): break # Note: we assume refresh_children() has been called # since the last time fields like source_libraries or capa_types were changed. matching_children_count = len(self.children) # pylint: disable=no-member if matching_children_count == 0: self._set_validation_error_if_empty( validation, StudioValidationMessage( StudioValidationMessage.WARNING, _(u'There are no matching problem types in the specified libraries.' ), action_class='edit-button', action_label=_(u"Select another problem type."))) if matching_children_count < self.max_count: self._set_validation_error_if_empty( validation, StudioValidationMessage( StudioValidationMessage.WARNING, (ngettext( u'The specified libraries are configured to fetch {count} problem, ', u'The specified libraries are configured to fetch {count} problems, ', self.max_count) + ngettext( u'but there is only {actual} matching problem.', u'but there are only {actual} matching problems.', matching_children_count)).format( count=self.max_count, actual=matching_children_count), action_class='edit-button', action_label=_(u"Edit the library configuration."))) return validation
def validate_split_test(self): """ Returns a StudioValidation object describing the current state of the split_test_module (not including superclass validation messages). """ _ = self.runtime.service(self, "i18n").ugettext # pylint: disable=redefined-outer-name split_validation = StudioValidation(self.location) if self.user_partition_id < 0: split_validation.add( StudioValidationMessage( StudioValidationMessage.NOT_CONFIGURED, _(u"The experiment is not associated with a group configuration." ), action_class='edit-button', action_label=_(u"Select a Group Configuration"))) else: user_partition = self.get_selected_partition() if not user_partition: split_validation.add( StudioValidationMessage( StudioValidationMessage.ERROR, _(u"The experiment uses a deleted group configuration. Select a valid group configuration or delete this experiment." ))) else: [active_children, inactive_children] = self.active_and_inactive_children() if len(active_children) < len(user_partition.groups): split_validation.add( StudioValidationMessage( StudioValidationMessage.ERROR, _(u"The experiment does not contain all of the groups in the configuration." ), action_runtime_event='add-missing-groups', action_label=_(u"Add Missing Groups"))) if len(inactive_children) > 0: split_validation.add( StudioValidationMessage( StudioValidationMessage.WARNING, _(u"The experiment has an inactive group. Move content into active groups, then delete the inactive group." ))) return split_validation
def validate_split_test(self): """ Returns a StudioValidation object describing the current state of the split_test_module (not including superclass validation messages). """ _ = self.runtime.service(self, "i18n").ugettext # pylint: disable=redefined-outer-name split_validation = StudioValidation(self.location) if self.user_partition_id < 0: split_validation.add( StudioValidationMessage( StudioValidationMessage.NOT_CONFIGURED, _(u"The experiment is not associated with a group configuration."), action_class='edit-button', action_label=_(u"Select a Group Configuration") ) ) else: user_partition = self.get_selected_partition() if not user_partition: split_validation.add( StudioValidationMessage( StudioValidationMessage.ERROR, _(u"The experiment uses a deleted group configuration. Select a valid group configuration or delete this experiment.") ) ) else: [active_children, inactive_children] = self.active_and_inactive_children() if len(active_children) < len(user_partition.groups): split_validation.add( StudioValidationMessage( StudioValidationMessage.ERROR, _(u"The experiment does not contain all of the groups in the configuration."), action_runtime_event='add-missing-groups', action_label=_(u"Add Missing Groups") ) ) if len(inactive_children) > 0: split_validation.add( StudioValidationMessage( StudioValidationMessage.WARNING, _(u"The experiment has an inactive group. Move content into active groups, then delete the inactive group.") ) ) return split_validation
def test_copy(self): validation = Validation("id") validation.add(ValidationMessage(ValidationMessage.ERROR, u"Error message")) studio_validation = StudioValidation.copy(validation) self.assertIsInstance(studio_validation, StudioValidation) self.assertFalse(studio_validation) self.assertEqual(1, len(studio_validation.messages)) expected = { "type": StudioValidationMessage.ERROR, "text": u"Error message" } self.assertEqual(expected, studio_validation.messages[0].to_json()) self.assertIsNone(studio_validation.summary)
def test_copy(self): validation = Validation("id") validation.add( ValidationMessage(ValidationMessage.ERROR, "Error message")) studio_validation = StudioValidation.copy(validation) assert isinstance(studio_validation, StudioValidation) assert not studio_validation assert 1 == len(studio_validation.messages) expected = { "type": StudioValidationMessage.ERROR, "text": "Error message" } assert expected == studio_validation.messages[0].to_json() assert studio_validation.summary is None
def validate(self): """ Validates the state of this instance. This is the override of the general XBlock method, and it will also ask its superclass to validate. """ validation = super(CombinedOpenEndedDescriptor, self).validate() validation = StudioValidation.copy(validation) i18n_service = self.runtime.service(self, "i18n") validation.summary = StudioValidationMessage( StudioValidationMessage.ERROR, i18n_service.ugettext( "ORA1 is no longer supported. To use this assessment, " "replace this ORA1 component with an ORA2 component.")) return validation
def validate(self): """ Validates the state of this library_sourced_xblock Instance. This is the override of the general XBlock method, and it will also ask its superclass to validate. """ validation = super().validate() validation = StudioValidation.copy(validation) if not self.source_block_ids: validation.set_summary( StudioValidationMessage( StudioValidationMessage.NOT_CONFIGURED, _("No XBlock has been configured for this component. Use the editor to select the target blocks." ), action_class='edit-button', action_label=_("Open Editor"))) return validation
def test_empty(self): """ Test that `empty` return True iff there are no messages and no summary. Also test the "bool" property of `Validation`. """ validation = StudioValidation("id") self.assertTrue(validation.empty) self.assertTrue(validation) validation.add(StudioValidationMessage(StudioValidationMessage.ERROR, u"Error message")) self.assertFalse(validation.empty) self.assertFalse(validation) validation_with_summary = StudioValidation("id") validation_with_summary.set_summary( StudioValidationMessage(StudioValidationMessage.NOT_CONFIGURED, u"Summary message") ) self.assertFalse(validation.empty) self.assertFalse(validation)
def validate(self): """ Validates the state of this instance. This is the override of the general XBlock method, and it will also ask its superclass to validate. """ validation = super(PeerGradingDescriptor, self).validate() validation = StudioValidation.copy(validation) i18n_service = self.runtime.service(self, "i18n") validation.summary = StudioValidationMessage( StudioValidationMessage.ERROR, i18n_service.ugettext( "ORA1 is no longer supported. To use this assessment, " "replace this ORA1 component with an ORA2 component." ) ) return validation
def validate(self): """ Validates the state of this split_test instance. This is the override of the general XBlock method, and it will also ask its superclass to validate. """ validation = super(SplitTestDescriptor, self).validate() split_test_validation = self.validate_split_test() if split_test_validation: return validation validation = StudioValidation.copy(validation) if validation and (not self.is_configured and len(split_test_validation.messages) == 1): validation.summary = split_test_validation.messages[0] else: validation.summary = self.general_validation_message(split_test_validation) validation.add_messages(split_test_validation) return validation
def validate_split_test(self): """ Returns a StudioValidation object describing the current state of the split_test_module (not including superclass validation messages). """ _ = self.runtime.service(self, "i18n").ugettext split_validation = StudioValidation(self.location) if self.user_partition_id < 0: split_validation.add( StudioValidationMessage( StudioValidationMessage.NOT_CONFIGURED, _("The experiment is not associated with a group configuration." ), action_class='edit-button', action_label=_("Select a Group Configuration"))) else: user_partition = self.get_selected_partition() if not user_partition: split_validation.add( StudioValidationMessage( StudioValidationMessage.ERROR, _("The experiment uses a deleted group configuration. Select a valid group configuration or delete this experiment." ) # lint-amnesty, pylint: disable=line-too-long )) else: # If the user_partition selected is not valid for the split_test module, error. # This can only happen via XML and import/export. if not get_split_user_partitions([user_partition]): split_validation.add( StudioValidationMessage( StudioValidationMessage.ERROR, _("The experiment uses a group configuration that is not supported for experiments. " "Select a valid group configuration or delete this experiment." ))) else: [active_children, inactive_children] = self.active_and_inactive_children() if len(active_children) < len(user_partition.groups): split_validation.add( StudioValidationMessage( StudioValidationMessage.ERROR, _("The experiment does not contain all of the groups in the configuration." ), action_runtime_event='add-missing-groups', action_label=_("Add Missing Groups"))) if len(inactive_children) > 0: split_validation.add( StudioValidationMessage( StudioValidationMessage.WARNING, _("The experiment has an inactive group. " "Move content into active groups, then delete the inactive group." ))) return split_validation
def test_set_summary_errors(self): """ Test that `set_summary` errors if argument is not a ValidationMessage. """ with pytest.raises(TypeError): StudioValidation("id").set_summary("foo")
def test_add_messages(self): """ Test the behavior of calling `add_messages` with combination of `StudioValidation` instances. """ validation_1 = StudioValidation("id") validation_1.set_summary( StudioValidationMessage(StudioValidationMessage.WARNING, u"Summary message")) validation_1.add( StudioValidationMessage(StudioValidationMessage.ERROR, u"Error message")) validation_2 = StudioValidation("id") validation_2.set_summary( StudioValidationMessage(StudioValidationMessage.ERROR, u"Summary 2 message")) validation_2.add( StudioValidationMessage(StudioValidationMessage.NOT_CONFIGURED, u"Not configured")) validation_1.add_messages(validation_2) self.assertEqual(2, len(validation_1.messages)) self.assertEqual(StudioValidationMessage.ERROR, validation_1.messages[0].type) self.assertEqual(u"Error message", validation_1.messages[0].text) self.assertEqual(StudioValidationMessage.NOT_CONFIGURED, validation_1.messages[1].type) self.assertEqual(u"Not configured", validation_1.messages[1].text) self.assertEqual(StudioValidationMessage.WARNING, validation_1.summary.type) self.assertEqual(u"Summary message", validation_1.summary.text)
def test_copy_errors(self): with pytest.raises(TypeError): StudioValidation.copy("foo")
def validate(self): """ Validates the state of this Library Content Module Instance. This is the override of the general XBlock method, and it will also ask its superclass to validate. """ validation = super(LibraryContentDescriptor, self).validate() if not isinstance(validation, StudioValidation): validation = StudioValidation.copy(validation) library_tools = self.runtime.service(self, "library_tools") if not (library_tools and library_tools.can_use_library_content(self)): validation.set_summary( StudioValidationMessage( StudioValidationMessage.ERROR, _( u"This course does not support content libraries. " u"Contact your system administrator for more information." ) ) ) return validation if not self.source_library_id: validation.set_summary( StudioValidationMessage( StudioValidationMessage.NOT_CONFIGURED, _(u"A library has not yet been selected."), action_class='edit-button', action_label=_(u"Select a Library.") ) ) return validation lib_tools = self.runtime.service(self, 'library_tools') self._validate_library_version(validation, lib_tools, self.source_library_version, self.source_library_key) # Note: we assume refresh_children() has been called # since the last time fields like source_library_id or capa_types were changed. matching_children_count = len(self.children) # pylint: disable=no-member if matching_children_count == 0: self._set_validation_error_if_empty( validation, StudioValidationMessage( StudioValidationMessage.WARNING, _(u'There are no matching problem types in the specified libraries.'), action_class='edit-button', action_label=_(u"Select another problem type.") ) ) if matching_children_count < self.max_count: self._set_validation_error_if_empty( validation, StudioValidationMessage( StudioValidationMessage.WARNING, ( ngettext( u'The specified library is configured to fetch {count} problem, ', u'The specified library is configured to fetch {count} problems, ', self.max_count ) + ngettext( u'but there is only {actual} matching problem.', u'but there are only {actual} matching problems.', matching_children_count ) ).format(count=self.max_count, actual=matching_children_count), action_class='edit-button', action_label=_(u"Edit the library configuration.") ) ) return validation
def test_add_messages(self): """ Test the behavior of calling `add_messages` with combination of `StudioValidation` instances. """ validation_1 = StudioValidation("id") validation_1.set_summary( StudioValidationMessage(StudioValidationMessage.WARNING, "Summary message")) validation_1.add( StudioValidationMessage(StudioValidationMessage.ERROR, "Error message")) validation_2 = StudioValidation("id") validation_2.set_summary( StudioValidationMessage(StudioValidationMessage.ERROR, "Summary 2 message")) validation_2.add( StudioValidationMessage(StudioValidationMessage.NOT_CONFIGURED, "Not configured")) validation_1.add_messages(validation_2) assert 2 == len(validation_1.messages) assert StudioValidationMessage.ERROR == validation_1.messages[0].type assert 'Error message' == validation_1.messages[0].text assert StudioValidationMessage.NOT_CONFIGURED == validation_1.messages[ 1].type assert 'Not configured' == validation_1.messages[1].text assert StudioValidationMessage.WARNING == validation_1.summary.type assert 'Summary message' == validation_1.summary.text
def test_add_messages(self): """ Test the behavior of calling `add_messages` with combination of `StudioValidation` instances. """ validation_1 = StudioValidation("id") validation_1.set_summary(StudioValidationMessage(StudioValidationMessage.WARNING, u"Summary message")) validation_1.add(StudioValidationMessage(StudioValidationMessage.ERROR, u"Error message")) validation_2 = StudioValidation("id") validation_2.set_summary(StudioValidationMessage(StudioValidationMessage.ERROR, u"Summary 2 message")) validation_2.add(StudioValidationMessage(StudioValidationMessage.NOT_CONFIGURED, u"Not configured")) validation_1.add_messages(validation_2) self.assertEqual(2, len(validation_1.messages)) self.assertEqual(StudioValidationMessage.ERROR, validation_1.messages[0].type) self.assertEqual(u"Error message", validation_1.messages[0].text) self.assertEqual(StudioValidationMessage.NOT_CONFIGURED, validation_1.messages[1].type) self.assertEqual(u"Not configured", validation_1.messages[1].text) self.assertEqual(StudioValidationMessage.WARNING, validation_1.summary.type) self.assertEqual(u"Summary message", validation_1.summary.text)
def test_copy_errors(self): with assert_raises(TypeError): StudioValidation.copy("foo")