@@ -210,7 +210,7 @@ def test_deadlock_move_and_rebuild(self):
210210 for (node_id , target_id ) in zip (
211211 root_children_ids , first_child_node_children_ids
212212 )
213- )
213+ ),
214214 )
215215
216216 for result in results :
@@ -236,7 +236,7 @@ def test_deadlock_create_and_rebuild(self):
236236 * (
237237 (create_contentnode , {"parent_id" : node_id })
238238 for node_id in first_child_node_children_ids
239- )
239+ ),
240240 )
241241
242242 for result in results :
@@ -1903,6 +1903,132 @@ def test_delete_no_permission_prerequisite(self):
19031903 self .assertEqual (len (response .data ["disallowed" ]), 1 )
19041904 self .assertTrue (contentnode .prerequisite .filter (id = prereq .id ).exists ())
19051905
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+
19062032
19072033class CRUDTestCase (StudioAPITestCase ):
19082034 def setUp (self ):
0 commit comments