3
3
"""
4
4
from ..components .test_api import ComponentTestCase
5
5
from openedx_learning .api import authoring as authoring_api
6
+ from openedx_learning .api import authoring_models
6
7
7
8
8
9
class UnitTestCase (ComponentTestCase ):
@@ -16,7 +17,7 @@ def setUp(self) -> None:
16
17
created = self .now ,
17
18
created_by = None ,
18
19
)
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 (
20
21
self .learning_package .id ,
21
22
component_type = self .problem_type ,
22
23
local_key = "Query Counting (2)" ,
@@ -25,6 +26,49 @@ def setUp(self) -> None:
25
26
created_by = None ,
26
27
)
27
28
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
+
28
72
def test_create_unit_with_content_instead_of_components (self ):
29
73
"""Test creating a unit with content instead of components.
30
74
@@ -56,7 +100,7 @@ def test_create_empty_unit_and_version(self):
56
100
assert unit .versioning .draft == unit_version
57
101
assert unit .versioning .published is None
58
102
59
- def test_create_next_unit_version_with_two_components (self ):
103
+ def test_create_next_unit_version_with_two_unpinned_components (self ):
60
104
"""Test creating a unit version with two unpinned components.
61
105
62
106
Expected results:
@@ -85,6 +129,33 @@ def test_create_next_unit_version_with_two_components(self):
85
129
authoring_api .UnitListEntry (component_version = self .component_1 .versioning .draft , pinned = False ),
86
130
authoring_api .UnitListEntry (component_version = self .component_2 .versioning .draft , pinned = False ),
87
131
]
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
88
159
89
160
def test_add_component_after_publish (self ):
90
161
"""
@@ -123,49 +194,31 @@ def test_add_component_after_publish(self):
123
194
assert unit .versioning .draft == unit_version_v2
124
195
assert unit .versioning .published == unit_version
125
196
126
- def test_modify_component_after_publish (self ):
197
+ def test_modify_unpinned_component_after_publish (self ):
127
198
"""
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.
132
203
"""
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:
142
205
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
+
150
209
# Publish the unit and the component:
151
210
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
153
212
self .component_1 .refresh_from_db ()
154
213
assert unit .versioning .has_unpublished_changes == False # Shallow check
155
214
assert authoring_api .contains_unpublished_changes (unit ) == False # Deeper check
156
215
assert self .component_1 .versioning .has_unpublished_changes == False
157
216
158
217
# 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" )
166
219
167
220
# 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
169
222
self .component_1 .refresh_from_db ()
170
223
assert unit .versioning .has_unpublished_changes == False # Shallow check should be false - unit is unchanged
171
224
assert authoring_api .contains_unpublished_changes (unit ) == True # But unit DOES contain changes
@@ -189,19 +242,46 @@ def test_modify_component_after_publish(self):
189
242
]
190
243
assert authoring_api .contains_unpublished_changes (unit ) == False # No longer contains unpublished changes
191
244
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
+
192
279
193
280
def test_query_count_of_contains_unpublished_changes (self ):
194
281
"""
195
282
Checking for unpublished changes in a unit should require a fixed number
196
283
of queries, not get more expensive as the unit gets larger.
197
284
"""
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
- )
205
285
# Add 100 components (unpinned)
206
286
component_count = 100
207
287
components = []
@@ -214,18 +294,17 @@ def test_query_count_of_contains_unpublished_changes(self):
214
294
created = self .now ,
215
295
)
216
296
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 )
223
298
authoring_api .publish_all_drafts (self .learning_package .id )
224
299
unit .refresh_from_db ()
225
300
with self .assertNumQueries (3 ):
226
301
assert authoring_api .contains_unpublished_changes (unit ) == False
227
302
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
+
229
308
# Test that only components can be added to units
230
309
# Test that components must be in the same learning package
231
310
# Test that _version_pks=[] arguments must be related to publishable_entities_pks
0 commit comments