diff --git a/pylabrobot/thermocycling/opentrons_backend.py b/pylabrobot/thermocycling/opentrons_backend.py index f2b444c786d..039708f348f 100644 --- a/pylabrobot/thermocycling/opentrons_backend.py +++ b/pylabrobot/thermocycling/opentrons_backend.py @@ -162,7 +162,10 @@ async def get_block_status(self) -> BlockStatus: return BlockStatus.IDLE async def get_hold_time(self) -> float: - return cast(float, self._find_module().get("holdTime", 0.0)) + hold_time = self._find_module().get("holdTime") + if hold_time is None: + raise RuntimeError("Hold time is not available. Is a profile running?") + return cast(float, hold_time) async def get_current_cycle_index(self) -> int: """Get the zero-based index of the current cycle from the Opentrons API.""" diff --git a/pylabrobot/thermocycling/opentrons_backend_tests.py b/pylabrobot/thermocycling/opentrons_backend_tests.py index b883b99b1f4..c68460a991e 100644 --- a/pylabrobot/thermocycling/opentrons_backend_tests.py +++ b/pylabrobot/thermocycling/opentrons_backend_tests.py @@ -109,3 +109,11 @@ async def test_getters_return_correct_data(self, mock_list_connected_modules): # assert await self.thermocycler_backend.get_total_cycle_count() == 10 assert await self.thermocycler_backend.get_current_step_index() == 0 # 1 - 1 = 0 (zero-based) assert await self.thermocycler_backend.get_total_step_count() == 3 + + @patch("pylabrobot.thermocycling.opentrons_backend.list_connected_modules") + async def test_get_hold_time_raises_if_not_running(self, mock_list_connected_modules): + mock_list_connected_modules.return_value = [{"id": "test_id", "data": {}}] + + with self.assertRaises(RuntimeError) as e: + await self.thermocycler_backend.get_hold_time() + self.assertEqual(str(e.exception), "Hold time is not available. Is a profile running?") diff --git a/pylabrobot/thermocycling/thermocycler_tests.py b/pylabrobot/thermocycling/thermocycler_tests.py index 157d6762358..db0d4821c6f 100644 --- a/pylabrobot/thermocycling/thermocycler_tests.py +++ b/pylabrobot/thermocycling/thermocycler_tests.py @@ -137,3 +137,17 @@ async def test_is_profile_running_logic(self): self.tc.backend.get_total_step_count.return_value = total_steps # type: ignore print(f"Testing with hold={hold}, cycle={cycle}, total_cycles={total_cycles}, ") assert await self.tc.is_profile_running() is expected + + async def test_is_profile_running_raises_if_no_profile_running(self): + """RuntimeError from get_hold_time should propagate.""" + self.tc.backend.get_hold_time.side_effect = RuntimeError("No profile is running") # type: ignore + + with self.assertRaises(RuntimeError): + await self.tc.is_profile_running() + + async def test_is_profile_running_preserves_not_implemented_error(self): + """Unsupported backends should still surface NotImplementedError.""" + self.tc.backend.get_hold_time.side_effect = NotImplementedError # type: ignore + + with self.assertRaises(NotImplementedError): + await self.tc.is_profile_running()