Skip to content

Commit 3f1f615

Browse files
committed
ridesx: add tests for fls integration
Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
1 parent bb2a238 commit 3f1f615

3 files changed

Lines changed: 108 additions & 8 deletions

File tree

python/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client_test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def test_flash_oci_auto_success(ridesx_client):
5959
"""Test successful flash_oci_auto call"""
6060
with patch.object(ridesx_client, "call") as mock_call:
6161
mock_call.side_effect = [
62+
None, # boot_to_fastboot call
6263
{"status": "device_found", "device_id": "ABC123"},
6364
{"status": "success"},
6465
]
@@ -67,7 +68,7 @@ def test_flash_oci_auto_success(ridesx_client):
6768

6869
assert result == {"status": "success"}
6970
# Verify flash_oci_image was called with the OCI URL
70-
flash_call = mock_call.call_args_list[1]
71+
flash_call = mock_call.call_args_list[2]
7172
assert flash_call[0][0] == "flash_oci_image"
7273
assert flash_call[0][1] == "oci://quay.io/org/image:tag"
7374

python/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/driver_test.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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)

python/packages/jumpstarter/jumpstarter/common/fls_test.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,31 @@ def test_get_fls_binary_falls_back_to_path():
6666

6767

6868
def test_download_fls_success():
69-
with patch("urllib.request.urlretrieve") as mock_retrieve:
69+
from unittest.mock import MagicMock, mock_open
70+
71+
mock_response = MagicMock()
72+
mock_response.read.side_effect = [b"binary data", b""] # Simulate chunked read
73+
mock_response.__enter__ = MagicMock(return_value=mock_response)
74+
mock_response.__exit__ = MagicMock(return_value=None)
75+
76+
with patch("urllib.request.urlopen", return_value=mock_response) as mock_urlopen:
7077
with patch("tempfile.mkstemp", return_value=(99, "/tmp/fls-test")):
7178
with patch("os.close") as mock_close:
72-
with patch("pathlib.Path.chmod"):
73-
result = download_fls("https://example.com/fls")
79+
with patch("pathlib.Path.chmod") as mock_chmod:
80+
with patch("os.replace") as mock_replace:
81+
with patch("builtins.open", mock_open()):
82+
with patch("os.fsync"):
83+
result = download_fls("https://example.com/fls")
7484

75-
mock_close.assert_called_once_with(99)
76-
mock_retrieve.assert_called_once_with("https://example.com/fls", "/tmp/fls-test")
77-
assert result == "/tmp/fls-test"
85+
mock_close.assert_called_once_with(99)
86+
mock_urlopen.assert_called_once_with("https://example.com/fls", timeout=30.0)
87+
mock_chmod.assert_called_once_with(0o755)
88+
mock_replace.assert_called_once_with("/tmp/fls-test.part", "/tmp/fls-test")
89+
assert result == "/tmp/fls-test"
7890

7991

8092
def test_download_fls_failure():
81-
with patch("urllib.request.urlretrieve", side_effect=Exception("Network error")):
93+
with patch("urllib.request.urlopen", side_effect=Exception("Network error")):
8294
with patch("tempfile.mkstemp", return_value=(99, "/tmp/fls-test")):
8395
with patch("os.close"):
8496
with patch("pathlib.Path.unlink"):

0 commit comments

Comments
 (0)