Skip to content

Commit cac79e1

Browse files
test: add tests for pinned components
1 parent 5f08231 commit cac79e1

File tree

5 files changed

+122
-39
lines changed

5 files changed

+122
-39
lines changed

openedx_learning/api/authoring_models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@
1212
from ..apps.authoring.contents.models import *
1313
from ..apps.authoring.publishing.model_mixins import *
1414
from ..apps.authoring.publishing.models import *
15+
from ..apps.authoring.containers.models import *
16+
from ..apps.authoring.units.models import *

openedx_learning/apps/authoring/containers/models.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
PublishableEntityVersionMixin,
1010
)
1111

12+
__all__ = [
13+
"ContainerEntity",
14+
"ContainerEntityVersion",
15+
]
16+
1217

1318
class EntityList(models.Model):
1419
"""

openedx_learning/apps/authoring/units/api.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,11 @@ def get_components_in_published_unit(
265265
given container.
266266
"""
267267
assert isinstance(unit, (Unit, UnitVersion))
268+
published_entities = container_api.get_entities_in_published_container(unit)
269+
if published_entities == None:
270+
return None # There is no published version of this unit. Should this be an exception?
268271
entity_list = []
269-
for entry in container_api.get_entities_in_published_container(unit):
272+
for entry in published_entities:
270273
# Convert from generic PublishableEntityVersion to ComponentVersion:
271274
component_version = entry.entity_version.componentversion
272275
assert isinstance(component_version, ComponentVersion)

openedx_learning/apps/authoring/units/models.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
from ..containers.models_mixin import ContainerEntityMixin, ContainerEntityVersionMixin
44

5+
__all__ = [
6+
"Unit",
7+
"UnitVersion",
8+
]
9+
510

611
class Unit(ContainerEntityMixin):
712
"""

tests/openedx_learning/apps/authoring/units/test_api.py

Lines changed: 106 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44
from ..components.test_api import ComponentTestCase
55
from openedx_learning.api import authoring as authoring_api
6+
from openedx_learning.api import authoring_models
67

78

89
class UnitTestCase(ComponentTestCase):
@@ -16,7 +17,7 @@ def setUp(self) -> None:
1617
created=self.now,
1718
created_by=None,
1819
)
19-
self.component_2, self.component_2_v2 = authoring_api.create_component_and_version(
20+
self.component_2, self.component_2_v1 = authoring_api.create_component_and_version(
2021
self.learning_package.id,
2122
component_type=self.problem_type,
2223
local_key="Query Counting (2)",
@@ -25,6 +26,31 @@ def setUp(self) -> None:
2526
created_by=None,
2627
)
2728

29+
def create_unit_with_components(
30+
self,
31+
components: list[authoring_models.Component | authoring_models.ComponentVersion],
32+
*,
33+
title="Unit",
34+
key="unit:key",
35+
) -> authoring_models.Unit:
36+
""" Helper method to quickly create a unit with some components """
37+
unit, _unit_v1 = authoring_api.create_unit_and_version(
38+
learning_package_id=self.learning_package.id,
39+
key=key,
40+
title=title,
41+
created=self.now,
42+
created_by=None,
43+
)
44+
unit_v2 = authoring_api.create_next_unit_version(
45+
unit=unit,
46+
title=title,
47+
components=components,
48+
created=self.now,
49+
created_by=None,
50+
)
51+
unit.refresh_from_db()
52+
return unit
53+
2854
def test_create_unit_with_content_instead_of_components(self):
2955
"""Test creating a unit with content instead of components.
3056
@@ -56,7 +82,7 @@ def test_create_empty_unit_and_version(self):
5682
assert unit.versioning.draft == unit_version
5783
assert unit.versioning.published is None
5884

59-
def test_create_next_unit_version_with_two_components(self):
85+
def test_create_next_unit_version_with_two_unpinned_components(self):
6086
"""Test creating a unit version with two unpinned components.
6187
6288
Expected results:
@@ -85,6 +111,33 @@ def test_create_next_unit_version_with_two_components(self):
85111
authoring_api.UnitListEntry(component_version=self.component_1.versioning.draft, pinned=False),
86112
authoring_api.UnitListEntry(component_version=self.component_2.versioning.draft, pinned=False),
87113
]
114+
assert authoring_api.get_components_in_published_unit(unit) is None
115+
116+
def test_create_next_unit_version_with_unpinned_and_pinned_components(self):
117+
"""
118+
Test creating a unit version with one unpinned and one pinned component.
119+
"""
120+
unit, unit_version = authoring_api.create_unit_and_version(
121+
learning_package_id=self.learning_package.id,
122+
key=f"unit:key",
123+
title="Unit",
124+
created=self.now,
125+
created_by=None,
126+
)
127+
unit_version_v2 = authoring_api.create_next_unit_version(
128+
unit=unit,
129+
title="Unit",
130+
components=[self.component_1, self.component_2_v1], # Note the "v1" pinning it to version 2
131+
created=self.now,
132+
created_by=None,
133+
)
134+
assert unit_version_v2.version_num == 2
135+
assert unit_version_v2 in unit.versioning.versions.all()
136+
assert authoring_api.get_components_in_draft_unit(unit) == [
137+
authoring_api.UnitListEntry(component_version=self.component_1_v1, pinned=False),
138+
authoring_api.UnitListEntry(component_version=self.component_2_v1, pinned=True), # Pinned to v1
139+
]
140+
assert authoring_api.get_components_in_published_unit(unit) is None
88141

89142
def test_add_component_after_publish(self):
90143
"""
@@ -123,33 +176,21 @@ def test_add_component_after_publish(self):
123176
assert unit.versioning.draft == unit_version_v2
124177
assert unit.versioning.published == unit_version
125178

126-
def test_modify_component_after_publish(self):
179+
def test_modify_unpinned_component_after_publish(self):
127180
"""
128-
Modifying a component in a published unit will NOT create a new version
129-
nor show that the unit has unpublished changes (but it will "contain"
130-
unpublished changes). The modifications will appear in the published
131-
version of the unit only after the component is published.
181+
Modifying an unpinned component in a published unit will NOT create a
182+
new version nor show that the unit has unpublished changes (but it will
183+
"contain" unpublished changes). The modifications will appear in the
184+
published version of the unit only after the component is published.
132185
"""
133-
# Create a unit:
134-
unit, unit_version = authoring_api.create_unit_and_version(
135-
learning_package_id=self.learning_package.id,
136-
key=f"unit:key",
137-
title="Unit",
138-
created=self.now,
139-
created_by=None,
140-
)
141-
# Add a draft component (unpinned):
186+
# Create a unit with one unpinned draft component:
142187
assert self.component_1.versioning.has_unpublished_changes == True
143-
unit_version_v2 = authoring_api.create_next_unit_version(
144-
unit=unit,
145-
title=unit_version.title,
146-
components=[self.component_1],
147-
created=self.now,
148-
created_by=None,
149-
)
188+
unit = self.create_unit_with_components([self.component_1])
189+
assert unit.versioning.has_unpublished_changes == True
190+
150191
# Publish the unit and the component:
151192
authoring_api.publish_all_drafts(self.learning_package.id)
152-
unit.refresh_from_db() # Reloading the unit is necessary
193+
unit.refresh_from_db() # Reloading the unit is necessary if we accessed 'versioning' before publish
153194
self.component_1.refresh_from_db()
154195
assert unit.versioning.has_unpublished_changes == False # Shallow check
155196
assert authoring_api.contains_unpublished_changes(unit) == False # Deeper check
@@ -165,7 +206,7 @@ def test_modify_component_after_publish(self):
165206
)
166207

167208
# The component now has unpublished changes; the unit doesn't directly but does contain
168-
unit.refresh_from_db() # Reloading the unit is necessary
209+
unit.refresh_from_db() # Reloading the unit is necessary, or 'unit.versioning' will be outdated
169210
self.component_1.refresh_from_db()
170211
assert unit.versioning.has_unpublished_changes == False # Shallow check should be false - unit is unchanged
171212
assert authoring_api.contains_unpublished_changes(unit) == True # But unit DOES contain changes
@@ -189,19 +230,51 @@ def test_modify_component_after_publish(self):
189230
]
190231
assert authoring_api.contains_unpublished_changes(unit) == False # No longer contains unpublished changes
191232

233+
def test_modify_pinned_component_after_publish(self):
234+
"""
235+
Modifying a pinned 📌 component in a published unit will have no effect
236+
on either the draft nor published version.
237+
"""
238+
# Create a unit with one component (pinned 📌 to v1):
239+
unit = self.create_unit_with_components([self.component_1_v1])
240+
241+
# Publish the unit and the component:
242+
authoring_api.publish_all_drafts(self.learning_package.id)
243+
expected_unit_contents = [
244+
authoring_api.UnitListEntry(component_version=self.component_1_v1, pinned=True), # pinned 📌 to v1
245+
]
246+
assert authoring_api.get_components_in_published_unit(unit) == expected_unit_contents
247+
248+
# Now modify the component by changing its title (it remains a draft):
249+
component_1_v2 = authoring_api.create_next_component_version(
250+
self.component_1.pk,
251+
content_to_replace={},
252+
title="Modified Counting Problem with new title",
253+
created=self.now,
254+
created_by=None,
255+
)
256+
257+
# The component now has unpublished changes; the unit is entirely unaffected
258+
unit.refresh_from_db() # Reloading the unit is necessary, or 'unit.versioning' will be outdated
259+
self.component_1.refresh_from_db()
260+
assert unit.versioning.has_unpublished_changes == False # Shallow check
261+
assert authoring_api.contains_unpublished_changes(unit) == False # Deep check
262+
assert self.component_1.versioning.has_unpublished_changes == True
263+
264+
# Neither the draft nor the published version of the unit is affected
265+
assert authoring_api.get_components_in_draft_unit(unit) == expected_unit_contents
266+
assert authoring_api.get_components_in_published_unit(unit) == expected_unit_contents
267+
# Even if we publish the component, the unit stays pinned to the specified version:
268+
self.publish_component(self.component_1)
269+
assert authoring_api.get_components_in_draft_unit(unit) == expected_unit_contents
270+
assert authoring_api.get_components_in_published_unit(unit) == expected_unit_contents
271+
192272

193273
def test_query_count_of_contains_unpublished_changes(self):
194274
"""
195275
Checking for unpublished changes in a unit should require a fixed number
196276
of queries, not get more expensive as the unit gets larger.
197277
"""
198-
unit, unit_version = authoring_api.create_unit_and_version(
199-
learning_package_id=self.learning_package.id,
200-
key=f"unit:key",
201-
title="Unit",
202-
created=self.now,
203-
created_by=None,
204-
)
205278
# Add 100 components (unpinned)
206279
component_count = 100
207280
components = []
@@ -214,12 +287,7 @@ def test_query_count_of_contains_unpublished_changes(self):
214287
created=self.now,
215288
)
216289
components.append(component)
217-
authoring_api.create_next_unit_version(
218-
unit=unit,
219-
title=unit_version.title,
220-
components=components,
221-
created=self.now,
222-
)
290+
unit = self.create_unit_with_components(components)
223291
authoring_api.publish_all_drafts(self.learning_package.id)
224292
unit.refresh_from_db()
225293
with self.assertNumQueries(3):

0 commit comments

Comments
 (0)