1
0

test_service_manager.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. # systemctl-mqtt - MQTT client triggering & reporting shutdown on systemd-based systems
  2. #
  3. # Copyright (C) 2024 Fabian Peter Hammerle <fabian@hammerle.me>
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. import typing
  18. import unittest.mock
  19. import pytest
  20. import jeepney.io.asyncio
  21. import jeepney.low_level
  22. import systemctl_mqtt
  23. # pylint: disable=protected-access
  24. class DBusErrorResponseMock(jeepney.wrappers.DBusErrorResponse):
  25. # pylint: disable=missing-class-docstring,super-init-not-called
  26. def __init__(self, name: str, data: typing.Any):
  27. self.name = name
  28. self.data = data
  29. @pytest.mark.asyncio
  30. async def test__get_unit_path() -> None:
  31. router_mock = unittest.mock.AsyncMock()
  32. reply_mock = unittest.mock.MagicMock()
  33. expected_path = "/org/freedesktop/systemd1/unit/ssh_2eservice"
  34. reply_mock.body = (expected_path,)
  35. router_mock.send_and_get_reply.return_value = reply_mock
  36. service_manager = jeepney.io.asyncio.Proxy(
  37. msggen=systemctl_mqtt._dbus.service_manager.ServiceManager(),
  38. router=router_mock,
  39. )
  40. assert (
  41. await systemctl_mqtt._get_unit_path(
  42. service_manager=service_manager, unit_name="ssh.service"
  43. )
  44. == expected_path
  45. )
  46. router_mock.send_and_get_reply.assert_awaited_once()
  47. (msg,), send_kwargs = router_mock.send_and_get_reply.await_args
  48. assert isinstance(msg, jeepney.low_level.Message)
  49. assert msg.header.fields == {
  50. jeepney.low_level.HeaderFields.path: "/org/freedesktop/systemd1",
  51. jeepney.low_level.HeaderFields.destination: "org.freedesktop.systemd1",
  52. jeepney.low_level.HeaderFields.interface: "org.freedesktop.systemd1.Manager",
  53. jeepney.low_level.HeaderFields.member: "GetUnit",
  54. jeepney.low_level.HeaderFields.signature: "s",
  55. }
  56. assert msg.body == ("ssh.service",)
  57. assert not send_kwargs
  58. def test__get_unit_proxy():
  59. unit_proxy = unittest.mock.MagicMock()
  60. manager_proxy = unittest.mock.MagicMock()
  61. manager_proxy.LoadUnit.return_value = ("/unit/foo",)
  62. with unittest.mock.patch(
  63. "systemctl_mqtt._dbus.service_manager._get_connection", return_value=object()
  64. ), unittest.mock.patch(
  65. "jeepney.io.blocking.Proxy", side_effect=(manager_proxy, unit_proxy)
  66. ):
  67. assert (
  68. systemctl_mqtt._dbus.service_manager._get_unit_proxy("foo.service")
  69. is unit_proxy
  70. )
  71. manager_proxy.LoadUnit.assert_called_once_with(name="foo.service")
  72. @pytest.mark.parametrize(
  73. ("function_name", "property_name", "propery_value", "return_value"),
  74. [
  75. ("is_isolate_unit_allowed", "AllowIsolate", True, True),
  76. ("is_isolate_unit_allowed", "AllowIsolate", False, False),
  77. ],
  78. )
  79. def test__unit_property(function_name, property_name, propery_value, return_value):
  80. mock_unit_proxy = unittest.mock.MagicMock()
  81. mock_unit_proxy.Get.return_value = ((None, propery_value),)
  82. with unittest.mock.patch(
  83. "systemctl_mqtt._dbus.service_manager._get_unit_proxy",
  84. return_value=mock_unit_proxy,
  85. ):
  86. # call the wrapper function dynamically
  87. assert (
  88. getattr(systemctl_mqtt._dbus.service_manager, function_name)("foo.service")
  89. is return_value
  90. )
  91. mock_unit_proxy.Get.assert_called_once_with(property_name)
  92. @pytest.mark.parametrize(
  93. "function_name",
  94. ["is_isolate_unit_allowed"],
  95. )
  96. def test__unit_property_with_exception_on_load_unit(function_name):
  97. with unittest.mock.patch(
  98. "systemctl_mqtt._dbus.service_manager.ServiceManager.LoadUnit",
  99. side_effect=DBusErrorResponseMock("DBus error", ("mocked",)),
  100. ), unittest.mock.patch(
  101. "systemctl_mqtt._dbus.service_manager._LOGGER"
  102. ) as mock_logger:
  103. assert (
  104. getattr(systemctl_mqtt._dbus.service_manager, function_name)("foo.service")
  105. is False
  106. )
  107. mock_logger.error.assert_called_once_with(
  108. "Failed to load unit: %s because %s",
  109. "foo.service",
  110. "DBus error",
  111. )
  112. @pytest.mark.parametrize(
  113. ("function_name", "property_name"),
  114. [("is_isolate_unit_allowed", "AllowIsolate")],
  115. )
  116. def test__unit_property_with_exception_on_get(function_name, property_name):
  117. mock_proxy = unittest.mock.MagicMock()
  118. mock_proxy.Get.side_effect = DBusErrorResponseMock("DBus error", ("mocked",))
  119. with unittest.mock.patch(
  120. "systemctl_mqtt._dbus.service_manager._get_unit_proxy",
  121. return_value=mock_proxy,
  122. ), unittest.mock.patch(
  123. "systemctl_mqtt._dbus.service_manager._LOGGER"
  124. ) as mock_logger:
  125. assert (
  126. getattr(systemctl_mqtt._dbus.service_manager, function_name)("foo.service")
  127. is False
  128. )
  129. mock_logger.error.assert_called_once_with(
  130. f"Failed to get {property_name} property of unit %s because %s",
  131. "foo.service",
  132. "DBus error",
  133. )
  134. @pytest.mark.parametrize(
  135. "action,method,mode",
  136. [
  137. ("start", "StartUnit", "replace"),
  138. ("stop", "StopUnit", "replace"),
  139. ("restart", "RestartUnit", "replace"),
  140. ("isolate", "StartUnit", "isolate"),
  141. ],
  142. )
  143. def test__unit_proxy(action, method, mode):
  144. mock_proxy = unittest.mock.MagicMock()
  145. with unittest.mock.patch(
  146. "systemctl_mqtt._dbus.service_manager.get_service_manager_proxy",
  147. return_value=mock_proxy,
  148. ):
  149. # call the wrapper function dynamically
  150. getattr(systemctl_mqtt._dbus.service_manager, f"{action}_unit")("foo.service")
  151. getattr(mock_proxy, method).assert_called_once_with("foo.service", mode)
  152. @pytest.mark.parametrize(
  153. "method",
  154. [
  155. "StartUnit",
  156. "StopUnit",
  157. "RestartUnit",
  158. ],
  159. )
  160. def test__unit_method_call(method):
  161. with unittest.mock.patch(
  162. "jeepney.new_method_call", return_value=unittest.mock.MagicMock()
  163. ) as mock_method_call:
  164. mgr = systemctl_mqtt._dbus.service_manager.ServiceManager()
  165. getattr(mgr, method)("foo.service", "replace")
  166. mock_method_call.assert_called_once_with(
  167. remote_obj=mgr,
  168. method=method,
  169. signature="ss",
  170. body=("foo.service", "replace"),
  171. )
  172. @pytest.mark.parametrize(
  173. "action,method",
  174. [
  175. ("start", "StartUnit"),
  176. ("stop", "StopUnit"),
  177. ("restart", "RestartUnit"),
  178. ("isolate", "StartUnit"),
  179. ],
  180. )
  181. def test__unit_with_exception(action, method):
  182. mock_proxy = unittest.mock.MagicMock()
  183. getattr(mock_proxy, method).side_effect = DBusErrorResponseMock(
  184. "DBus error", ("mocked",)
  185. )
  186. with unittest.mock.patch(
  187. "systemctl_mqtt._dbus.service_manager.get_service_manager_proxy",
  188. return_value=mock_proxy,
  189. ), unittest.mock.patch(
  190. "systemctl_mqtt._dbus.service_manager._LOGGER"
  191. ) as mock_logger:
  192. getattr(systemctl_mqtt._dbus.service_manager, f"{action}_unit")(
  193. "example.service"
  194. )
  195. mock_logger.error.assert_called_once_with(
  196. f"Failed to {action} unit: %s because %s ",
  197. "example.service",
  198. "DBus error",
  199. )