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:
Jillian Vogel
2022-01-25 21:08:57 +10:30
parent 7d81c3e671
commit 8b77638bf0
13 changed files with 377 additions and 73 deletions

View File

@@ -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 ######################################

View File

@@ -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']

View File

@@ -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", [])

View File

@@ -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

View File

@@ -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.
"""

View File

@@ -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.
"""

View File

@@ -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.
"""

View File

@@ -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.
"""

View File

@@ -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.
"""

View File

@@ -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.
"""

View File

@@ -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.
"""

View File

@@ -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():

View File

@@ -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.
"""