@@ -452,3 +452,90 @@ def test_power_rescue(ridesx_power_driver):
452452 with pytest .raises (NotImplementedError , match = "Rescue mode not available" ):
453453 client .call ("rescue" )
454454
455+
456+ # Flash OCI Image Tests
457+ # Note: FLS download utilities are tested in jumpstarter.common.fls_test
458+
459+
460+ def test_flash_oci_image_success (temp_storage_dir , ridesx_driver ):
461+ with serve (ridesx_driver ) as client :
462+ with patch ("jumpstarter_driver_ridesx.driver.get_fls_binary" , return_value = "/usr/local/bin/fls" ):
463+ with patch ("subprocess.run" ) as mock_subprocess :
464+ mock_result = MagicMock ()
465+ mock_result .stdout = "Flashing complete"
466+ mock_result .stderr = ""
467+ mock_result .returncode = 0
468+ mock_subprocess .return_value = mock_result
469+
470+ result = client .call ("flash_oci_image" , "oci://quay.io/image:tag" , None )
471+
472+ assert result ["status" ] == "success"
473+ mock_subprocess .assert_called_once ()
474+ call_args = mock_subprocess .call_args [0 ][0 ]
475+ assert call_args [0 ] == "/usr/local/bin/fls"
476+ assert call_args [1 ] == "fastboot"
477+ assert call_args [2 ] == "oci://quay.io/image:tag"
478+
479+
480+ def test_flash_oci_image_with_partitions (temp_storage_dir , ridesx_driver ):
481+ with serve (ridesx_driver ) as client :
482+ with patch ("jumpstarter_driver_ridesx.driver.get_fls_binary" , return_value = "fls" ):
483+ with patch ("subprocess.run" ) as mock_subprocess :
484+ mock_result = MagicMock ()
485+ mock_result .stdout = "Flashing complete"
486+ mock_result .stderr = ""
487+ mock_result .returncode = 0
488+ mock_subprocess .return_value = mock_result
489+
490+ partitions = {"boot_a" : "boot.img" , "system_a" : "rootfs.simg" }
491+ result = client .call ("flash_oci_image" , "oci://image:tag" , partitions )
492+
493+ assert result ["status" ] == "success"
494+ call_args = mock_subprocess .call_args [0 ][0 ]
495+ # Check that -t flags are present for partitions
496+ assert "-t" in call_args
497+ assert "boot_a:boot.img" in call_args
498+ assert "system_a:rootfs.simg" in call_args
499+
500+
501+ def test_flash_oci_image_error_cases (temp_storage_dir , ridesx_driver ):
502+ """Test flash_oci_image error handling for various failure modes"""
503+ from jumpstarter .client .core import DriverError
504+
505+ with serve (ridesx_driver ) as client :
506+ # Reject non-oci:// schemes
507+ with pytest .raises (DriverError , match = "OCI URL must start with oci://" ):
508+ client .call ("flash_oci_image" , "docker://image:tag" , None )
509+
510+ with patch ("jumpstarter_driver_ridesx.driver.get_fls_binary" , return_value = "fls" ):
511+ with patch ("subprocess.run" ) as mock_subprocess :
512+ # CalledProcessError
513+ error = subprocess .CalledProcessError (1 , "fls" )
514+ error .stdout = ""
515+ error .stderr = "Flash failed"
516+ mock_subprocess .side_effect = error
517+
518+ with pytest .raises (DriverError , match = "FLS fastboot auto-detection failed" ):
519+ client .call ("flash_oci_image" , "oci://image:tag" , None )
520+
521+ # TimeoutExpired
522+ mock_subprocess .side_effect = subprocess .TimeoutExpired ("fls" , 1800 )
523+
524+ with pytest .raises (DriverError , match = "FLS fastboot auto-detection timeout" ):
525+ client .call ("flash_oci_image" , "oci://image:tag" , None )
526+
527+ # FileNotFoundError
528+ mock_subprocess .side_effect = FileNotFoundError ("fls not found" )
529+
530+ with pytest .raises (DriverError , match = "FLS command not found" ):
531+ client .call ("flash_oci_image" , "oci://image:tag" , None )
532+
533+
534+ def test_flash_oci_image_requires_oci_scheme (temp_storage_dir , ridesx_driver ):
535+ """Test that only oci:// URLs are accepted"""
536+ from jumpstarter .client .core import DriverError
537+
538+ with serve (ridesx_driver ) as client :
539+ # Bare registry URL should be rejected
540+ with pytest .raises (DriverError , match = "OCI URL must start with oci://" ):
541+ client .call ("flash_oci_image" , "quay.io/org/image:v1" , None )
0 commit comments