|
@@ -17,54 +17,41 @@
|
|
|
|
|
|
import datetime
|
|
|
import logging
|
|
|
+import typing
|
|
|
import unittest.mock
|
|
|
|
|
|
-import dbus
|
|
|
+import jeepney
|
|
|
+import jeepney.low_level
|
|
|
+import jeepney.wrappers
|
|
|
import pytest
|
|
|
|
|
|
import systemctl_mqtt._dbus
|
|
|
|
|
|
-_UTC = datetime.timezone(offset=datetime.timedelta(seconds=0))
|
|
|
-
|
|
|
|
|
|
|
|
|
|
|
|
-def test_get_login_manager():
|
|
|
- login_manager = systemctl_mqtt._dbus.get_login_manager()
|
|
|
- assert isinstance(login_manager, dbus.proxies.Interface)
|
|
|
- assert login_manager.dbus_interface == "org.freedesktop.login1.Manager"
|
|
|
+def test_get_login_manager_proxy():
|
|
|
+ login_manager = systemctl_mqtt._dbus.get_login_manager_proxy()
|
|
|
+ assert isinstance(login_manager, jeepney.io.blocking.Proxy)
|
|
|
+ assert login_manager._msggen.interface == "org.freedesktop.login1.Manager"
|
|
|
|
|
|
- assert isinstance(login_manager.CanPowerOff(), dbus.String)
|
|
|
+ assert login_manager.CanPowerOff() in {("yes",), ("challenge",)}
|
|
|
|
|
|
|
|
|
def test__log_shutdown_inhibitors_some(caplog):
|
|
|
login_manager = unittest.mock.MagicMock()
|
|
|
- login_manager.ListInhibitors.return_value = dbus.Array(
|
|
|
+ login_manager.ListInhibitors.return_value = (
|
|
|
[
|
|
|
- dbus.Struct(
|
|
|
- (
|
|
|
- dbus.String("shutdown:sleep"),
|
|
|
- dbus.String("Developer"),
|
|
|
- dbus.String("Haven't pushed my commits yet"),
|
|
|
- dbus.String("delay"),
|
|
|
- dbus.UInt32(1000),
|
|
|
- dbus.UInt32(1234),
|
|
|
- ),
|
|
|
- signature=None,
|
|
|
- ),
|
|
|
- dbus.Struct(
|
|
|
- (
|
|
|
- dbus.String("shutdown"),
|
|
|
- dbus.String("Editor"),
|
|
|
- dbus.String(""),
|
|
|
- dbus.String("Unsafed files open"),
|
|
|
- dbus.UInt32(0),
|
|
|
- dbus.UInt32(42),
|
|
|
- ),
|
|
|
- signature=None,
|
|
|
+ (
|
|
|
+ "shutdown:sleep",
|
|
|
+ "Developer",
|
|
|
+ "Haven't pushed my commits yet",
|
|
|
+ "delay",
|
|
|
+ 1000,
|
|
|
+ 1234,
|
|
|
),
|
|
|
+ ("shutdown", "Editor", "", "Unsafed files open", 0, 42),
|
|
|
],
|
|
|
- signature=dbus.Signature("(ssssuu)"),
|
|
|
)
|
|
|
with caplog.at_level(logging.DEBUG):
|
|
|
systemctl_mqtt._dbus._log_shutdown_inhibitors(login_manager)
|
|
@@ -79,7 +66,7 @@ def test__log_shutdown_inhibitors_some(caplog):
|
|
|
|
|
|
def test__log_shutdown_inhibitors_none(caplog):
|
|
|
login_manager = unittest.mock.MagicMock()
|
|
|
- login_manager.ListInhibitors.return_value = dbus.Array([])
|
|
|
+ login_manager.ListInhibitors.return_value = ([],)
|
|
|
with caplog.at_level(logging.DEBUG):
|
|
|
systemctl_mqtt._dbus._log_shutdown_inhibitors(login_manager)
|
|
|
assert len(caplog.records) == 1
|
|
@@ -89,12 +76,15 @@ def test__log_shutdown_inhibitors_none(caplog):
|
|
|
|
|
|
def test__log_shutdown_inhibitors_fail(caplog):
|
|
|
login_manager = unittest.mock.MagicMock()
|
|
|
- login_manager.ListInhibitors.side_effect = dbus.DBusException("mocked")
|
|
|
+ login_manager.ListInhibitors.side_effect = DBusErrorResponseMock("error", "mocked")
|
|
|
with caplog.at_level(logging.DEBUG):
|
|
|
systemctl_mqtt._dbus._log_shutdown_inhibitors(login_manager)
|
|
|
assert len(caplog.records) == 1
|
|
|
assert caplog.records[0].levelno == logging.WARNING
|
|
|
- assert caplog.records[0].message == "failed to fetch shutdown inhibitors: mocked"
|
|
|
+ assert (
|
|
|
+ caplog.records[0].message
|
|
|
+ == "failed to fetch shutdown inhibitors: [error] mocked"
|
|
|
+ )
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("action", ["poweroff", "reboot"])
|
|
@@ -102,40 +92,52 @@ def test__log_shutdown_inhibitors_fail(caplog):
|
|
|
def test__schedule_shutdown(action, delay):
|
|
|
login_manager_mock = unittest.mock.MagicMock()
|
|
|
with unittest.mock.patch(
|
|
|
- "systemctl_mqtt._dbus.get_login_manager", return_value=login_manager_mock
|
|
|
+ "systemctl_mqtt._dbus.get_login_manager_proxy", return_value=login_manager_mock
|
|
|
):
|
|
|
systemctl_mqtt._dbus.schedule_shutdown(action=action, delay=delay)
|
|
|
login_manager_mock.ScheduleShutdown.assert_called_once()
|
|
|
schedule_args, schedule_kwargs = login_manager_mock.ScheduleShutdown.call_args
|
|
|
- assert len(schedule_args) == 2
|
|
|
- assert schedule_args[0] == action
|
|
|
- assert isinstance(schedule_args[1], dbus.UInt64)
|
|
|
- shutdown_datetime = datetime.datetime.fromtimestamp(
|
|
|
- schedule_args[1] / 10**6, tz=_UTC
|
|
|
- )
|
|
|
- actual_delay = shutdown_datetime - datetime.datetime.now(tz=_UTC)
|
|
|
+ assert not schedule_args
|
|
|
+ assert schedule_kwargs.pop("action") == action
|
|
|
+ actual_delay = schedule_kwargs.pop("time") - datetime.datetime.now()
|
|
|
assert actual_delay.total_seconds() == pytest.approx(delay.total_seconds(), abs=0.1)
|
|
|
assert not schedule_kwargs
|
|
|
|
|
|
|
|
|
+class DBusErrorResponseMock(jeepney.wrappers.DBusErrorResponse):
|
|
|
+
|
|
|
+ def __init__(self, name: str, data: typing.Any):
|
|
|
+ self.name = name
|
|
|
+ self.data = data
|
|
|
+
|
|
|
+
|
|
|
@pytest.mark.parametrize("action", ["poweroff"])
|
|
|
@pytest.mark.parametrize(
|
|
|
- ("exception_message", "log_message"),
|
|
|
+ ("error_name", "error_message", "log_message"),
|
|
|
[
|
|
|
- ("test message", "test message"),
|
|
|
(
|
|
|
+ "test error",
|
|
|
+ "test message",
|
|
|
+ "[test error] ('test message',)",
|
|
|
+ ),
|
|
|
+ (
|
|
|
+ "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired",
|
|
|
"Interactive authentication required.",
|
|
|
"unauthorized; missing polkit authorization rules?",
|
|
|
),
|
|
|
],
|
|
|
)
|
|
|
-def test__schedule_shutdown_fail(caplog, action, exception_message, log_message):
|
|
|
+def test__schedule_shutdown_fail(
|
|
|
+ caplog, action, error_name, error_message, log_message
|
|
|
+):
|
|
|
login_manager_mock = unittest.mock.MagicMock()
|
|
|
- login_manager_mock.ScheduleShutdown.side_effect = dbus.DBusException(
|
|
|
- exception_message
|
|
|
+ login_manager_mock.ScheduleShutdown.side_effect = DBusErrorResponseMock(
|
|
|
+ name=error_name,
|
|
|
+ data=(error_message,),
|
|
|
)
|
|
|
+ login_manager_mock.ListInhibitors.return_value = ([],)
|
|
|
with unittest.mock.patch(
|
|
|
- "systemctl_mqtt._dbus.get_login_manager", return_value=login_manager_mock
|
|
|
+ "systemctl_mqtt._dbus.get_login_manager_proxy", return_value=login_manager_mock
|
|
|
), caplog.at_level(logging.DEBUG):
|
|
|
systemctl_mqtt._dbus.schedule_shutdown(
|
|
|
action=action, delay=datetime.timedelta(seconds=21)
|
|
@@ -152,10 +154,64 @@ def test__schedule_shutdown_fail(caplog, action, exception_message, log_message)
|
|
|
def test_lock_all_sessions(caplog):
|
|
|
login_manager_mock = unittest.mock.MagicMock()
|
|
|
with unittest.mock.patch(
|
|
|
- "systemctl_mqtt._dbus.get_login_manager", return_value=login_manager_mock
|
|
|
+ "systemctl_mqtt._dbus.get_login_manager_proxy", return_value=login_manager_mock
|
|
|
), caplog.at_level(logging.INFO):
|
|
|
systemctl_mqtt._dbus.lock_all_sessions()
|
|
|
login_manager_mock.LockSessions.assert_called_once_with()
|
|
|
assert len(caplog.records) == 1
|
|
|
assert caplog.records[0].levelno == logging.INFO
|
|
|
assert caplog.records[0].message == "instruct all sessions to activate screen locks"
|
|
|
+
|
|
|
+
|
|
|
+def test__run_signal_loop():
|
|
|
+
|
|
|
+ login_manager_mock = unittest.mock.MagicMock()
|
|
|
+ dbus_connection_mock = unittest.mock.MagicMock()
|
|
|
+ with unittest.mock.patch(
|
|
|
+ "paho.mqtt.client.Client"
|
|
|
+ ) as mqtt_client_mock, unittest.mock.patch(
|
|
|
+ "systemctl_mqtt._dbus.get_login_manager_proxy", return_value=login_manager_mock
|
|
|
+ ), unittest.mock.patch(
|
|
|
+ "jeepney.io.blocking.open_dbus_connection", return_value=dbus_connection_mock
|
|
|
+ ) as open_dbus_connection_mock:
|
|
|
+ add_match_reply = unittest.mock.Mock()
|
|
|
+ add_match_reply.body = ()
|
|
|
+ dbus_connection_mock.send_and_get_reply.return_value = add_match_reply
|
|
|
+ dbus_connection_mock.recv_until_filtered.side_effect = [
|
|
|
+ jeepney.low_level.Message(header=None, body=(False,)),
|
|
|
+ jeepney.low_level.Message(header=None, body=(True,)),
|
|
|
+ jeepney.low_level.Message(header=None, body=(False,)),
|
|
|
+ ]
|
|
|
+ login_manager_mock.Inhibit.return_value = (jeepney.fds.FileDescriptor(-1),)
|
|
|
+ with pytest.raises(StopIteration):
|
|
|
+ systemctl_mqtt._run(
|
|
|
+ mqtt_host="localhost",
|
|
|
+ mqtt_port=1833,
|
|
|
+ mqtt_username=None,
|
|
|
+ mqtt_password=None,
|
|
|
+ mqtt_topic_prefix="systemctl/host",
|
|
|
+ homeassistant_discovery_prefix="homeassistant",
|
|
|
+ homeassistant_discovery_object_id="test",
|
|
|
+ poweroff_delay=datetime.timedelta(),
|
|
|
+ )
|
|
|
+ open_dbus_connection_mock.assert_called_once_with(bus="SYSTEM")
|
|
|
+ dbus_connection_mock.send_and_get_reply.assert_called_once()
|
|
|
+ add_match_msg = dbus_connection_mock.send_and_get_reply.call_args[0][0]
|
|
|
+ assert (
|
|
|
+ add_match_msg.header.fields[jeepney.low_level.HeaderFields.member] == "AddMatch"
|
|
|
+ )
|
|
|
+ assert add_match_msg.body == (
|
|
|
+ "interface='org.freedesktop.login1.Manager',member='PrepareForShutdown'"
|
|
|
+ ",path='/org/freedesktop/login1',type='signal'",
|
|
|
+ )
|
|
|
+ assert mqtt_client_mock().publish.call_args_list == [
|
|
|
+ unittest.mock.call(
|
|
|
+ topic="systemctl/host/preparing-for-shutdown", payload="false", retain=True
|
|
|
+ ),
|
|
|
+ unittest.mock.call(
|
|
|
+ topic="systemctl/host/preparing-for-shutdown", payload="true", retain=True
|
|
|
+ ),
|
|
|
+ unittest.mock.call(
|
|
|
+ topic="systemctl/host/preparing-for-shutdown", payload="false", retain=True
|
|
|
+ ),
|
|
|
+ ]
|