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