diff --git a/aspects/extensions/filters.py b/aspects/extensions/filters.py index b1583a9..6b2f010 100644 --- a/aspects/extensions/filters.py +++ b/aspects/extensions/filters.py @@ -8,10 +8,10 @@ from openedx_filters import PipelineStep from web_fragments.fragment import Fragment -from aspects.utils import update_context +from aspects.utils import generate_superset_context TEMPLATE_ABSOLUTE_PATH = "/instructor_dashboard/" -BLOCK_CATEGORY = "superset" +BLOCK_CATEGORY = "aspects" class AddSupersetTab(PipelineStep): @@ -31,32 +31,27 @@ def run_filter( settings, "SUPERSET_INSTRUCTOR_DASHBOARD", {} ) dashboard_uuid = instructor_dashboard_config.get("dashboard_uuid") - extra_filters_format = getattr(settings, "SUPERSET_EXTRA_FILTERS_FORMAT", []) - default_filters = [ "org = '{course.org}'", "course_name = '{course.display_name}'", "course_run = '{course.id.run}'", ] - filters = default_filters + extra_filters_format - context = update_context( - context, dashboard_uuid=dashboard_uuid, filters=filters + context = generate_superset_context( + context, dashboard_uuid, filters ) - template = Template(self.resource_string("static/html/superset.html")) + template = Template(self.resource_string("static/html/superset.html")) html = template.render(Context(context)) frag = Fragment(html) - frag.add_css(self.resource_string("static/css/superset.css")) frag.add_javascript(self.resource_string("static/js/embed_dashboard.js")) - section_data = { "fragment": frag, "section_key": BLOCK_CATEGORY, - "section_display_name": "Aspects", + "section_display_name": BLOCK_CATEGORY.title(), "course_id": str(course.id), "template_path_prefix": TEMPLATE_ABSOLUTE_PATH, } diff --git a/aspects/settings/common.py b/aspects/settings/common.py index 91bd07d..3896381 100644 --- a/aspects/settings/common.py +++ b/aspects/settings/common.py @@ -5,32 +5,8 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/2.22/ref/settings/ """ - from aspects import ROOT_DIRECTORY -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/2.22/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "secret-key" - - -# Application definition - -INSTALLED_APPS = [ - "aspects", -] - - -# Internationalization -# https://docs.djangoproject.com/en/2.22/topics/i18n/ - -LANGUAGE_CODE = "en-us" - -TIME_ZONE = "UTC" - -USE_TZ = True - def plugin_settings(settings): """ diff --git a/aspects/settings/test.py b/aspects/settings/test.py deleted file mode 100644 index dd00b5d..0000000 --- a/aspects/settings/test.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -Common Django settings for eox_hooks project. -For more information on this file, see -https://docs.djangoproject.com/en/2.22/topics/settings/ -For the full list of settings and their values, see -https://docs.djangoproject.com/en/2.22/ref/settings/ -""" - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/2.22/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": "default.db", - "USER": "", - "PASSWORD": "", - "HOST": "", - "PORT": "", - }, - "read_replica": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": "read_replica.db", - "USER": "", - "PASSWORD": "", - "HOST": "", - "PORT": "", - }, -} - -SECRET_KEY = "not-so-secret-key" - -# Internationalization -# https://docs.djangoproject.com/en/2.22/topics/i18n/ - -LANGUAGE_CODE = "en-us" - -TIME_ZONE = "UTC" - -USE_TZ = True diff --git a/aspects/utils.py b/aspects/utils.py index 12d2b12..f502283 100644 --- a/aspects/utils.py +++ b/aspects/utils.py @@ -16,12 +16,10 @@ logger = logging.getLogger(__name__) -def update_context( # pylint: disable=dangerous-default-value +def generate_superset_context( # pylint: disable=dangerous-default-value context, - superset_config={}, dashboard_uuid="", - filters=[], - user=None, + filters=[] ): """ Update context with superset token and dashboard id. @@ -31,16 +29,13 @@ def update_context( # pylint: disable=dangerous-default-value superset_config (dict): superset config. dashboard_uuid (str): superset dashboard uuid. filters (list): list of filters to apply to the dashboard. - user (User): user object. """ course = context["course"] + user = get_current_user() - if user is None: - user = get_current_user() superset_token, dashboard_uuid = generate_guest_token( user=user, course=course, - superset_config=superset_config, dashboard_uuid=dashboard_uuid, filters=filters, ) @@ -63,7 +58,7 @@ def update_context( # pylint: disable=dangerous-default-value return context -def generate_guest_token(user, course, superset_config, dashboard_uuid, filters): +def generate_guest_token(user, course, dashboard_uuid, filters): """ Generate a Superset guest token for the user. @@ -75,10 +70,9 @@ def generate_guest_token(user, course, superset_config, dashboard_uuid, filters) tuple: Superset guest token and dashboard id. or None, exception if Superset is missconfigured or cannot generate guest token. """ - if not superset_config: - superset_config = getattr(settings, "SUPERSET_CONFIG", {}) + superset_config = getattr(settings, "SUPERSET_CONFIG", {}) - superset_internal_host = superset_config.get("service_url", "http://superset:8088/") + superset_internal_host = superset_config.get("service_url") superset_username = superset_config.get("username") superset_password = superset_config.get("password") diff --git a/test_settings.py b/test_settings.py index c51c483..4559fc7 100644 --- a/test_settings.py +++ b/test_settings.py @@ -14,6 +14,7 @@ def root(*args): """ return join(abspath(dirname(__file__)), *args) +DEBUG = True DATABASES = { 'default': { @@ -57,3 +58,16 @@ def root(*args): ], }, }] + +SUPERSET_INSTRUCTOR_DASHBOARD = { + "dashboard_slug": "test-dashboard-slug", + "dashboard_uuid": "test-dashboard-uuid", +} + +SUPERSET_CONFIG = { + "url": "http://dummy-superset-url:8088", + "username": "superset", + "password": "superset", +} + +SUPERSET_EXTRA_FILTERS_FORMAT = [] \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_dummy.py b/tests/test_dummy.py deleted file mode 100644 index 3a92518..0000000 --- a/tests/test_dummy.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -Dummy test to check if the test suite is working. -""" -import pytest - - -@pytest.mark.skip(reason="Placeholder to allow pytest to succeed before real tests are in place.") -def test_placeholder(): - """ - TODO: Delete this test once there are real tests. - """ diff --git a/tests/test_filters.py b/tests/test_filters.py new file mode 100644 index 0000000..4bdee4a --- /dev/null +++ b/tests/test_filters.py @@ -0,0 +1,46 @@ +""" +Tests for the filters module. +""" + +from unittest import TestCase +from unittest.mock import Mock, patch + +from aspects.extensions.filters import BLOCK_CATEGORY, AddSupersetTab + + +class TestFilters(TestCase): + """ + Test suite for the LimeSurveyXBlock filters. + """ + + def setUp(self) -> None: + """ + Set up the test suite. + """ + self.filter = AddSupersetTab(filter_type=Mock(), running_pipeline=Mock()) + self.template_name = "test-template-name" + self.context = {"course": Mock()} + + @patch("aspects.extensions.filters.generate_superset_context") + def test_run_filter(self, mock_generate_superset_context): + """ + Check the filter is not executed when there are no LimeSurvey blocks in the course. + + Expected result: + - The context is returned without modifications. + """ + mock_generate_superset_context.return_value = { + "sections": [], + } + + context = self.filter.run_filter(self.context, self.template_name) + + self.assertDictContainsSubset( + { + "course_id": str(self.context["course"].id), + "section_key": BLOCK_CATEGORY, + "section_display_name": BLOCK_CATEGORY.title(), + "template_path_prefix": "/instructor_dashboard/", + }, + context["context"]["sections"][0], + ) diff --git a/tests/test_settings.py b/tests/test_settings.py new file mode 100644 index 0000000..4d7417e --- /dev/null +++ b/tests/test_settings.py @@ -0,0 +1,58 @@ +""" +Test plugin settings for commond, devstack and production environments +""" + +from django.conf import settings +from django.test import TestCase + +from aspects.settings import common as common_settings +from aspects.settings import production as production_setttings + + +class TestPluginSettings(TestCase): + """ + Tests plugin settings + """ + + def test_common_settings(self): + """ + Test common settings + """ + settings.MAKO_TEMPLATE_DIRS_BASE = [] + common_settings.plugin_settings(settings) + self.assertIn("MAKO_TEMPLATE_DIRS_BASE", settings.__dict__) + self.assertIn("url", settings.SUPERSET_CONFIG) + self.assertIn("username", settings.SUPERSET_CONFIG) + self.assertIn("password", settings.SUPERSET_CONFIG) + self.assertIn("dashboard_slug", settings.SUPERSET_INSTRUCTOR_DASHBOARD) + self.assertIn("dashboard_uuid", settings.SUPERSET_INSTRUCTOR_DASHBOARD) + self.assertIsNotNone(settings.SUPERSET_EXTRA_FILTERS_FORMAT) + + def test_production_settings(self): + """ + Test production settings + """ + settings.ENV_TOKENS = { + "SUPERSET_CONFIG": { + "url": "http://superset.local.overhang.io:8088", + "username": "superset", + "password": "superset", + }, + "SUPERSET_INSTRUCTOR_DASHBOARD": { + "dashboard_slug": "instructor-dashboard", + "dashboard_uuid": "1d6bf904-f53f-47fd-b1c9-6cd7e284d286", + }, + "SUPERSET_EXTRA_FILTERS_FORMAT": [], + } + production_setttings.plugin_settings(settings) + self.assertEqual( + settings.SUPERSET_CONFIG, settings.ENV_TOKENS["SUPERSET_CONFIG"] + ) + self.assertEqual( + settings.SUPERSET_INSTRUCTOR_DASHBOARD, + settings.ENV_TOKENS["SUPERSET_INSTRUCTOR_DASHBOARD"], + ) + self.assertEqual( + settings.SUPERSET_EXTRA_FILTERS_FORMAT, + settings.ENV_TOKENS["SUPERSET_EXTRA_FILTERS_FORMAT"], + ) diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..7966aa9 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,100 @@ +""" +Tests for the utils module. +""" + +from collections import namedtuple +from unittest import TestCase +from unittest.mock import Mock, patch + +from django.conf import settings +from django.test.utils import override_settings + +from aspects.utils import generate_superset_context + +User = namedtuple("User", ["username"]) + + +class TestContext(TestCase): + + @patch("aspects.utils.generate_guest_token") + def test_generate_superset_context(self, mock_generate_guest_token): + """ + Test generate_superset_context + """ + course_mock = Mock() + filter_mock = Mock() + context = {"course": course_mock} + mock_generate_guest_token.return_value = ("test-token", "test-dashboard-uuid") + + context = generate_superset_context( + context, + dashboard_uuid="test-dashboard-uuid", + filters=[filter_mock], + ) + + self.assertEqual(context["superset_token"], "test-token") + self.assertEqual(context["dashboard_uuid"], "test-dashboard-uuid") + self.assertEqual(context["superset_url"], settings.SUPERSET_CONFIG.get("host")) + self.assertNotIn("exception", context) + + @patch("aspects.utils.SupersetClient") + def test_generate_superset_context_with_superset_client_exception(self, mock_superset_client): + """ + Test generate_superset_context + """ + course_mock = Mock() + filter_mock = Mock() + context = {"course": course_mock} + mock_superset_client.side_effect = Exception("test-exception") + + context = generate_superset_context( + context, + dashboard_uuid="test-dashboard-uuid", + filters=[filter_mock], + ) + + self.assertIn("exception", context) + + @patch("aspects.utils.SupersetClient") + @patch("aspects.utils.get_current_user") + def test_generate_superset_context_succesful(self, mock_get_current_user, mock_superset_client): + """ + Test generate_superset_context + """ + course_mock = Mock() + filter_mock = Mock() + context = {"course": course_mock} + response_mock = Mock(status_code=200) + mock_superset_client.return_value.session.post.return_value = response_mock + response_mock.json.return_value = { + "token": "test-token", + } + mock_get_current_user.return_value = User(username="test-user") + + context = generate_superset_context( + context, + dashboard_uuid="test-dashboard-uuid", + filters=[filter_mock], + ) + + self.assertEqual(context["superset_token"], "test-token") + self.assertEqual(context["dashboard_uuid"], "test-dashboard-uuid") + self.assertEqual(context["superset_url"], settings.SUPERSET_CONFIG.get("host")) + + @patch("aspects.utils.get_current_user") + def test_generate_superset_context_with_exception(self, mock_get_current_user): + """ + Test generate_superset_context + """ + course_mock = Mock() + filter_mock = Mock() + mock_get_current_user.return_value = User(username="test-user") + context = {"course": course_mock} + + context = generate_superset_context( + context, + dashboard_uuid="test-dashboard-uuid", + filters=[filter_mock], + ) + + self.assertIn("exception", context)