@@ -210,7 +210,7 @@ def test_deadlock_move_and_rebuild(self):
210
210
for (node_id , target_id ) in zip (
211
211
root_children_ids , first_child_node_children_ids
212
212
)
213
- )
213
+ ),
214
214
)
215
215
216
216
for result in results :
@@ -236,7 +236,7 @@ def test_deadlock_create_and_rebuild(self):
236
236
* (
237
237
(create_contentnode , {"parent_id" : node_id })
238
238
for node_id in first_child_node_children_ids
239
- )
239
+ ),
240
240
)
241
241
242
242
for result in results :
@@ -1903,6 +1903,132 @@ def test_delete_no_permission_prerequisite(self):
1903
1903
self .assertEqual (len (response .data ["disallowed" ]), 1 )
1904
1904
self .assertTrue (contentnode .prerequisite .filter (id = prereq .id ).exists ())
1905
1905
1906
+ def test_create_html5_contentnode_with_entry_validation (self ):
1907
+ """
1908
+ Regression test for HTML5 nodes validation failure when entry value is set in extra_fields.
1909
+
1910
+ This test verifies that newly created HTML5 content nodes with an "entry" value
1911
+ in extra_fields.options.entry can be successfully validated and created.
1912
+ """
1913
+ contentnode_data = self .contentnode_metadata
1914
+ contentnode_data ["kind" ] = content_kinds .HTML5
1915
+ contentnode_data ["extra_fields" ] = {"options" : {"entry" : "index.html" }}
1916
+
1917
+ response = self .sync_changes (
1918
+ [
1919
+ generate_create_event (
1920
+ contentnode_data ["id" ],
1921
+ CONTENTNODE ,
1922
+ contentnode_data ,
1923
+ channel_id = self .channel .id ,
1924
+ )
1925
+ ],
1926
+ )
1927
+ self .assertEqual (response .status_code , 200 , response .content )
1928
+ self .assertEqual (
1929
+ len (response .data .get ("errors" , [])),
1930
+ 0 ,
1931
+ f"Expected no validation errors, but got: { response .data .get ('errors' , [])} " ,
1932
+ )
1933
+
1934
+ try :
1935
+ new_node = models .ContentNode .objects .get (id = contentnode_data ["id" ])
1936
+ except models .ContentNode .DoesNotExist :
1937
+ self .fail ("HTML5 ContentNode with entry value was not created" )
1938
+
1939
+ self .assertEqual (new_node .parent_id , self .channel .main_tree_id )
1940
+ self .assertEqual (new_node .kind_id , content_kinds .HTML5 )
1941
+ self .assertEqual (new_node .extra_fields ["options" ]["entry" ], "index.html" )
1942
+
1943
+ def test_create_exercise_contentnode_requires_randomize (self ):
1944
+ """
1945
+ Test that exercise content nodes require the randomize field in extra_fields.
1946
+ """
1947
+ contentnode_data = self .contentnode_metadata
1948
+ contentnode_data ["kind" ] = content_kinds .EXERCISE
1949
+ # Deliberately omit randomize field
1950
+ contentnode_data ["extra_fields" ] = {"options" : {}}
1951
+
1952
+ response = self .sync_changes (
1953
+ [
1954
+ generate_create_event (
1955
+ contentnode_data ["id" ],
1956
+ CONTENTNODE ,
1957
+ contentnode_data ,
1958
+ channel_id = self .channel .id ,
1959
+ )
1960
+ ],
1961
+ )
1962
+ self .assertEqual (response .status_code , 200 , response .content )
1963
+ self .assertEqual (len (response .data .get ("errors" , [])), 1 )
1964
+
1965
+ error = response .data ["errors" ][0 ]
1966
+
1967
+ self .assertIn ("randomize" , error ["errors" ]["extra_fields" ])
1968
+ self .assertEqual (
1969
+ error ["errors" ]["extra_fields" ]["randomize" ][0 ],
1970
+ "This field is required for exercise content." ,
1971
+ )
1972
+
1973
+ def test_create_exercise_contentnode_with_randomize_succeeds (self ):
1974
+ """
1975
+ Test that exercise content nodes with randomize field are created successfully.
1976
+ """
1977
+ contentnode_data = self .contentnode_metadata
1978
+ contentnode_data ["kind" ] = content_kinds .EXERCISE
1979
+ contentnode_data ["extra_fields" ] = {"randomize" : True , "options" : {}}
1980
+
1981
+ response = self .sync_changes (
1982
+ [
1983
+ generate_create_event (
1984
+ contentnode_data ["id" ],
1985
+ CONTENTNODE ,
1986
+ contentnode_data ,
1987
+ channel_id = self .channel .id ,
1988
+ )
1989
+ ],
1990
+ )
1991
+ self .assertEqual (response .status_code , 200 , response .content )
1992
+ self .assertEqual (len (response .data .get ("errors" , [])), 0 )
1993
+
1994
+ try :
1995
+ new_node = models .ContentNode .objects .get (id = contentnode_data ["id" ])
1996
+ except models .ContentNode .DoesNotExist :
1997
+ self .fail ("Exercise ContentNode with randomize field was not created" )
1998
+
1999
+ self .assertEqual (new_node .kind_id , content_kinds .EXERCISE )
2000
+ self .assertTrue (new_node .extra_fields ["randomize" ])
2001
+
2002
+ def test_cannot_update_contentnode_kind (self ):
2003
+ """
2004
+ Test that content node kind cannot be changed after creation.
2005
+ """
2006
+ contentnode = models .ContentNode .objects .create (** self .contentnode_db_metadata )
2007
+ original_kind = contentnode .kind_id
2008
+
2009
+ response = self .sync_changes (
2010
+ [
2011
+ generate_update_event (
2012
+ contentnode .id ,
2013
+ CONTENTNODE ,
2014
+ {"kind" : content_kinds .HTML5 },
2015
+ channel_id = self .channel .id ,
2016
+ )
2017
+ ],
2018
+ )
2019
+ self .assertEqual (response .status_code , 200 , response .content )
2020
+ self .assertEqual (len (response .data .get ("errors" , [])), 1 )
2021
+
2022
+ error = response .data ["errors" ][0 ]
2023
+ self .assertIn ("kind" , error ["errors" ])
2024
+ self .assertEqual (
2025
+ error ["errors" ]["kind" ][0 ], "Content kind cannot be changed after creation"
2026
+ )
2027
+
2028
+ # Verify kind was not changed
2029
+ contentnode .refresh_from_db ()
2030
+ self .assertEqual (contentnode .kind_id , original_kind )
2031
+
1906
2032
1907
2033
class CRUDTestCase (StudioAPITestCase ):
1908
2034
def setUp (self ):
0 commit comments