33"""
44from ..components .test_api import ComponentTestCase
55from openedx_learning .api import authoring as authoring_api
6+ from openedx_learning .api import authoring_models
67
78
89class 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