Skip to content

Commit fffd5c2

Browse files
committed
test: add test to validate lp_dump output structure
1 parent 123ad6a commit fffd5c2

File tree

4 files changed

+130
-0
lines changed

4 files changed

+130
-0
lines changed

openedx_learning/api/authoring.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from ..apps.authoring.sections.api import *
1717
from ..apps.authoring.subsections.api import *
1818
from ..apps.authoring.units.api import *
19+
from ..apps.authoring.backup_restore.api import *
1920

2021
# This was renamed after the authoring API refactoring pushed this and other
2122
# app APIs into the openedx_learning.api.authoring module. Here I'm aliasing to

test_settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def root(*args):
4848
"openedx_learning.apps.authoring.sections.apps.SectionsConfig",
4949
"openedx_learning.apps.authoring.subsections.apps.SubsectionsConfig",
5050
"openedx_learning.apps.authoring.units.apps.UnitsConfig",
51+
"openedx_learning.apps.authoring.backup_restore.apps.BackupRestoreConfig",
5152
]
5253

5354
AUTHENTICATION_BACKENDS = [

tests/openedx_learning/apps/authoring/backup_restore/__init__.py

Whitespace-only changes.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"""
2+
Tests relating to dumping learning packages to disk
3+
"""
4+
import zipfile
5+
from datetime import datetime, timezone
6+
from io import StringIO
7+
from pathlib import Path
8+
9+
from django.contrib.auth import get_user_model
10+
from django.core.management import CommandError, call_command
11+
12+
from openedx_learning.api import authoring as api
13+
from openedx_learning.api.authoring_models import LearningPackage
14+
from openedx_learning.lib.test_utils import TestCase
15+
16+
User = get_user_model()
17+
18+
19+
class LpDumpCommandTestCase(TestCase):
20+
"""
21+
Test serving static assets (Content files, via Component lookup).
22+
"""
23+
24+
learning_package: LearningPackage
25+
26+
@classmethod
27+
def setUpTestData(cls):
28+
"""
29+
Initialize our content data
30+
"""
31+
cls.user = User.objects.create(
32+
username="user",
33+
34+
)
35+
36+
cls.learning_package = api.create_learning_package(
37+
key="ComponentTestCase-test-key",
38+
title="Components Test Case Learning Package",
39+
)
40+
cls.learning_package_2 = api.create_learning_package(
41+
key="ComponentTestCase-test-key-2",
42+
title="Components Test Case another Learning Package",
43+
)
44+
cls.now = datetime(2024, 8, 5, tzinfo=timezone.utc)
45+
46+
cls.html_type = api.get_or_create_component_type("xblock.v1", "html")
47+
cls.problem_type = api.get_or_create_component_type("xblock.v1", "problem")
48+
created_time = datetime(2025, 4, 1, tzinfo=timezone.utc)
49+
cls.draft_unit = api.create_unit(
50+
learning_package_id=cls.learning_package.id,
51+
key="unit-1",
52+
created=created_time,
53+
created_by=cls.user.id,
54+
)
55+
56+
# Make and publish one Component
57+
cls.published_component, _ = api.create_component_and_version(
58+
cls.learning_package.id,
59+
cls.problem_type,
60+
local_key="my_published_example",
61+
title="My published problem",
62+
created=cls.now,
63+
created_by=cls.user.id,
64+
)
65+
api.publish_all_drafts(
66+
cls.learning_package.id,
67+
message="Publish from CollectionTestCase.setUpTestData",
68+
published_at=cls.now,
69+
)
70+
71+
# Create a Draft component, one in each learning package
72+
cls.draft_component, _ = api.create_component_and_version(
73+
cls.learning_package.id,
74+
cls.html_type,
75+
local_key="my_draft_example",
76+
title="My draft html",
77+
created=cls.now,
78+
created_by=cls.user.id,
79+
)
80+
81+
def check_zip_file_structure(self, zip_path: Path):
82+
"""
83+
Check that the zip file has the expected structure.
84+
"""
85+
86+
with zipfile.ZipFile(zip_path, 'r') as zip_file:
87+
# Check that the zip file contains the expected files
88+
expected_files = [
89+
"package.toml",
90+
"entities/",
91+
"entities/xblock.v1:problem:my_published_example.toml",
92+
"entities/xblock.v1:html:my_draft_example.toml",
93+
]
94+
for expected_file in expected_files:
95+
self.assertIn(expected_file, zip_file.namelist())
96+
97+
def test_lp_dump_command(self):
98+
lp_key = self.learning_package.key
99+
file_name = f"{lp_key}.zip"
100+
try:
101+
out = StringIO()
102+
103+
# Call the management command to dump the learning package
104+
call_command("lp_dump", lp_key, file_name, stdout=out)
105+
106+
# Check that the zip file was created
107+
self.assertTrue(Path(file_name).exists())
108+
# Check the structure of the zip file
109+
self.check_zip_file_structure(Path(file_name))
110+
111+
# Check the output message
112+
message = f'{lp_key} written to {file_name}'
113+
self.assertIn(message, out.getvalue())
114+
except Exception as e: # pylint: disable=broad-exception-caught
115+
self.fail(f"lp_dump command failed with error: {e}")
116+
finally:
117+
# Clean up the created zip file
118+
if Path(file_name).exists():
119+
Path(file_name).unlink(missing_ok=True)
120+
121+
def test_dump_nonexistent_learning_package(self):
122+
out = StringIO()
123+
lp_key = "nonexistent_lp"
124+
file_name = f"{lp_key}.zip"
125+
with self.assertRaises(CommandError):
126+
# Attempt to dump a learning package that does not exist
127+
call_command("lp_dump", lp_key, file_name, stdout=out)
128+
self.assertIn("Learning package 'nonexistent_lp' does not exist", out.getvalue())

0 commit comments

Comments
 (0)