test: refactors blockstore integration tests to run as unit tests.
Tests which @requires_blockstore (i.e. the Blockstore service) have been made to run as a unit test using the installed Blockstore app, and will be run by the platform CI. The Blockstore service tests can still be run manually by setting EDXAPP_RUN_BLOCKSTORE_TESTS=1 Related fixes: * adds blockstore bundle storage settings * let the studio devstack and test servers serve static files from the /media URL This allows the blockstore/content libraries API to serve blockstore assets in dev. * Wrap ContentLibrary creation in an atomic transaction, so that if it fails, the related bundle can be deleted directly from the database during the exception handler. (Previously, we called a REST API which deleted it as part of a separate service.)
This commit is contained in:
@@ -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 ######################################
|
||||
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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", [])
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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 = """<svg xmlns="http://www.w3.org/2000/svg" height="30" width="100">
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user