Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
45ad00ae
Commit
45ad00ae
authored
Apr 14, 2015
by
John Eskew
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add tests for more draft/published import/export.
Include tests that mimic behavior in PLAT-452.
parent
c21cf665
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
348 additions
and
10 deletions
+348
-10
common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py
+348
-10
No files found.
common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py
View file @
45ad00ae
...
@@ -10,15 +10,19 @@ import itertools
...
@@ -10,15 +10,19 @@ import itertools
import
mimetypes
import
mimetypes
from
unittest
import
skip
from
unittest
import
skip
from
uuid
import
uuid4
from
uuid
import
uuid4
from
contextlib
import
contextmanager
# Mixed modulestore depends on django, so we'll manually configure some django settings
# Mixed modulestore depends on django, so we'll manually configure some django settings
# before importing the module
# before importing the module
# TODO remove this import and the configuration -- xmodule should not depend on django!
# TODO remove this import and the configuration -- xmodule should not depend on django!
from
django.conf
import
settings
from
django.conf
import
settings
# This import breaks this test file when run separately. Needs to be fixed! (PLAT-449)
from
mock_django
import
mock_signal_receiver
from
mock_django
import
mock_signal_receiver
from
nose.plugins.attrib
import
attr
from
nose.plugins.attrib
import
attr
import
pymongo
import
pymongo
from
pytz
import
UTC
from
pytz
import
UTC
from
shutil
import
rmtree
from
tempfile
import
mkdtemp
from
xmodule.x_module
import
XModuleMixin
from
xmodule.x_module
import
XModuleMixin
from
xmodule.modulestore.edit_info
import
EditInfoMixin
from
xmodule.modulestore.edit_info
import
EditInfoMixin
...
@@ -27,6 +31,7 @@ from xmodule.modulestore.tests.test_cross_modulestore_import_export import Mongo
...
@@ -27,6 +31,7 @@ from xmodule.modulestore.tests.test_cross_modulestore_import_export import Mongo
from
xmodule.contentstore.content
import
StaticContent
from
xmodule.contentstore.content
import
StaticContent
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
xmodule.modulestore.xml_importer
import
import_course_from_xml
from
xmodule.modulestore.xml_importer
import
import_course_from_xml
from
xmodule.modulestore.xml_exporter
import
export_course_to_xml
from
xmodule.modulestore.django
import
SignalHandler
from
xmodule.modulestore.django
import
SignalHandler
if
not
settings
.
configured
:
if
not
settings
.
configured
:
...
@@ -49,9 +54,7 @@ from xmodule.tests import DATA_DIR, CourseComparisonTest
...
@@ -49,9 +54,7 @@ from xmodule.tests import DATA_DIR, CourseComparisonTest
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
@ddt.ddt
class
CommonMixedModuleStoreSetup
(
CourseComparisonTest
):
@attr
(
'mongo'
)
class
TestMixedModuleStore
(
CourseComparisonTest
):
"""
"""
Quasi-superclass which tests Location based apps against both split and mongo dbs (Locator and
Quasi-superclass which tests Location based apps against both split and mongo dbs (Locator and
Location-based dbs)
Location-based dbs)
...
@@ -126,7 +129,7 @@ class TestMixedModuleStore(CourseComparisonTest):
...
@@ -126,7 +129,7 @@ class TestMixedModuleStore(CourseComparisonTest):
"""
"""
Set up the database for testing
Set up the database for testing
"""
"""
super
(
TestMixedModuleStore
,
self
)
.
setUp
()
super
(
CommonMixedModuleStoreSetup
,
self
)
.
setUp
()
self
.
exclude_field
(
None
,
'wiki_slug'
)
self
.
exclude_field
(
None
,
'wiki_slug'
)
self
.
exclude_field
(
None
,
'xml_attributes'
)
self
.
exclude_field
(
None
,
'xml_attributes'
)
...
@@ -241,6 +244,12 @@ class TestMixedModuleStore(CourseComparisonTest):
...
@@ -241,6 +244,12 @@ class TestMixedModuleStore(CourseComparisonTest):
"""
"""
return
self
.
course_locations
[
string
]
.
course_key
return
self
.
course_locations
[
string
]
.
course_key
def
_has_changes
(
self
,
location
):
"""
Helper function that loads the item before calling has_changes
"""
return
self
.
store
.
has_changes
(
self
.
store
.
get_item
(
location
))
# pylint: disable=dangerous-default-value
# pylint: disable=dangerous-default-value
def
_initialize_mixed
(
self
,
mappings
=
MAPPINGS
,
contentstore
=
None
):
def
_initialize_mixed
(
self
,
mappings
=
MAPPINGS
,
contentstore
=
None
):
"""
"""
...
@@ -285,6 +294,13 @@ class TestMixedModuleStore(CourseComparisonTest):
...
@@ -285,6 +294,13 @@ class TestMixedModuleStore(CourseComparisonTest):
)
)
self
.
_create_course
(
self
.
course_locations
[
self
.
MONGO_COURSEID
]
.
course_key
)
self
.
_create_course
(
self
.
course_locations
[
self
.
MONGO_COURSEID
]
.
course_key
)
@ddt.ddt
@attr
(
'mongo'
)
class
TestMixedModuleStore
(
CommonMixedModuleStoreSetup
):
"""
Tests of the MixedModulestore interface methods.
"""
@ddt.data
(
'draft'
,
'split'
)
@ddt.data
(
'draft'
,
'split'
)
def
test_get_modulestore_type
(
self
,
default_ms
):
def
test_get_modulestore_type
(
self
,
default_ms
):
"""
"""
...
@@ -506,12 +522,6 @@ class TestMixedModuleStore(CourseComparisonTest):
...
@@ -506,12 +522,6 @@ class TestMixedModuleStore(CourseComparisonTest):
component
=
self
.
store
.
publish
(
component
.
location
,
self
.
user_id
)
component
=
self
.
store
.
publish
(
component
.
location
,
self
.
user_id
)
self
.
assertFalse
(
self
.
store
.
has_changes
(
component
))
self
.
assertFalse
(
self
.
store
.
has_changes
(
component
))
def
_has_changes
(
self
,
location
):
"""
Helper function that loads the item before calling has_changes
"""
return
self
.
store
.
has_changes
(
self
.
store
.
get_item
(
location
))
def
setup_has_changes
(
self
,
default_ms
):
def
setup_has_changes
(
self
,
default_ms
):
"""
"""
Common set up for has_changes tests below.
Common set up for has_changes tests below.
...
@@ -2244,3 +2254,331 @@ class TestMixedModuleStore(CourseComparisonTest):
...
@@ -2244,3 +2254,331 @@ class TestMixedModuleStore(CourseComparisonTest):
self
.
store
.
update_item
(
unit
,
self
.
user_id
)
self
.
store
.
update_item
(
unit
,
self
.
user_id
)
self
.
assertEqual
(
receiver
.
call_count
,
0
)
self
.
assertEqual
(
receiver
.
call_count
,
0
)
self
.
assertEqual
(
receiver
.
call_count
,
0
)
self
.
assertEqual
(
receiver
.
call_count
,
0
)
@ddt.ddt
@attr
(
'mongo'
)
class
TestPublishOverExportImport
(
CommonMixedModuleStoreSetup
):
"""
Tests which publish (or don't publish) items - and then export/import the course,
checking the state of the imported items.
"""
def
setUp
(
self
):
"""
Set up the database for testing
"""
super
(
TestPublishOverExportImport
,
self
)
.
setUp
()
self
.
user_id
=
ModuleStoreEnum
.
UserID
.
test
self
.
export_dir
=
mkdtemp
()
self
.
addCleanup
(
rmtree
,
self
.
export_dir
,
ignore_errors
=
True
)
def
_export_import_course_round_trip
(
self
,
modulestore
,
contentstore
,
source_course_key
,
export_dir
):
"""
Export the course from a modulestore and then re-import the course.
"""
top_level_export_dir
=
'exported_source_course'
export_course_to_xml
(
modulestore
,
contentstore
,
source_course_key
,
export_dir
,
top_level_export_dir
,
)
import_course_from_xml
(
modulestore
,
'test_user'
,
export_dir
,
source_dirs
=
[
top_level_export_dir
],
static_content_store
=
contentstore
,
target_id
=
source_course_key
,
create_if_not_present
=
True
,
raise_on_failure
=
True
,
)
@contextmanager
def
_build_store
(
self
,
default_ms
):
"""
Perform the modulestore-building and course creation steps for a mixed modulestore test.
"""
with
MongoContentstoreBuilder
()
.
build
()
as
contentstore
:
# initialize the mixed modulestore
self
.
_initialize_mixed
(
contentstore
=
contentstore
,
mappings
=
{})
with
self
.
store
.
default_store
(
default_ms
):
source_course_key
=
self
.
store
.
make_course_key
(
"org.source"
,
"course.source"
,
"run.source"
)
self
.
_create_course
(
source_course_key
)
yield
contentstore
,
source_course_key
@ddt.data
(
ModuleStoreEnum
.
Type
.
mongo
,
ModuleStoreEnum
.
Type
.
split
)
def
test_draft_has_changes_before_export_and_after_import
(
self
,
default_ms
):
"""
Tests that an unpublished unit remains with no changes across export and re-import.
"""
with
self
.
_build_store
(
default_ms
)
as
(
contentstore
,
source_course_key
):
# Create a dummy component to test against and don't publish it.
draft_xblock
=
self
.
store
.
create_item
(
self
.
user_id
,
self
.
course
.
id
,
'vertical'
,
block_id
=
'test_vertical'
)
# Not yet published, so changes are present
self
.
assertTrue
(
self
.
_has_changes
(
draft_xblock
.
location
))
self
.
_export_import_course_round_trip
(
self
.
store
,
contentstore
,
source_course_key
,
self
.
export_dir
)
# Verify that the imported block still is a draft, i.e. has changes.
self
.
assertTrue
(
self
.
_has_changes
(
draft_xblock
.
location
))
@ddt.data
(
ModuleStoreEnum
.
Type
.
mongo
,
ModuleStoreEnum
.
Type
.
split
)
def
test_published_has_changes_before_export_and_after_import
(
self
,
default_ms
):
"""
Tests that an published unit remains published across export and re-import.
"""
with
self
.
_build_store
(
default_ms
)
as
(
contentstore
,
source_course_key
):
# Create a dummy component to test against and publish it.
published_xblock
=
self
.
store
.
create_item
(
self
.
user_id
,
self
.
course
.
id
,
'vertical'
,
block_id
=
'test_vertical'
)
self
.
store
.
publish
(
published_xblock
.
location
,
self
.
user_id
)
# Retrieve the published block and make sure it's published.
self
.
assertFalse
(
self
.
_has_changes
(
published_xblock
.
location
))
self
.
_export_import_course_round_trip
(
self
.
store
,
contentstore
,
source_course_key
,
self
.
export_dir
)
# Get the published xblock from the imported course.
# Verify that it still is published, i.e. has no changes.
self
.
assertFalse
(
self
.
_has_changes
(
published_xblock
.
location
))
@ddt.data
(
ModuleStoreEnum
.
Type
.
mongo
,
ModuleStoreEnum
.
Type
.
split
)
def
test_changed_published_has_changes_before_export_and_after_import
(
self
,
default_ms
):
"""
Tests that an published unit with an unpublished draft remains published across export and re-import.
"""
with
self
.
_build_store
(
default_ms
)
as
(
contentstore
,
source_course_key
):
# Create a dummy component to test against and publish it.
published_xblock
=
self
.
store
.
create_item
(
self
.
user_id
,
self
.
course
.
id
,
'vertical'
,
block_id
=
'test_vertical'
)
self
.
store
.
publish
(
published_xblock
.
location
,
self
.
user_id
)
# Retrieve the published block and make sure it's published.
self
.
assertFalse
(
self
.
_has_changes
(
published_xblock
.
location
))
updated_display_name
=
'Changed Display Name'
component
=
self
.
store
.
get_item
(
published_xblock
.
location
)
component
.
display_name
=
updated_display_name
component
=
self
.
store
.
update_item
(
component
,
self
.
user_id
)
self
.
assertTrue
(
self
.
store
.
has_changes
(
component
))
self
.
_export_import_course_round_trip
(
self
.
store
,
contentstore
,
source_course_key
,
self
.
export_dir
)
# Get the published xblock from the imported course.
# Verify that the published block still has a draft block, i.e. has changes.
self
.
assertTrue
(
self
.
_has_changes
(
published_xblock
.
location
))
# Verify that the changes in the draft vertical still exist.
with
self
.
store
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
draft_preferred
,
source_course_key
):
component
=
self
.
store
.
get_item
(
published_xblock
.
location
)
self
.
assertEqual
(
component
.
display_name
,
updated_display_name
)
@ddt.data
(
ModuleStoreEnum
.
Type
.
mongo
,
ModuleStoreEnum
.
Type
.
split
)
def
test_seq_with_unpublished_vertical_has_changes_before_export_and_after_import
(
self
,
default_ms
):
"""
Tests that an published unit with an unpublished draft remains published across export and re-import.
"""
with
self
.
_build_store
(
default_ms
)
as
(
contentstore
,
source_course_key
):
# create chapter
chapter
=
self
.
store
.
create_child
(
self
.
user_id
,
self
.
course
.
location
,
'chapter'
,
block_id
=
'section_one'
)
self
.
store
.
publish
(
chapter
.
location
,
self
.
user_id
)
# create sequential
sequential
=
self
.
store
.
create_child
(
self
.
user_id
,
chapter
.
location
,
'sequential'
,
block_id
=
'subsection_one'
)
self
.
store
.
publish
(
sequential
.
location
,
self
.
user_id
)
# create vertical - don't publish it!
vertical
=
self
.
store
.
create_child
(
self
.
user_id
,
sequential
.
location
,
'vertical'
,
block_id
=
'moon_unit'
)
# Retrieve the published block and make sure it's published.
# Chapter is published - but the changes in vertical below means it "has_changes".
self
.
assertTrue
(
self
.
_has_changes
(
chapter
.
location
))
# Sequential is published - but the changes in vertical below means it "has_changes".
self
.
assertTrue
(
self
.
_has_changes
(
sequential
.
location
))
# Vertical is unpublished - so it "has_changes".
self
.
assertTrue
(
self
.
_has_changes
(
vertical
.
location
))
self
.
_export_import_course_round_trip
(
self
.
store
,
contentstore
,
source_course_key
,
self
.
export_dir
)
# Get the published xblock from the imported course.
# Verify that the published block still has a draft block, i.e. has changes.
self
.
assertTrue
(
self
.
_has_changes
(
chapter
.
location
))
self
.
assertTrue
(
self
.
_has_changes
(
sequential
.
location
))
self
.
assertTrue
(
self
.
_has_changes
(
vertical
.
location
))
@ddt.data
(
ModuleStoreEnum
.
Type
.
mongo
,
ModuleStoreEnum
.
Type
.
split
)
def
test_vertical_with_draft_and_published_unit_has_changes_before_export_and_after_import
(
self
,
default_ms
):
"""
Tests that an published unit with an unpublished draft remains published across export and re-import.
"""
with
self
.
_build_store
(
default_ms
)
as
(
contentstore
,
source_course_key
):
# create chapter
chapter
=
self
.
store
.
create_child
(
self
.
user_id
,
self
.
course
.
location
,
'chapter'
,
block_id
=
'section_one'
)
self
.
store
.
publish
(
chapter
.
location
,
self
.
user_id
)
# create sequential
sequential
=
self
.
store
.
create_child
(
self
.
user_id
,
chapter
.
location
,
'sequential'
,
block_id
=
'subsection_one'
)
self
.
store
.
publish
(
sequential
.
location
,
self
.
user_id
)
# create vertical
vertical
=
self
.
store
.
create_child
(
self
.
user_id
,
sequential
.
location
,
'vertical'
,
block_id
=
'moon_unit'
)
# Vertical has changes until it is actually published.
self
.
assertTrue
(
self
.
_has_changes
(
vertical
.
location
))
self
.
store
.
publish
(
vertical
.
location
,
self
.
user_id
)
self
.
assertFalse
(
self
.
_has_changes
(
vertical
.
location
))
# create unit
unit
=
self
.
store
.
create_child
(
self
.
user_id
,
vertical
.
location
,
'html'
,
block_id
=
'html_unit'
)
# Vertical has a new child -and- unit is unpublished. So both have changes.
self
.
assertTrue
(
self
.
_has_changes
(
vertical
.
location
))
self
.
assertTrue
(
self
.
_has_changes
(
unit
.
location
))
# Publishing the vertical also publishes its unit child.
self
.
store
.
publish
(
vertical
.
location
,
self
.
user_id
)
self
.
assertFalse
(
self
.
_has_changes
(
vertical
.
location
))
self
.
assertFalse
(
self
.
_has_changes
(
unit
.
location
))
# Publishing the unit separately has no effect on whether it has changes - it's already published.
self
.
store
.
publish
(
unit
.
location
,
self
.
user_id
)
self
.
assertFalse
(
self
.
_has_changes
(
vertical
.
location
))
self
.
assertFalse
(
self
.
_has_changes
(
unit
.
location
))
# Retrieve the published block and make sure it's published.
self
.
store
.
publish
(
chapter
.
location
,
self
.
user_id
)
self
.
assertFalse
(
self
.
_has_changes
(
chapter
.
location
))
self
.
assertFalse
(
self
.
_has_changes
(
sequential
.
location
))
self
.
assertFalse
(
self
.
_has_changes
(
vertical
.
location
))
self
.
assertFalse
(
self
.
_has_changes
(
unit
.
location
))
# Now make changes to the unit - but don't publish them.
component
=
self
.
store
.
get_item
(
unit
.
location
)
updated_display_name
=
'Changed Display Name'
component
.
display_name
=
updated_display_name
component
=
self
.
store
.
update_item
(
component
,
self
.
user_id
)
self
.
assertTrue
(
self
.
_has_changes
(
component
.
location
))
# Export the course - then import the course export.
self
.
_export_import_course_round_trip
(
self
.
store
,
contentstore
,
source_course_key
,
self
.
export_dir
)
# Get the published xblock from the imported course.
# Verify that the published block still has a draft block, i.e. has changes.
self
.
assertTrue
(
self
.
_has_changes
(
chapter
.
location
))
self
.
assertTrue
(
self
.
_has_changes
(
sequential
.
location
))
self
.
assertTrue
(
self
.
_has_changes
(
vertical
.
location
))
self
.
assertTrue
(
self
.
_has_changes
(
unit
.
location
))
# Verify that the changes in the draft unit still exist.
with
self
.
store
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
draft_preferred
,
source_course_key
):
component
=
self
.
store
.
get_item
(
unit
.
location
)
self
.
assertEqual
(
component
.
display_name
,
updated_display_name
)
# Verify that the draft changes don't exist in the published unit - it still uses the default name.
with
self
.
store
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
published_only
,
source_course_key
):
component
=
self
.
store
.
get_item
(
unit
.
location
)
self
.
assertEqual
(
component
.
display_name
,
'Text'
)
@ddt.data
(
ModuleStoreEnum
.
Type
.
mongo
,
ModuleStoreEnum
.
Type
.
split
)
def
test_vertical_with_published_unit_remains_published_before_export_and_after_import
(
self
,
default_ms
):
"""
Tests that an published unit remains published across export and re-import.
"""
with
self
.
_build_store
(
default_ms
)
as
(
contentstore
,
source_course_key
):
# create chapter
chapter
=
self
.
store
.
create_child
(
self
.
user_id
,
self
.
course
.
location
,
'chapter'
,
block_id
=
'section_one'
)
self
.
store
.
publish
(
chapter
.
location
,
self
.
user_id
)
# create sequential
sequential
=
self
.
store
.
create_child
(
self
.
user_id
,
chapter
.
location
,
'sequential'
,
block_id
=
'subsection_one'
)
self
.
store
.
publish
(
sequential
.
location
,
self
.
user_id
)
# create vertical
vertical
=
self
.
store
.
create_child
(
self
.
user_id
,
sequential
.
location
,
'vertical'
,
block_id
=
'moon_unit'
)
# Vertical has changes until it is actually published.
self
.
assertTrue
(
self
.
_has_changes
(
vertical
.
location
))
self
.
store
.
publish
(
vertical
.
location
,
self
.
user_id
)
self
.
assertFalse
(
self
.
_has_changes
(
vertical
.
location
))
# create unit
unit
=
self
.
store
.
create_child
(
self
.
user_id
,
vertical
.
location
,
'html'
,
block_id
=
'html_unit'
)
# Now make changes to the unit.
updated_display_name
=
'Changed Display Name'
unit
.
display_name
=
updated_display_name
unit
=
self
.
store
.
update_item
(
unit
,
self
.
user_id
)
self
.
assertTrue
(
self
.
_has_changes
(
unit
.
location
))
# Publishing the vertical also publishes its unit child.
self
.
store
.
publish
(
vertical
.
location
,
self
.
user_id
)
self
.
assertFalse
(
self
.
_has_changes
(
vertical
.
location
))
self
.
assertFalse
(
self
.
_has_changes
(
unit
.
location
))
# Export the course - then import the course export.
self
.
_export_import_course_round_trip
(
self
.
store
,
contentstore
,
source_course_key
,
self
.
export_dir
)
# Get the published xblock from the imported course.
# Verify that the published block still has a draft block, i.e. has changes.
self
.
assertFalse
(
self
.
_has_changes
(
chapter
.
location
))
self
.
assertFalse
(
self
.
_has_changes
(
sequential
.
location
))
self
.
assertFalse
(
self
.
_has_changes
(
vertical
.
location
))
self
.
assertFalse
(
self
.
_has_changes
(
unit
.
location
))
# Verify that the published changes exist in the published unit.
with
self
.
store
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
published_only
,
source_course_key
):
component
=
self
.
store
.
get_item
(
unit
.
location
)
self
.
assertEqual
(
component
.
display_name
,
updated_display_name
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment