Skip to content

Commit 3545527

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

File tree

5 files changed

+141
-47
lines changed

5 files changed

+141
-47
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: 125 additions & 46 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,49 @@ 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+
54+
def modify_component(
55+
self,
56+
component: authoring_models.Component,
57+
*,
58+
title="Modified Component",
59+
timestamp=None,
60+
) -> authoring_models.ComponentVersion:
61+
"""
62+
Helper method to modify a component for the purposes of testing units/drafts/pinning/publishing/etc.
63+
"""
64+
return authoring_api.create_next_component_version(
65+
component.pk,
66+
content_to_replace={},
67+
title=title,
68+
created=timestamp or self.now,
69+
created_by=None,
70+
)
71+
2872
def test_create_unit_with_content_instead_of_components(self):
2973
"""Test creating a unit with content instead of components.
3074
@@ -56,7 +100,7 @@ def test_create_empty_unit_and_version(self):
56100
assert unit.versioning.draft == unit_version
57101
assert unit.versioning.published is None
58102

59-
def test_create_next_unit_version_with_two_components(self):
103+
def test_create_next_unit_version_with_two_unpinned_components(self):
60104
"""Test creating a unit version with two unpinned components.
61105
62106
Expected results:
@@ -85,6 +129,33 @@ def test_create_next_unit_version_with_two_components(self):
85129
authoring_api.UnitListEntry(component_version=self.component_1.versioning.draft, pinned=False),
86130
authoring_api.UnitListEntry(component_version=self.component_2.versioning.draft, pinned=False),
87131
]
132+
assert authoring_api.get_components_in_published_unit(unit) is None
133+
134+
def test_create_next_unit_version_with_unpinned_and_pinned_components(self):
135+
"""
136+
Test creating a unit version with one unpinned and one pinned component.
137+
"""
138+
unit, unit_version = authoring_api.create_unit_and_version(
139+
learning_package_id=self.learning_package.id,
140+
key=f"unit:key",
141+
title="Unit",
142+
created=self.now,
143+
created_by=None,
144+
)
145+
unit_version_v2 = authoring_api.create_next_unit_version(
146+
unit=unit,
147+
title="Unit",
148+
components=[self.component_1, self.component_2_v1], # Note the "v1" pinning it to version 2
149+
created=self.now,
150+
created_by=None,
151+
)
152+
assert unit_version_v2.version_num == 2
153+
assert unit_version_v2 in unit.versioning.versions.all()
154+
assert authoring_api.get_components_in_draft_unit(unit) == [
155+
authoring_api.UnitListEntry(component_version=self.component_1_v1, pinned=False),
156+
authoring_api.UnitListEntry(component_version=self.component_2_v1, pinned=True), # Pinned to v1
157+
]
158+
assert authoring_api.get_components_in_published_unit(unit) is None
88159

89160
def test_add_component_after_publish(self):
90161
"""
@@ -123,49 +194,31 @@ def test_add_component_after_publish(self):
123194
assert unit.versioning.draft == unit_version_v2
124195
assert unit.versioning.published == unit_version
125196

126-
def test_modify_component_after_publish(self):
197+
def test_modify_unpinned_component_after_publish(self):
127198
"""
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.
199+
Modifying an unpinned component in a published unit will NOT create a
200+
new version nor show that the unit has unpublished changes (but it will
201+
"contain" unpublished changes). The modifications will appear in the
202+
published version of the unit only after the component is published.
132203
"""
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):
204+
# Create a unit with one unpinned draft component:
142205
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-
)
206+
unit = self.create_unit_with_components([self.component_1])
207+
assert unit.versioning.has_unpublished_changes == True
208+
150209
# Publish the unit and the component:
151210
authoring_api.publish_all_drafts(self.learning_package.id)
152-
unit.refresh_from_db() # Reloading the unit is necessary
211+
unit.refresh_from_db() # Reloading the unit is necessary if we accessed 'versioning' before publish
153212
self.component_1.refresh_from_db()
154213
assert unit.versioning.has_unpublished_changes == False # Shallow check
155214
assert authoring_api.contains_unpublished_changes(unit) == False # Deeper check
156215
assert self.component_1.versioning.has_unpublished_changes == False
157216

158217
# Now modify the component by changing its title (it remains a draft):
159-
component_1_v2 = authoring_api.create_next_component_version(
160-
self.component_1.pk,
161-
content_to_replace={},
162-
title="Modified Counting Problem with new title",
163-
created=self.now,
164-
created_by=None,
165-
)
218+
component_1_v2 = self.modify_component(self.component_1, title="Modified Counting Problem with new title")
166219

167220
# The component now has unpublished changes; the unit doesn't directly but does contain
168-
unit.refresh_from_db() # Reloading the unit is necessary
221+
unit.refresh_from_db() # Reloading the unit is necessary, or 'unit.versioning' will be outdated
169222
self.component_1.refresh_from_db()
170223
assert unit.versioning.has_unpublished_changes == False # Shallow check should be false - unit is unchanged
171224
assert authoring_api.contains_unpublished_changes(unit) == True # But unit DOES contain changes
@@ -189,19 +242,46 @@ def test_modify_component_after_publish(self):
189242
]
190243
assert authoring_api.contains_unpublished_changes(unit) == False # No longer contains unpublished changes
191244

245+
def test_modify_pinned_component(self):
246+
"""
247+
When a pinned 📌 component in unit is modified and/or published, it will
248+
have no effect on either the draft nor published version of the unit,
249+
which will continue to use the pinned version.
250+
"""
251+
# Create a unit with one component (pinned 📌 to v1):
252+
unit = self.create_unit_with_components([self.component_1_v1])
253+
254+
# Publish the unit and the component:
255+
authoring_api.publish_all_drafts(self.learning_package.id)
256+
expected_unit_contents = [
257+
authoring_api.UnitListEntry(component_version=self.component_1_v1, pinned=True), # pinned 📌 to v1
258+
]
259+
assert authoring_api.get_components_in_published_unit(unit) == expected_unit_contents
260+
261+
# Now modify the component by changing its title (it remains a draft):
262+
self.modify_component(self.component_1, title="Modified Counting Problem with new title")
263+
264+
# The component now has unpublished changes; the unit is entirely unaffected
265+
unit.refresh_from_db() # Reloading the unit is necessary, or 'unit.versioning' will be outdated
266+
self.component_1.refresh_from_db()
267+
assert unit.versioning.has_unpublished_changes == False # Shallow check
268+
assert authoring_api.contains_unpublished_changes(unit) == False # Deep check
269+
assert self.component_1.versioning.has_unpublished_changes == True
270+
271+
# Neither the draft nor the published version of the unit is affected
272+
assert authoring_api.get_components_in_draft_unit(unit) == expected_unit_contents
273+
assert authoring_api.get_components_in_published_unit(unit) == expected_unit_contents
274+
# Even if we publish the component, the unit stays pinned to the specified version:
275+
self.publish_component(self.component_1)
276+
assert authoring_api.get_components_in_draft_unit(unit) == expected_unit_contents
277+
assert authoring_api.get_components_in_published_unit(unit) == expected_unit_contents
278+
192279

193280
def test_query_count_of_contains_unpublished_changes(self):
194281
"""
195282
Checking for unpublished changes in a unit should require a fixed number
196283
of queries, not get more expensive as the unit gets larger.
197284
"""
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-
)
205285
# Add 100 components (unpinned)
206286
component_count = 100
207287
components = []
@@ -214,18 +294,17 @@ def test_query_count_of_contains_unpublished_changes(self):
214294
created=self.now,
215295
)
216296
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-
)
297+
unit = self.create_unit_with_components(components)
223298
authoring_api.publish_all_drafts(self.learning_package.id)
224299
unit.refresh_from_db()
225300
with self.assertNumQueries(3):
226301
assert authoring_api.contains_unpublished_changes(unit) == False
227302

228-
# Test that pinned components with changes don't show up as "contains unpublished changes"
303+
# Modify the most recently created component:
304+
self.modify_component(component, title="Modified Component")
305+
with self.assertNumQueries(2):
306+
assert authoring_api.contains_unpublished_changes(unit) == True
307+
229308
# Test that only components can be added to units
230309
# Test that components must be in the same learning package
231310
# Test that _version_pks=[] arguments must be related to publishable_entities_pks

0 commit comments

Comments
 (0)