"""Waiting for vSphere tasks, and turning vSphere faults into RuntimeError.""" from __future__ import annotations from unittest.mock import MagicMock import pytest from pyVmomi import vim from napalm_vmware._tasks import fault_message, invoke, wait_for_task class _Clock: def __init__(self): self.now = 0.0 def __call__(self): return self.now def sleep(self, seconds): self.now += seconds def _inventory(*states): inv = MagicMock() inv.properties.side_effect = list(states) return inv class TestWaitForTask: def test_returns_once_the_task_succeeds(self): inv = _inventory({"info.state": "running"}, {"info.state": "success"}) clock = _Clock() wait_for_task(inv, "task", timeout=10, clock=clock, sleep=clock.sleep) assert inv.properties.call_count == 2 def test_error_becomes_runtime_error_with_the_fault_text(self): inv = _inventory( { "info.state": "error", "info.error": { "_type": "InvalidPowerState", "msg": "The attempted operation cannot be performed " "in the current state (Powered off).", }, } ) with pytest.raises(RuntimeError, match="current state"): wait_for_task(inv, "task", timeout=10) def test_times_out(self): clock = _Clock() inv = MagicMock() inv.properties.return_value = {"info.state": "running"} with pytest.raises(RuntimeError, match="did not finish within 5s"): wait_for_task(inv, "task", timeout=5, clock=clock, sleep=clock.sleep) def test_vanished_task_is_an_error(self): with pytest.raises(RuntimeError, match="disappeared"): wait_for_task(_inventory({}), "task", timeout=5) class TestFaultMessage: def test_free_license(self): msg = fault_message( { "_type": "RestrictedVersion", "msg": "Current license or ESXi version prohibits " "execution of the requested operation.", } ) assert "free" in msg.lower() assert "read-only" in msg def test_localized_message_preferred(self): assert fault_message({"_type": "X", "localizedMessage": "L", "msg": "M"}) == "L" def test_bare_type(self): assert fault_message({"_type": "NotSupported"}) == "NotSupported" class TestInvoke: def test_passes_the_result_through(self): assert invoke(lambda x: x + 1, 1) == 2 def test_method_fault_becomes_runtime_error(self): def boom(): raise vim.fault.RestrictedVersion(msg="prohibited") with pytest.raises(RuntimeError, match="free"): invoke(boom) def test_other_exceptions_are_left_alone(self): def boom(): raise KeyError("x") with pytest.raises(KeyError): invoke(boom) class TestWaitUntil: def test_returns_when_the_check_passes(self): from napalm_vmware._tasks import wait_until answers = iter([False, False, True]) clock = _Clock() wait_until(lambda: next(answers), timeout=10, what="x", clock=clock, sleep=clock.sleep) assert clock.now == 1.0 def test_times_out_naming_what_it_waited_for(self): from napalm_vmware._tasks import wait_until clock = _Clock() with pytest.raises(RuntimeError, match="guest shutdown did not finish within 3s"): wait_until( lambda: False, timeout=3, what="guest shutdown", clock=clock, sleep=clock.sleep )