diff --git a/cms/envs/test.py b/cms/envs/test.py index d8fa4ff8cc..cf27e957bd 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -27,6 +27,8 @@ from .common import * # import settings from LMS for consistent behavior with CMS from lms.envs.test import ( # pylint: disable=wrong-import-order + BLOCKSTORE_USE_BLOCKSTORE_APP_API, + BLOCKSTORE_API_URL, COMPREHENSIVE_THEME_DIRS, # unimport:skip DEFAULT_FILE_STORAGE, ECOMMERCE_API_URL, @@ -182,6 +184,13 @@ CACHES = { RUN_BLOCKSTORE_TESTS = os.environ.get('EDXAPP_RUN_BLOCKSTORE_TESTS', 'no').lower() in ('true', 'yes', '1') BLOCKSTORE_API_URL = os.environ.get('EDXAPP_BLOCKSTORE_API_URL', "http://edx.devstack.blockstore-test:18251/api/v1/") BLOCKSTORE_API_AUTH_TOKEN = os.environ.get('EDXAPP_BLOCKSTORE_API_AUTH_TOKEN', 'edxapp-test-key') +BUNDLE_ASSET_STORAGE_SETTINGS = dict( + STORAGE_CLASS='django.core.files.storage.FileSystemStorage', + STORAGE_KWARGS=dict( + location=MEDIA_ROOT, + base_url=MEDIA_URL, + ), +) ################################# CELERY ###################################### diff --git a/cms/urls.py b/cms/urls.py index 541e2aee85..8a881339e2 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -271,6 +271,8 @@ if settings.DEBUG: except ImportError: pass + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + urlpatterns += static( settings.VIDEO_IMAGE_SETTINGS['STORAGE_KWARGS']['base_url'], document_root=settings.VIDEO_IMAGE_SETTINGS['STORAGE_KWARGS']['location'] diff --git a/lms/envs/test.py b/lms/envs/test.py index 406b5156ac..b50f3e5e8a 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -223,16 +223,6 @@ CACHES = { }, } -############################### BLOCKSTORE ##################################### -# Blockstore tests -RUN_BLOCKSTORE_TESTS = os.environ.get('EDXAPP_RUN_BLOCKSTORE_TESTS', 'no').lower() in ('true', 'yes', '1') -BLOCKSTORE_API_URL = os.environ.get('EDXAPP_BLOCKSTORE_API_URL', "http://edx.devstack.blockstore-test:18251/api/v1/") -BLOCKSTORE_API_AUTH_TOKEN = os.environ.get('EDXAPP_BLOCKSTORE_API_AUTH_TOKEN', 'edxapp-test-key') -XBLOCK_RUNTIME_V2_EPHEMERAL_DATA_CACHE = 'blockstore' # This must be set to a working cache for the tests to pass - -# Dummy secret key for dev -SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd' - ############################# SECURITY SETTINGS ################################ # Default to advanced security in common.py, so tests can reset here to use # a simpler security model @@ -314,7 +304,7 @@ ENTERPRISE_MARKETING_FOOTER_QUERY_PARAMS = OrderedDict([ ############################ STATIC FILES ############################# DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' MEDIA_ROOT = TEST_ROOT / "uploads" -MEDIA_URL = "/static/uploads/" +MEDIA_URL = "/uploads/" STATICFILES_DIRS.append(("uploads", MEDIA_ROOT)) _NEW_STATICFILES_DIRS = [] @@ -553,6 +543,24 @@ add_plugins(__name__, ProjectType.LMS, SettingsType.TEST) derive_settings(__name__) +############################### BLOCKSTORE ##################################### +# Blockstore tests +RUN_BLOCKSTORE_TESTS = os.environ.get('EDXAPP_RUN_BLOCKSTORE_TESTS', 'no').lower() in ('true', 'yes', '1') +BLOCKSTORE_USE_BLOCKSTORE_APP_API = not RUN_BLOCKSTORE_TESTS +BLOCKSTORE_API_URL = os.environ.get('EDXAPP_BLOCKSTORE_API_URL', "http://edx.devstack.blockstore-test:18251/api/v1/") +BLOCKSTORE_API_AUTH_TOKEN = os.environ.get('EDXAPP_BLOCKSTORE_API_AUTH_TOKEN', 'edxapp-test-key') +XBLOCK_RUNTIME_V2_EPHEMERAL_DATA_CACHE = 'blockstore' # This must be set to a working cache for the tests to pass +BUNDLE_ASSET_STORAGE_SETTINGS = dict( + STORAGE_CLASS='django.core.files.storage.FileSystemStorage', + STORAGE_KWARGS=dict( + location=MEDIA_ROOT, + base_url=MEDIA_URL, + ), +) + +# Dummy secret key for dev +SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd' + ############### Settings for edx-rbac ############### SYSTEM_WIDE_ROLE_CLASSES = os.environ.get("SYSTEM_WIDE_ROLE_CLASSES", []) diff --git a/openedx/core/djangoapps/content_libraries/api.py b/openedx/core/djangoapps/content_libraries/api.py index 9e22df4196..c1d95c9d68 100644 --- a/openedx/core/djangoapps/content_libraries/api.py +++ b/openedx/core/djangoapps/content_libraries/api.py @@ -65,7 +65,7 @@ from django.conf import settings from django.contrib.auth.models import AbstractUser, Group from django.core.exceptions import PermissionDenied from django.core.validators import validate_unicode_slug -from django.db import IntegrityError +from django.db import IntegrityError, transaction from django.utils.translation import gettext as _ from elasticsearch.exceptions import ConnectionError as ElasticConnectionError from lxml import etree @@ -436,15 +436,18 @@ def create_library( ) # Now create the library reference in our database: try: - ref = ContentLibrary.objects.create( - org=org, - slug=slug, - type=library_type, - bundle_uuid=bundle.uuid, - allow_public_learning=allow_public_learning, - allow_public_read=allow_public_read, - license=library_license, - ) + # Atomic transaction required because if this fails, + # we need to delete the bundle in the exception handler. + with transaction.atomic(): + ref = ContentLibrary.objects.create( + org=org, + slug=slug, + type=library_type, + bundle_uuid=bundle.uuid, + allow_public_learning=allow_public_learning, + allow_public_read=allow_public_read, + license=library_license, + ) except IntegrityError: delete_bundle(bundle.uuid) raise LibraryAlreadyExists(slug) # lint-amnesty, pylint: disable=raise-missing-from diff --git a/openedx/core/djangoapps/content_libraries/tests/base.py b/openedx/core/djangoapps/content_libraries/tests/base.py index 38138bac2e..34aee552ee 100644 --- a/openedx/core/djangoapps/content_libraries/tests/base.py +++ b/openedx/core/djangoapps/content_libraries/tests/base.py @@ -4,10 +4,12 @@ Tests for Blockstore-based Content Libraries from contextlib import contextmanager from io import BytesIO from urllib.parse import urlencode -import unittest -from unittest.mock import patch +from unittest import mock, skipUnless +from urllib.parse import urlparse from django.conf import settings +from django.test import LiveServerTestCase +from django.test.client import RequestFactory from django.test.utils import override_settings from organizations.models import Organization from rest_framework.test import APITestCase, APIClient @@ -47,8 +49,39 @@ URL_BLOCK_METADATA_URL = '/api/xblock/v2/xblocks/{block_key}/' URL_BLOCK_XBLOCK_HANDLER = '/api/xblock/v2/xblocks/{block_key}/handler/{user_id}-{secure_token}/{handler_name}/' -# Decorator for tests that require blockstore -requires_blockstore = unittest.skipUnless(settings.RUN_BLOCKSTORE_TESTS, "Requires a running Blockstore server") +# Decorators for tests that require the blockstore service/app +requires_blockstore = skipUnless(settings.RUN_BLOCKSTORE_TESTS, "Requires a running Blockstore server") + +requires_blockstore_app = skipUnless(settings.BLOCKSTORE_USE_BLOCKSTORE_APP_API, "Requires blockstore app") + + +class BlockstoreAppTestMixin: + """ + Sets up the environment for tests to be run using the installed Blockstore app. + """ + def setUp(self): + """ + Ensure there's an active request, so that bundle file URLs can be made absolute. + """ + super().setUp() + + # Patch the blockstore get_current_request to use our live_server_url + mock.patch('blockstore.apps.api.methods.get_current_request', + mock.Mock(return_value=self._get_current_request())).start() + self.addCleanup(mock.patch.stopall) + + def _get_current_request(self): + """ + Returns a request object using the live_server_url, if available. + """ + request_args = {} + if hasattr(self, 'live_server_url'): + live_server_url = urlparse(self.live_server_url) + name, port = live_server_url.netloc.split(':') + request_args['SERVER_NAME'] = name + request_args['SERVER_PORT'] = port or '80' + request_args['wsgi.url_scheme'] = live_server_url.scheme + return RequestFactory().request(**request_args) def elasticsearch_test(func): @@ -63,9 +96,11 @@ def elasticsearch_test(func): 'host': settings.TEST_ELASTICSEARCH_HOST, 'port': settings.TEST_ELASTICSEARCH_PORT, }])(func) - func = patch("openedx.core.djangoapps.content_libraries.libraries_index.SearchIndexerBase.SEARCH_KWARGS", new={ - 'refresh': 'wait_for' - })(func) + func = mock.patch( + "openedx.core.djangoapps.content_libraries.libraries_index.SearchIndexerBase.SEARCH_KWARGS", + new={ + 'refresh': 'wait_for' + })(func) return func else: @classmethod @@ -77,20 +112,19 @@ def elasticsearch_test(func): size=MAX_SIZE ) - func = patch( + func = mock.patch( "openedx.core.djangoapps.content_libraries.libraries_index.SearchIndexerBase.SEARCH_KWARGS", new={} )(func) - func = patch( + func = mock.patch( "openedx.core.djangoapps.content_libraries.libraries_index.SearchIndexerBase._perform_elastic_search", new=mock_perform )(func) return func -@requires_blockstore @skip_unless_cms # Content Libraries REST API is only available in Studio -class ContentLibrariesRestApiTest(APITestCase): +class _ContentLibrariesRestApiTestMixin: """ Base class for Blockstore-based Content Libraries test that use the REST API @@ -350,3 +384,26 @@ class ContentLibrariesRestApiTest(APITestCase): """ url = URL_BLOCK_GET_HANDLER_URL.format(block_key=block_key, handler_name=handler_name) return self._api('get', url, None, expect_response=200)["handler_url"] + + +@requires_blockstore +class ContentLibrariesRestApiBlockstoreServiceTest(_ContentLibrariesRestApiTestMixin, APITestCase): + """ + Base class for Blockstore-based Content Libraries test that use the REST API + and the standalone Blockstore service. + """ + + +@requires_blockstore_app +class ContentLibrariesRestApiTest( + _ContentLibrariesRestApiTestMixin, + BlockstoreAppTestMixin, + APITestCase, + LiveServerTestCase, +): + """ + Base class for Blockstore-based Content Libraries test that use the REST API + and the installed Blockstore app. + + We run this test with a live server, so that the blockstore asset files can be served. + """ diff --git a/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py b/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py index 7cdbe54eeb..9bc3405d98 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py @@ -19,6 +19,7 @@ from xblock.core import XBlock from openedx.core.djangoapps.content_libraries.libraries_index import LibraryBlockIndexer, ContentLibraryIndexer from openedx.core.djangoapps.content_libraries.tests.base import ( + ContentLibrariesRestApiBlockstoreServiceTest, ContentLibrariesRestApiTest, elasticsearch_test, URL_BLOCK_METADATA_URL, @@ -33,8 +34,7 @@ from common.djangoapps.student.tests.factories import UserFactory @ddt.ddt -@elasticsearch_test -class ContentLibrariesTest(ContentLibrariesRestApiTest): +class ContentLibrariesTestMixin: """ General tests for Blockstore-based Content Libraries @@ -872,6 +872,26 @@ class ContentLibrariesTest(ContentLibrariesRestApiTest): assert len(types) > 1 +@elasticsearch_test +class ContentLibrariesBlockstoreServiceTest( + ContentLibrariesTestMixin, + ContentLibrariesRestApiBlockstoreServiceTest, +): + """ + General tests for Blockstore-based Content Libraries, using the standalone Blockstore service. + """ + + +@elasticsearch_test +class ContentLibrariesTest( + ContentLibrariesTestMixin, + ContentLibrariesRestApiTest, +): + """ + General tests for Blockstore-based Content Libraries, using the installed Blockstore app. + """ + + @ddt.ddt class ContentLibraryXBlockValidationTest(APITestCase): """Tests only focused on service validation, no Blockstore needed.""" @@ -941,8 +961,7 @@ class AltBlock(XBlock): @ddt.ddt -@elasticsearch_test -class ContentLibrariesXBlockTypeOverrideTest(ContentLibrariesRestApiTest): +class ContentLibrariesXBlockTypeOverrideTestMixin: """ Tests for Blockstore-based Content Libraries XBlock API, where the expected XBlock type returned is overridden in the request. @@ -1073,3 +1092,23 @@ class ContentLibrariesXBlockTypeOverrideTest(ContentLibrariesRestApiTest): assert f"lb:CL-TEST:handler-{slug}:video:handler-{slug}" in response['transcripts']['en'] del response['transcripts']['en'] assert response == expected_response + + +@elasticsearch_test +class ContentLibrariesXBlockTypeOverrideBlockstoreServiceTest( + ContentLibrariesXBlockTypeOverrideTestMixin, + ContentLibrariesRestApiBlockstoreServiceTest, +): + """ + Tests for the Content Libraries XBlock API type override using the standalone Blockstore service. + """ + + +@elasticsearch_test +class ContentLibrariesXBlockTypeOverrideTest( + ContentLibrariesXBlockTypeOverrideTestMixin, + ContentLibrariesRestApiTest, +): + """ + Tests for the Content Libraries XBlock API type override using the installed Blockstore app. + """ diff --git a/openedx/core/djangoapps/content_libraries/tests/test_libraries_index.py b/openedx/core/djangoapps/content_libraries/tests/test_libraries_index.py index 194786db51..fb6464c35c 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_libraries_index.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_libraries_index.py @@ -10,12 +10,14 @@ from opaque_keys.edx.locator import LibraryLocatorV2, LibraryUsageLocatorV2 from search.search_engine_base import SearchEngine from openedx.core.djangoapps.content_libraries.libraries_index import ContentLibraryIndexer, LibraryBlockIndexer -from openedx.core.djangoapps.content_libraries.tests.base import ContentLibrariesRestApiTest, elasticsearch_test +from openedx.core.djangoapps.content_libraries.tests.base import ( + ContentLibrariesRestApiBlockstoreServiceTest, + ContentLibrariesRestApiTest, + elasticsearch_test, +) -@override_settings(FEATURES={**settings.FEATURES, 'ENABLE_CONTENT_LIBRARY_INDEX': True}) -@elasticsearch_test -class ContentLibraryIndexerTest(ContentLibrariesRestApiTest): +class ContentLibraryIndexerTestMixin: """ Tests the operation of ContentLibraryIndexer """ @@ -181,7 +183,27 @@ class ContentLibraryIndexerTest(ContentLibrariesRestApiTest): @override_settings(FEATURES={**settings.FEATURES, 'ENABLE_CONTENT_LIBRARY_INDEX': True}) @elasticsearch_test -class LibraryBlockIndexerTest(ContentLibrariesRestApiTest): +class ContentLibraryIndexerBlockstoreServiceTest( + ContentLibraryIndexerTestMixin, + ContentLibrariesRestApiBlockstoreServiceTest, +): + """ + Tests the operation of ContentLibraryIndexer using the standalone Blockstore service. + """ + + +@override_settings(FEATURES={**settings.FEATURES, 'ENABLE_CONTENT_LIBRARY_INDEX': True}) +@elasticsearch_test +class ContentLibraryIndexerTest( + ContentLibraryIndexerTestMixin, + ContentLibrariesRestApiTest, +): + """ + Tests the operation of ContentLibraryIndexer using the installed Blockstore app. + """ + + +class LibraryBlockIndexerTestMixin: """ Tests the operation of LibraryBlockIndexer """ @@ -279,3 +301,25 @@ class LibraryBlockIndexerTest(ContentLibrariesRestApiTest): LibraryBlockIndexer.get_items([block['id']]) self._delete_library(lib['id']) assert LibraryBlockIndexer.get_items([block['id']]) == [] + + +@override_settings(FEATURES={**settings.FEATURES, 'ENABLE_CONTENT_LIBRARY_INDEX': True}) +@elasticsearch_test +class LibraryBlockIndexerBlockstoreServiceTest( + LibraryBlockIndexerTestMixin, + ContentLibrariesRestApiBlockstoreServiceTest, +): + """ + Tests the operation of LibraryBlockIndexer using the standalone Blockstore service. + """ + + +@override_settings(FEATURES={**settings.FEATURES, 'ENABLE_CONTENT_LIBRARY_INDEX': True}) +@elasticsearch_test +class LibraryBlockIndexerTest( + LibraryBlockIndexerTestMixin, + ContentLibrariesRestApiTest, +): + """ + Tests the operation of LibraryBlockIndexer using the installed Blockstore app. + """ diff --git a/openedx/core/djangoapps/content_libraries/tests/test_runtime.py b/openedx/core/djangoapps/content_libraries/tests/test_runtime.py index 36e768bce1..62798f97a5 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_runtime.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_runtime.py @@ -6,7 +6,8 @@ from gettext import GNUTranslations from completion.test_utils import CompletionWaffleTestMixin from django.db import connections -from django.test import TestCase, override_settings +from django.test import LiveServerTestCase, TestCase, override_settings +from django.utils.text import slugify from organizations.models import Organization from rest_framework.test import APIClient from xblock.core import XBlock @@ -14,7 +15,9 @@ from xblock.core import XBlock from lms.djangoapps.courseware.model_data import get_score from openedx.core.djangoapps.content_libraries import api as library_api from openedx.core.djangoapps.content_libraries.tests.base import ( + BlockstoreAppTestMixin, requires_blockstore, + requires_blockstore_app, URL_BLOCK_RENDER_VIEW, URL_BLOCK_GET_HANDLER_URL, URL_BLOCK_METADATA_URL, @@ -33,26 +36,27 @@ class ContentLibraryContentTestMixin: """ Mixin for content library tests that creates two students and a library. """ - @classmethod - def setUpClass(cls): - super().setUpClass() + def setUp(self): + super().setUp() # Create a couple students that the tests can use - cls.student_a = UserFactory.create(username="Alice", email="alice@example.com", password="edx") - cls.student_b = UserFactory.create(username="Bob", email="bob@example.com", password="edx") + self.student_a = UserFactory.create(username="Alice", email="alice@example.com", password="edx") + self.student_b = UserFactory.create(username="Bob", email="bob@example.com", password="edx") + # Create a collection using Blockstore API directly only because there # is not yet any Studio REST API for doing so: - cls.collection = blockstore_api.create_collection("Content Library Test Collection") + self.collection = blockstore_api.create_collection("Content Library Test Collection") # Create an organization - cls.organization = Organization.objects.create( + self.organization = Organization.objects.create( name="Content Libraries Tachyon Exploration & Survey Team", short_name="CL-TEST", ) - cls.library = library_api.create_library( - collection_uuid=cls.collection.uuid, + _, slug = self.id().rsplit('.', 1) + self.library = library_api.create_library( + collection_uuid=self.collection.uuid, library_type=COMPLEX, - org=cls.organization, - slug=cls.__name__, - title=(cls.__name__ + " Test Lib"), + org=self.organization, + slug=slugify(slug), + title=(f"{slug} Test Lib"), description="", allow_public_learning=True, allow_public_read=False, @@ -60,10 +64,7 @@ class ContentLibraryContentTestMixin: ) -@requires_blockstore -# EphemeralKeyValueStore requires a working cache, and the default test cache doesn't work: -@override_settings(XBLOCK_RUNTIME_V2_EPHEMERAL_DATA_CACHE='blockstore') -class ContentLibraryRuntimeTest(ContentLibraryContentTestMixin, TestCase): +class ContentLibraryRuntimeTestMixin(ContentLibraryContentTestMixin): """ Basic tests of the Blockstore-based XBlock runtime using XBlocks in a content library. @@ -181,10 +182,29 @@ class ContentLibraryRuntimeTest(ContentLibraryContentTestMixin, TestCase): @requires_blockstore +# EphemeralKeyValueStore requires a working cache, and the default test cache doesn't work: +@override_settings(XBLOCK_RUNTIME_V2_EPHEMERAL_DATA_CACHE='blockstore') +class ContentLibraryRuntimeBServiceTest(ContentLibraryRuntimeTestMixin, TestCase): + """ + Tests XBlock runtime using XBlocks in a content library using the standalone Blockstore service. + """ + + +@requires_blockstore_app +# EphemeralKeyValueStore requires a working cache, and the default test cache doesn't work: +@override_settings(XBLOCK_RUNTIME_V2_EPHEMERAL_DATA_CACHE='blockstore') +class ContentLibraryRuntimeTest(ContentLibraryRuntimeTestMixin, BlockstoreAppTestMixin, LiveServerTestCase): + """ + Tests XBlock runtime using XBlocks in a content library using the installed Blockstore app. + + We run this test with a live server, so that the blockstore asset files can be served. + """ + + # We can remove the line below to enable this in Studio once we implement a session-backed # field data store which we can use for both studio users and anonymous users @skip_unless_lms -class ContentLibraryXBlockUserStateTest(ContentLibraryContentTestMixin, TestCase): +class ContentLibraryXBlockUserStateTestMixin(ContentLibraryContentTestMixin): """ Test that the Blockstore-based XBlock runtime can store and retrieve student state for XBlocks when learners access blocks directly in a library context, @@ -487,8 +507,27 @@ class ContentLibraryXBlockUserStateTest(ContentLibraryContentTestMixin, TestCase @requires_blockstore +class ContentLibraryXBlockUserStateBServiceTest(ContentLibraryXBlockUserStateTestMixin, TestCase): + """ + Tests XBlock user state for XBlocks in a content library using the standalone Blockstore service. + """ + + +@requires_blockstore_app +class ContentLibraryXBlockUserStateTest( + ContentLibraryXBlockUserStateTestMixin, + BlockstoreAppTestMixin, + LiveServerTestCase, +): + """ + Tests XBlock user state for XBlocks in a content library using the installed Blockstore app. + + We run this test with a live server, so that the blockstore asset files can be served. + """ + + @skip_unless_lms # No completion tracking in Studio -class ContentLibraryXBlockCompletionTest(ContentLibraryContentTestMixin, CompletionWaffleTestMixin, TestCase): +class ContentLibraryXBlockCompletionTestMixin(ContentLibraryContentTestMixin, CompletionWaffleTestMixin): """ Test that the Blockstore-based XBlocks can track their completion status using the completion library. @@ -539,3 +578,30 @@ class ContentLibraryXBlockCompletionTest(ContentLibraryContentTestMixin, Complet # Now the block is completed assert get_block_completion_status() == 1 + + +@requires_blockstore +class ContentLibraryXBlockCompletionBServiceTest( + ContentLibraryXBlockCompletionTestMixin, + CompletionWaffleTestMixin, + TestCase, +): + """ + Test that the Blockstore-based XBlocks can track their completion status + using the standalone Blockstore service. + """ + + +@requires_blockstore_app +class ContentLibraryXBlockCompletionTest( + ContentLibraryXBlockCompletionTestMixin, + CompletionWaffleTestMixin, + BlockstoreAppTestMixin, + LiveServerTestCase, +): + """ + Test that the Blockstore-based XBlocks can track their completion status + using the installed Blockstore app. + + We run this test with a live server, so that the blockstore asset files can be served. + """ diff --git a/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py b/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py index 0342e27ebd..a3ff52c789 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py @@ -5,7 +5,10 @@ Tests for static asset files in Blockstore-based Content Libraries import requests -from openedx.core.djangoapps.content_libraries.tests.base import ContentLibrariesRestApiTest +from openedx.core.djangoapps.content_libraries.tests.base import ( + ContentLibrariesRestApiBlockstoreServiceTest, + ContentLibrariesRestApiTest, +) # Binary data representing an SVG image file SVG_DATA = """ @@ -23,7 +26,7 @@ I'm Anant Agarwal, I'm the president of edX, """ -class ContentLibrariesStaticAssetsTest(ContentLibrariesRestApiTest): +class ContentLibrariesStaticAssetsTestMixin: """ Tests for static asset files in Blockstore-based Content Libraries @@ -166,3 +169,21 @@ class ContentLibrariesStaticAssetsTest(ContentLibrariesRestApiTest): self._commit_library_changes(library["id"]) check_sjson() check_download() + + +class ContentLibrariesStaticAssetsBlockstoreServiceTest( + ContentLibrariesStaticAssetsTestMixin, + ContentLibrariesRestApiBlockstoreServiceTest, +): + """ + Tests for static asset files in Blockstore-based Content Libraries, using the standalone Blockstore service. + """ + + +class ContentLibrariesStaticAssetsTest( + ContentLibrariesStaticAssetsTestMixin, + ContentLibrariesRestApiTest, +): + """ + Tests for static asset files in Blockstore-based Content Libraries, using the installed Blockstore app. + """ diff --git a/openedx/core/djangoapps/content_libraries/tests/test_views_lti.py b/openedx/core/djangoapps/content_libraries/tests/test_views_lti.py index 97633fa237..58dd7e9b21 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_views_lti.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_views_lti.py @@ -8,6 +8,7 @@ from django.test import TestCase, override_settings from openedx.core.djangoapps.content_libraries.constants import PROBLEM from .base import ( + ContentLibrariesRestApiBlockstoreServiceTest, ContentLibrariesRestApiTest, URL_LIB_LTI_JWKS, skip_unless_cms, @@ -49,9 +50,7 @@ class LtiToolJwksViewTest(TestCase): self.assertJSONEqual(response.content, '{"keys": []}') -@override_features(ENABLE_CONTENT_LIBRARIES=True, - ENABLE_CONTENT_LIBRARIES_LTI_TOOL=True) -class LibraryBlockLtiUrlViewTest(ContentLibrariesRestApiTest): +class LibraryBlockLtiUrlViewTestMixin: """ Test generating LTI URL for a block in a library. """ @@ -80,3 +79,25 @@ class LibraryBlockLtiUrlViewTest(ContentLibrariesRestApiTest): Test the LTI URL cannot be generated as the block not found. """ self._api("get", '/api/libraries/v2/blocks/lb:CL-TEST:libgg:problem:bad-block/lti/', None, expect_response=404) + + +@override_features(ENABLE_CONTENT_LIBRARIES=True, + ENABLE_CONTENT_LIBRARIES_LTI_TOOL=True) +class LibraryBlockLtiUrlViewBlockstoreServiceTest( + LibraryBlockLtiUrlViewTestMixin, + ContentLibrariesRestApiBlockstoreServiceTest, +): + """ + Test generating LTI URL for a block in a library, using the standalone Blockstore service. + """ + + +@override_features(ENABLE_CONTENT_LIBRARIES=True, + ENABLE_CONTENT_LIBRARIES_LTI_TOOL=True) +class LibraryBlockLtiUrlViewTest( + LibraryBlockLtiUrlViewTestMixin, + ContentLibrariesRestApiTest, +): + """ + Test generating LTI URL for a block in a library, using the installed Blockstore app. + """ diff --git a/openedx/core/djangolib/tests/test_blockstore_cache.py b/openedx/core/djangolib/tests/test_blockstore_cache.py index a0e3ee6618..84d071ab8f 100644 --- a/openedx/core/djangolib/tests/test_blockstore_cache.py +++ b/openedx/core/djangolib/tests/test_blockstore_cache.py @@ -5,11 +5,14 @@ from unittest.mock import patch from django.test import TestCase from openedx.core.djangolib.blockstore_cache import BundleCache -from openedx.core.djangoapps.content_libraries.tests.base import requires_blockstore +from openedx.core.djangoapps.content_libraries.tests.base import ( + BlockstoreAppTestMixin, + requires_blockstore, + requires_blockstore_app, +) from openedx.core.lib import blockstore_api as api -@requires_blockstore class TestWithBundleMixin: """ Mixin that gives every test method access to a bundle + draft @@ -24,7 +27,7 @@ class TestWithBundleMixin: @patch('openedx.core.djangolib.blockstore_cache.MAX_BLOCKSTORE_CACHE_DELAY', 0) -class BundleCacheTest(TestWithBundleMixin, TestCase): +class BundleCacheTestMixin(TestWithBundleMixin): """ Tests for BundleCache """ @@ -109,3 +112,17 @@ class BundleCacheClearTest(TestWithBundleMixin, TestCase): # Now "clear" the cache, forcing the check of the new version: cache.clear() assert cache.get(key1) is None + + +@requires_blockstore +class BundleCacheBlockstoreServiceTest(BundleCacheTestMixin, TestCase): + """ + Tests BundleCache using the standalone Blockstore service. + """ + + +@requires_blockstore_app +class BundleCacheTest(BundleCacheTestMixin, BlockstoreAppTestMixin, TestCase): + """ + Tests BundleCache using the installed Blockstore app. + """ diff --git a/openedx/core/lib/blockstore_api/config/__init__.py b/openedx/core/lib/blockstore_api/config/__init__.py index 2f9de4c0fc..4cd2999d2e 100644 --- a/openedx/core/lib/blockstore_api/config/__init__.py +++ b/openedx/core/lib/blockstore_api/config/__init__.py @@ -2,7 +2,7 @@ Helper method to indicate when the blockstore app API is enabled. """ from django.conf import settings -from .waffle import BLOCKSTORE_USE_BLOCKSTORE_APP_API +from .waffle import BLOCKSTORE_USE_BLOCKSTORE_APP_API # pylint: disable=invalid-django-waffle-import def use_blockstore_app(): diff --git a/openedx/core/lib/blockstore_api/tests/test_blockstore_api.py b/openedx/core/lib/blockstore_api/tests/test_blockstore_api.py index e2c0997576..a1239d5a00 100644 --- a/openedx/core/lib/blockstore_api/tests/test_blockstore_api.py +++ b/openedx/core/lib/blockstore_api/tests/test_blockstore_api.py @@ -8,16 +8,19 @@ import pytest from django.test import TestCase from openedx.core.lib import blockstore_api as api -from openedx.core.djangoapps.content_libraries.tests.base import requires_blockstore +from openedx.core.djangoapps.content_libraries.tests.base import ( + BlockstoreAppTestMixin, + requires_blockstore, + requires_blockstore_app, +) # A fake UUID that won't represent any real bundle/draft/collection: BAD_UUID = UUID('12345678-0000-0000-0000-000000000000') -@requires_blockstore -class BlockstoreApiClientTest(TestCase): +class BlockstoreApiClientTestMixin: """ - Test for the Blockstore API Client. + Tests for the Blockstore API Client. The goal of these tests is not to test that Blockstore works correctly, but that the API client can interact with it and all the API client methods @@ -192,3 +195,17 @@ class BlockstoreApiClientTest(TestCase): # Finally, test deleting a link from course's draft: api.set_draft_link(course_draft.uuid, link2_name, None, None) assert not api.get_bundle_links(course_bundle.uuid, use_draft=course_draft.name) + + +@requires_blockstore +class BlockstoreServiceApiClientTest(BlockstoreApiClientTestMixin, TestCase): + """ + Test the Blockstore API Client, using the standalone Blockstore service. + """ + + +@requires_blockstore_app +class BlockstoreAppApiClientTest(BlockstoreApiClientTestMixin, BlockstoreAppTestMixin, TestCase): + """ + Test the Blockstore API Client, using the installed Blockstore app. + """