def test_models_lti_consumer_auth_parameters_edit_mode_predefined( self, _mock_rl, _mock_ts, _mock_nonce ): """ Verify that instructor LTI authentication parameters are correctly built with edit mode """ instance = LTIConsumerFactory( lti_provider_id="lti_provider_test", url="http://localhost:8060/lti/videos/3cd0bcc4-0", ) expected_content_parameters = { "lti_message_type": "basic-lti-launch-request", "lti_version": "LTI-1p0", "resource_link_id": "1234", "context_id": "example.com", "user_id": "richie", "lis_person_contact_email_primary": "", "roles": "instructor", "oauth_consumer_key": "TestOauthConsumerKey", "oauth_nonce": "59474787080480293391616018589", "oauth_signature": "Y3d9qVSe+W7kA5E9+7JB/NeF2OA=", "oauth_signature_method": "HMAC-SHA1", "oauth_timestamp": "1616018589", "oauth_version": "1.0", } self.assertDictEqual( expected_content_parameters, instance.get_content_parameters(edit=True) )
def test_models_lti_consumer_auth_parameters_no_edit_mode_predefined( self, _mock_rl, _mock_ts, _mock_nonce ): """ Verify that student LTI authentication parameters are correctly built without edit mode when credentials come from the settings. """ instance = LTIConsumerFactory( lti_provider_id="lti_provider_test", url="http://localhost:8060/lti/videos/3cd0bcc4-0", # Add credentials on the model to check that they have no influence oauth_consumer_key="IgnoredOauthConsumerKey", shared_secret="IgnoredSharedSecret", ) expected_content_parameters = { "lti_message_type": "basic-lti-launch-request", "lti_version": "LTI-1p0", "resource_link_id": "1234", "context_id": "example.com", "user_id": "richie", "lis_person_contact_email_primary": "", "roles": "student", "oauth_consumer_key": "TestOauthConsumerKey", "oauth_nonce": "59474787080480293391616018589", "oauth_signature": "PkxLai53gItjVvgbccU7AW4HwuY=", "oauth_signature_method": "HMAC-SHA1", "oauth_timestamp": "1616018589", "oauth_version": "1.0", } self.assertDictEqual( expected_content_parameters, instance.get_content_parameters() )
def test_models_lti_consumer_auth_parameters_edit_mode_manual( self, _mock_rl, _mock_ts, _mock_nonce ): """ Verify that instructor LTI authentication parameters are correctly built with edit mode when credentials are setup manually. """ instance = LTIConsumerFactory( lti_provider_id=None, url="http://localhost:8060/lti/videos/3cd0bcc4-0", oauth_consumer_key="ManualTestOauthConsumerKey", shared_secret="ManualTestSharedSecret", ) expected_content_parameters = { "lti_message_type": "basic-lti-launch-request", "lti_version": "LTI-1p0", "resource_link_id": "1234", "context_id": "example.com", "user_id": "richie", "lis_person_contact_email_primary": "", "roles": "instructor", "oauth_consumer_key": "ManualTestOauthConsumerKey", "oauth_nonce": "59474787080480293391616018589", "oauth_signature": "CCnCQtLjPlb+Yr2C0FjYmoVO6Gk=", "oauth_signature_method": "HMAC-SHA1", "oauth_timestamp": "1616018589", "oauth_version": "1.0", } self.assertDictEqual( expected_content_parameters, instance.get_content_parameters(edit=True) )
def test_lti_consumer_models_inline_ratio_max(self): """The "inline_ratio" field should not accept values bigger than 10""" instance = LTIConsumerFactory(inline_ratio=10.01) with self.assertRaises(ValidationError) as context: instance.full_clean() self.assertEqual( str(context.exception), "{'inline_ratio': ['Ensure this value is less than or equal to 10.']}", )
def test_lti_consumer_models_inline_ratio_min(self): """The "inline_ratio" field should not accept values smaller than 0.1""" instance = LTIConsumerFactory(inline_ratio=0.09) with self.assertRaises(ValidationError) as context: instance.full_clean() self.assertEqual( str(context.exception), "{'inline_ratio': ['Ensure this value is greater than or equal to 0.1.']}", )
def test_lti_consumer_factories_create_with_lti_provider(self): """ The url field should be computed by the model's "save" method if an LTI provider is defined. """ lti_consumer = LTIConsumerFactory() self.assertIn(lti_consumer.url, "http://localhost:8060/lti/videos/")
def test_lti_consumer_api_get_context(self, mock_params): """ Instianciating this plugin and make a request to its API endpoint to get context """ placeholder = Placeholder.objects.create(slot="test") lti_consumer = LTIConsumerFactory() model_instance = add_plugin( placeholder, LTIConsumerPlugin, "en", url=lti_consumer.url, lti_provider_id=lti_consumer.lti_provider_id, ) response = self.client.get( f"/api/v1.0/plugins/lti-consumer/{model_instance.pk}/context/") content = json.loads(response.content) self.assertEqual(response.status_code, 200) self.assertIn(content["url"], lti_consumer.url) self.assertEqual(content["automatic_resizing"], lti_consumer.automatic_resizing) self.assertEqual(content["content_parameters"], "test_content") mock_params.assert_called_once()
def test_cms_plugins_lti_consumer_context_and_html(self): """ Instanciating this plugin with an instance should populate the context and render in the template. """ placeholder = Placeholder.objects.create(slot="test") lti_consumer = LTIConsumerFactory() model_instance = add_plugin( placeholder, LTIConsumerPlugin, "en", url=lti_consumer.url, lti_provider_id=lti_consumer.lti_provider_id, ) plugin_instance = model_instance.get_plugin_class_instance() context = plugin_instance.render({}, model_instance, None) # Check if instance is in context self.assertEqual(model_instance, context["instance"]) # Get generated html for LTI consumer url renderer = ContentRenderer(request=RequestFactory()) html = renderer.render_plugin(model_instance, {}) # Check rendered url is correct after save and sanitize self.assertIn(lti_consumer.url, html) self.assertIn("student", html)
def test_lti_consumer_api_get_context(self, mock_params): """ Instantiating this plugin and make a request to its API endpoint to get context """ page = create_i18n_page("A page") placeholder = page.placeholders.get(slot="maincontent") lti_consumer = LTIConsumerFactory() model_instance = add_plugin( placeholder, LTIConsumerPlugin, "en", url=lti_consumer.url, lti_provider_id=lti_consumer.lti_provider_id, ) user_id = str(uuid.uuid4()) response = self.client.get( f"/api/v1.0/plugins/lti-consumer/{model_instance.pk}/context/", {"user_id": user_id}, ) self.assertEqual(response.status_code, 200) content = json.loads(response.content) self.assertIn(content["url"], lti_consumer.url) self.assertTrue(content["is_automatic_resizing"]) self.assertEqual(content["content_parameters"], "test_content") mock_params.assert_called_once_with(user_infos={"user_id": user_id}, edit=False)
def test_lti_consumer_view_set_get_context_method_is_cached( self, mock_params): """ If "memory_cache" is defined, get_context method is cached for 9 minutes and 30 seconds to optimize db accesses without return stale oauth credentials. """ placeholder = Placeholder.objects.create(slot="test") lti_consumer = LTIConsumerFactory() model_instance = add_plugin( placeholder, LTIConsumerPlugin, "en", url=lti_consumer.url, lti_provider_id=lti_consumer.lti_provider_id, ) request = RequestFactory().get("/") request.user = AnonymousUser() request.session = {} request.toolbar = CMSToolbar(request) view_set = LTIConsumerViewsSet() with self.assertNumQueries(1): view_set.get_context(request, "v1.0", model_instance.pk) mock_params.assert_called_once() mock_params.reset_mock() with self.assertNumQueries(0): view_set.get_context(request, "v1.0", model_instance.pk) mock_params.assert_not_called()
def test_lti_consumer_api_get_context_edit_all_permissions( self, mock_params): """ A query with a user in edition mode and with all permissions required to change the plugin should get the instructor role. """ user = UserFactory() page = create_i18n_page("A page") placeholder = page.placeholders.get(slot="maincontent") lti_consumer = LTIConsumerFactory() model_instance = add_plugin( placeholder, LTIConsumerPlugin, "en", url=lti_consumer.url, lti_provider_id=lti_consumer.lti_provider_id, ) # Add all necessary model and object level permissions self.add_permission(user, "change_lticonsumer") self.add_permission(user, "change_page") PagePermission.objects.create( page=placeholder.page, user=user, can_add=False, can_change=True, can_delete=False, can_publish=False, can_move_page=False, ) request = RequestFactory().get( f"/api/v1.0/plugins/lti-consumer/{model_instance.pk}/context/", { "user_id": user.username, "lis_person_sourcedid": user.username, "lis_person_name_given:": user.username, "lis_person_contact_email_primary": user.email, }, ) request.user = user request.session = {} request.toolbar = CMSToolbar(request) request.toolbar.edit_mode_active = True view_set = LTIConsumerViewsSet() response = view_set.get_context(request, "v1.0", model_instance.pk) self.assertEqual(response.status_code, 200) mock_params.assert_called_once_with( user_infos={ "user_id": user.username, "lis_person_sourcedid": user.username, "lis_person_name_given:": user.username, "lis_person_contact_email_primary": user.email, }, edit=True, )
def test_lti_consumer_api_get_context_method_is_cached(self, mock_params): """ If "memory_cache" is defined, get_context method is cached for 5 minutes to optimize db accesses without returning stale oauth credentials. """ placeholder = Placeholder.objects.create(slot="test") lti_consumer = LTIConsumerFactory() model_instance = add_plugin( placeholder, LTIConsumerPlugin, "en", url=lti_consumer.url, lti_provider_id=lti_consumer.lti_provider_id, ) request = RequestFactory().get("/") request.user = AnonymousUser() request.session = {} request.toolbar = CMSToolbar(request) view_set = LTIConsumerViewsSet() with self.assertNumQueries(1): view_set.get_context(request, "v1.0", model_instance.pk) mock_params.assert_called_once_with(edit=False) mock_params.reset_mock() with self.assertNumQueries(0): view_set.get_context(request, "v1.0", model_instance.pk) mock_params.assert_not_called() # Check that cache is set separately for each language translation.activate("fr") with self.assertNumQueries(1): view_set.get_context(request, "v1.0", model_instance.pk) mock_params.assert_called_once_with(edit=False) mock_params.reset_mock() with self.assertNumQueries(0): view_set.get_context(request, "v1.0", model_instance.pk) mock_params.assert_not_called() translation.deactivate()
def test_lti_consumer_api_get_context_edit_missing_permissions( self, mock_params): """ A query with a user in edition mode but with missing permissions to change the plugin should not get the instructor role. """ user = UserFactory() page = create_i18n_page("A page") placeholder = page.placeholders.get(slot="maincontent") lti_consumer = LTIConsumerFactory() model_instance = add_plugin( placeholder, LTIConsumerPlugin, "en", url=lti_consumer.url, lti_provider_id=lti_consumer.lti_provider_id, ) # Add all necessary model and object level permissions except one skip = random.choice(range(3)) if skip != 0: self.add_permission(user, "change_lticonsumer") if skip != 1: self.add_permission(user, "change_page") if skip != 2: PagePermission.objects.create( page=placeholder.page, user=user, can_add=False, can_change=True, can_delete=False, can_publish=False, can_move_page=False, ) request = RequestFactory().get( f"/api/v1.0/plugins/lti-consumer/{model_instance.pk}/context/") request.user = user request.session = {} request.toolbar = CMSToolbar(request) request.toolbar.edit_mode_active = True view_set = LTIConsumerViewsSet() response = view_set.get_context(request, "v1.0", model_instance.pk) self.assertEqual(response.status_code, 200) mock_params.assert_called_once_with(edit=False)
def test_lti_consumer_api_get_context_is_automatic_resizing_default( self, _mock_params): """ The "is_automatic_resizing" parameter should default to True if it is not set in the provider configuration. """ page = create_i18n_page("A page") placeholder = page.placeholders.get(slot="maincontent") lti_consumer = LTIConsumerFactory() model_instance = add_plugin( placeholder, LTIConsumerPlugin, "en", url=lti_consumer.url, lti_provider_id=lti_consumer.lti_provider_id, is_automatic_resizing=lti_consumer.is_automatic_resizing, ) # Set from settings is_automatic_param = random.choice([True, False]) user_id = str(uuid.uuid4()) with override_settings(RICHIE_LTI_PROVIDERS={ "lti_provider_test": { "is_automatic_resizing": is_automatic_param } }): response = self.client.get( f"/api/v1.0/plugins/lti-consumer/{model_instance.pk}/context/", {"user_id": user_id}, ) self.assertEqual(response.status_code, 200) content = json.loads(response.content) self.assertEqual(content["is_automatic_resizing"], is_automatic_param) # Set from the instance with override_settings(RICHIE_LTI_PROVIDERS={"lti_provider_test": {}}): response = self.client.get( f"/api/v1.0/plugins/lti-consumer/{model_instance.pk}/context/", {"user_id": user_id}, ) self.assertEqual(response.status_code, 200) content = json.loads(response.content) self.assertTrue(content["is_automatic_resizing"])
def test_cms_plugins_get_lti_consumer_widget_props(self, _): """ Verify that LTI content consumption parameters are correctly built through content_parameters wrapper """ lti_consumer = LTIConsumerFactory() expected_content_parameters = { "lti_message_type": lti_consumer.lti_provider.get("display_name"), "lti_version": "LTI-1p0", "resource_link_id": str(lti_consumer.id), "context_id": "example.com", "user_id": "richie", "lis_person_contact_email_primary": "", "roles": "instructor", "oauth_consumer_key": lti_consumer.lti_provider.get("oauth_consumer_key"), "oauth_signature_method": "HMAC-SHA1", "oauth_timestamp": "1378916897", "oauth_nonce": "80966668944732164491378916897", "oauth_version": "1.0", "oauth_signature": "frVp4JuvT1mVXlxktiAUjQ7/1cw=", } expected_widget_props = { "url": lti_consumer.url, "content_parameters": expected_content_parameters, "automatic_resizing": True, } self.assertDictEqual( expected_widget_props, LTIConsumerPlugin.get_lti_consumer_widget_props(lti_consumer, edit=True), )
def test_lti_consumer_forms_shared_secret_placeholder(self): """ The "form_shared_secret" should act as a proxy to the "shared_secret" field on the model and allow hiding the shared secret from the form after creation. """ lti_consumer = LTIConsumerFactory( lti_provider_id=None, oauth_consumer_key="thisIsAtestOauthConsumerKey", shared_secret="thisIsAtestSharedSecret", ) form = LTIConsumerForm(instance=lti_consumer) rendered = form.as_p() self.assertIn( ('<input type="password" name="form_shared_secret" ' 'value="%%shared_secret_placeholder%%" onfocus="this.value=''" ' 'maxlength="50" id="id_form_shared_secret">'), rendered, ) self.assertNotIn('id="shared_secret"', rendered) self.assertNotIn("thisIsAtestSharedSecret", rendered) # Submitting the placeholder value for the secret should not # impact the field on the model data = form.initial data["form_shared_secret"] = "%%shared_secret_placeholder%%" form = LTIConsumerForm(instance=lti_consumer, data=data) form.is_valid() self.assertTrue(form.is_valid()) form.save() lti_consumer.refresh_from_db() self.assertEqual(lti_consumer.shared_secret, "thisIsAtestSharedSecret") # Submitting a new secret should update the corresponding field on the model data["form_shared_secret"] = "NewSharedSecret" form = LTIConsumerForm(instance=lti_consumer, data=data) form.is_valid() self.assertTrue(form.is_valid()) form.save() lti_consumer.refresh_from_db() self.assertEqual(lti_consumer.shared_secret, "NewSharedSecret")
def test_lti_consumer_factories_create_without_lti_provider(self): """The url field should be set to a random value for a custom provider.""" lti_consumer = LTIConsumerFactory(lti_provider_id=None) self.assertIsNotNone(lti_consumer.url)
def test_factories_lti_consumer_create_success(self): """Factory creation success.""" lti_consumer = LTIConsumerFactory() self.assertIsNotNone(lti_consumer.url)