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 _validate_library_version(self, validation, lib_tools, version, library_key): """ Validates library version """ latest_version = lib_tools.get_library_version(library_key) if latest_version is not None: if version is None or version != unicode(latest_version): validation.set_summary( StudioValidationMessage( StudioValidationMessage.WARNING, _(u'This component is out of date. The library has new content.' ), # TODO: change this to action_runtime_event='...' once the unit page supports that feature. # See https://openedx.atlassian.net/browse/TNL-993 action_class='library-update-btn', # Translators: {refresh_icon} placeholder is substituted to "↻" (without double quotes) action_label=_(u"{refresh_icon} Update now.").format( refresh_icon=u"↻"))) return False else: validation.set_summary( StudioValidationMessage( StudioValidationMessage.ERROR, _(u'Library is invalid, corrupt, or has been deleted.'), action_class='edit-button', action_label=_(u"Edit Library List."))) return False return True
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_to_json(self): """ Test the `to_json` method. """ self.assertEqual( { "type": StudioValidationMessage.NOT_CONFIGURED, "text": u"Not Configured message", "action_label": u"Action label" }, StudioValidationMessage(StudioValidationMessage.NOT_CONFIGURED, u"Not Configured message", action_label=u"Action label").to_json()) self.assertEqual( { "type": StudioValidationMessage.WARNING, "text": u"Warning message", "action_class": "class-for-action" }, StudioValidationMessage(StudioValidationMessage.WARNING, u"Warning message", action_class="class-for-action").to_json()) self.assertEqual( { "type": StudioValidationMessage.ERROR, "text": u"Error message", "action_runtime_event": "do-fix-up" }, StudioValidationMessage( StudioValidationMessage.ERROR, u"Error message", action_runtime_event="do-fix-up").to_json())
def test_warning_message_present(self): """ Tests if validation message is present (warning case). """ mocked_message = StudioValidationMessage(StudioValidationMessage.WARNING, u"Validation message") expected_result = StudioValidationMessage( StudioValidationMessage.WARNING, u"This content experiment has issues that affect content visibility." ) self.verify_validation_add_usage_info(expected_result, mocked_message) # pylint: disable=no-value-for-parameter
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: # 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, _(u"The experiment uses a group configuration that is not supported for experiments. " u"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. " u"Move content into active groups, then delete the inactive group.") ) ) return split_validation
def test_update_usage_info(self): """ Tests if validation message is present when updating usage info. """ mocked_message = StudioValidationMessage(StudioValidationMessage.WARNING, u"Validation message") expected_result = StudioValidationMessage( StudioValidationMessage.WARNING, u"This content experiment has issues that affect content visibility." ) # pylint: disable=no-value-for-parameter self.verify_validation_update_usage_info(expected_result, mocked_message)
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 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 test_bad_parameters(self): """ Test that `TypeError`s are thrown for bad input parameters. """ with pytest.raises(TypeError): StudioValidationMessage("unknown type", u"Unknown type info") with pytest.raises(TypeError): StudioValidationMessage(StudioValidationMessage.WARNING, u"bad warning", action_class=0) with pytest.raises(TypeError): StudioValidationMessage(StudioValidationMessage.WARNING, u"bad warning", action_runtime_event=0) with pytest.raises(TypeError): StudioValidationMessage(StudioValidationMessage.WARNING, u"bad warning", action_label="Non-unicode string")
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_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 test_to_json(self): """ Test the `to_json` method. """ assert \ {'type': StudioValidationMessage.NOT_CONFIGURED, 'text': 'Not Configured message', 'action_label': 'Action label'} == \ StudioValidationMessage(StudioValidationMessage.NOT_CONFIGURED, 'Not Configured message', action_label='Action label').to_json() assert \ {'type': StudioValidationMessage.WARNING, 'text': 'Warning message', 'action_class': 'class-for-action'} ==\ StudioValidationMessage(StudioValidationMessage.WARNING, 'Warning message', action_class='class-for-action').to_json() assert \ {'type': StudioValidationMessage.ERROR, 'text': 'Error message', 'action_runtime_event': 'do-fix-up'} ==\ StudioValidationMessage(StudioValidationMessage.ERROR, 'Error message', action_runtime_event='do-fix-up').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 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 general_validation_message(self, validation=None): """ Returns just a summary message about whether or not this split_test instance has validation issues (not including superclass validation messages). If the split_test instance validates correctly, this method returns None. """ if validation is None: validation = self.validate_split_test() if not validation: has_error = any(message.type == StudioValidationMessage.ERROR for message in validation.messages) return StudioValidationMessage( StudioValidationMessage.ERROR if has_error else StudioValidationMessage.WARNING, _(u"This content experiment has issues that affect content visibility.") ) return 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 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 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