diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ae070e..d62febd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,6 +29,7 @@ repos: language: system types: - python + pass_filenames: false require_serial: true - id: pytest name: Run pytest diff --git a/src/publisher/core.py b/src/publisher/core.py index 50fdf6b..5fa8295 100644 --- a/src/publisher/core.py +++ b/src/publisher/core.py @@ -85,19 +85,27 @@ def publish_json( raise NotImplementedError @abstractmethod - def publish_str(self, key: str, value: str, no_prefix: bool = False) -> None: + def publish_str( + self, key: str, value: str, no_prefix: bool = False, *, retain: bool = True + ) -> None: raise NotImplementedError @abstractmethod - def publish_int(self, key: str, value: int, no_prefix: bool = False) -> None: + def publish_int( + self, key: str, value: int, no_prefix: bool = False, *, retain: bool = True + ) -> None: raise NotImplementedError @abstractmethod - def publish_bool(self, key: str, value: bool, no_prefix: bool = False) -> None: + def publish_bool( + self, key: str, value: bool, no_prefix: bool = False, *, retain: bool = True + ) -> None: raise NotImplementedError @abstractmethod - def publish_float(self, key: str, value: float, no_prefix: bool = False) -> None: + def publish_float( + self, key: str, value: float, no_prefix: bool = False, *, retain: bool = True + ) -> None: raise NotImplementedError @abstractmethod diff --git a/src/publisher/log_publisher.py b/src/publisher/log_publisher.py index 00deb67..45e30d2 100644 --- a/src/publisher/log_publisher.py +++ b/src/publisher/log_publisher.py @@ -32,27 +32,35 @@ def publish_json( retain: bool = True, ) -> None: anonymized_json = self.dict_to_anonymized_json(data) - self.internal_publish(key, anonymized_json) + self.internal_publish(key, anonymized_json, retain=retain) @override - def publish_str(self, key: str, value: str, no_prefix: bool = False) -> None: - self.internal_publish(key, value) + def publish_str( + self, key: str, value: str, no_prefix: bool = False, *, retain: bool = True + ) -> None: + self.internal_publish(key, value, retain=retain) @override - def publish_int(self, key: str, value: int, no_prefix: bool = False) -> None: - self.internal_publish(key, value) + def publish_int( + self, key: str, value: int, no_prefix: bool = False, *, retain: bool = True + ) -> None: + self.internal_publish(key, value, retain=retain) @override - def publish_bool(self, key: str, value: bool, no_prefix: bool = False) -> None: - self.internal_publish(key, value) + def publish_bool( + self, key: str, value: bool, no_prefix: bool = False, *, retain: bool = True + ) -> None: + self.internal_publish(key, value, retain=retain) @override - def publish_float(self, key: str, value: float, no_prefix: bool = False) -> None: - self.internal_publish(key, value) + def publish_float( + self, key: str, value: float, no_prefix: bool = False, *, retain: bool = True + ) -> None: + self.internal_publish(key, value, retain=retain) @override def clear_topic(self, key: str, no_prefix: bool = False) -> None: self.internal_publish(key, None) - def internal_publish(self, key: str, value: Any) -> None: - LOG.debug(f"{key}: {value}") + def internal_publish(self, key: str, value: Any, *, retain: bool = True) -> None: + LOG.debug(f"{key}: {value} (retain={retain})") diff --git a/src/publisher/mqtt_publisher.py b/src/publisher/mqtt_publisher.py index b441fef..6d3256f 100644 --- a/src/publisher/mqtt_publisher.py +++ b/src/publisher/mqtt_publisher.py @@ -248,20 +248,36 @@ def publish_json( ) @override - def publish_str(self, key: str, value: str, no_prefix: bool = False) -> None: - self.__publish(topic=self.get_topic(key, no_prefix), payload=value) + def publish_str( + self, key: str, value: str, no_prefix: bool = False, *, retain: bool = True + ) -> None: + self.__publish( + topic=self.get_topic(key, no_prefix), payload=value, retain=retain + ) @override - def publish_int(self, key: str, value: int, no_prefix: bool = False) -> None: - self.__publish(topic=self.get_topic(key, no_prefix), payload=value) + def publish_int( + self, key: str, value: int, no_prefix: bool = False, *, retain: bool = True + ) -> None: + self.__publish( + topic=self.get_topic(key, no_prefix), payload=value, retain=retain + ) @override - def publish_bool(self, key: str, value: bool, no_prefix: bool = False) -> None: - self.__publish(topic=self.get_topic(key, no_prefix), payload=value) + def publish_bool( + self, key: str, value: bool, no_prefix: bool = False, *, retain: bool = True + ) -> None: + self.__publish( + topic=self.get_topic(key, no_prefix), payload=value, retain=retain + ) @override - def publish_float(self, key: str, value: float, no_prefix: bool = False) -> None: - self.__publish(topic=self.get_topic(key, no_prefix), payload=value) + def publish_float( + self, key: str, value: float, no_prefix: bool = False, *, retain: bool = True + ) -> None: + self.__publish( + topic=self.get_topic(key, no_prefix), payload=value, retain=retain + ) @override def clear_topic(self, key: str, no_prefix: bool = False) -> None: diff --git a/tests/mocks/__init__.py b/tests/mocks/__init__.py index 9790d3a..de6db2d 100644 --- a/tests/mocks/__init__.py +++ b/tests/mocks/__init__.py @@ -18,7 +18,7 @@ def __init__(self, configuration: Configuration) -> None: self.publish_count: dict[str, int] = {} @override - def internal_publish(self, key: str, value: Any) -> None: + def internal_publish(self, key: str, value: Any, *, retain: bool = True) -> None: self.map[key] = value self.publish_count[key] = self.publish_count.get(key, 0) + 1 - LOG.debug(f"{key}: {value}") + LOG.debug(f"{key}: {value} (retain={retain})") diff --git a/tests/test_mqtt_publisher.py b/tests/test_mqtt_publisher.py index 2416dc9..1fdeba3 100644 --- a/tests/test_mqtt_publisher.py +++ b/tests/test_mqtt_publisher.py @@ -2,6 +2,7 @@ from typing import Any, override import unittest +from unittest.mock import patch from configuration import Configuration, TransportProtocol from publisher.core import MqttCommandListener @@ -98,3 +99,64 @@ async def on_charger_connection_state_changed( self, vin: str, connected: bool ) -> None: pass + + def test_publish_str_default_is_retained(self) -> None: + with patch.object(self.mqtt_client.client, "publish") as m_pub: + self.mqtt_client.publish_str("foo", "bar") + m_pub.assert_called_once_with("saic/foo", "bar", retain=True) + + def test_publish_str_forwards_retain_false(self) -> None: + with patch.object(self.mqtt_client.client, "publish") as m_pub: + self.mqtt_client.publish_str("foo", "bar", retain=False) + m_pub.assert_called_once_with("saic/foo", "bar", retain=False) + + def test_publish_int_default_is_retained(self) -> None: + with patch.object(self.mqtt_client.client, "publish") as m_pub: + self.mqtt_client.publish_int("foo", 42) + m_pub.assert_called_once_with("saic/foo", 42, retain=True) + + def test_publish_int_forwards_retain_false(self) -> None: + with patch.object(self.mqtt_client.client, "publish") as m_pub: + self.mqtt_client.publish_int("foo", 42, retain=False) + m_pub.assert_called_once_with("saic/foo", 42, retain=False) + + def test_publish_bool_default_is_retained(self) -> None: + with patch.object(self.mqtt_client.client, "publish") as m_pub: + self.mqtt_client.publish_bool("foo", True) + m_pub.assert_called_once_with("saic/foo", True, retain=True) + + def test_publish_bool_forwards_retain_false(self) -> None: + with patch.object(self.mqtt_client.client, "publish") as m_pub: + self.mqtt_client.publish_bool("foo", True, retain=False) + m_pub.assert_called_once_with("saic/foo", True, retain=False) + + def test_publish_float_default_is_retained(self) -> None: + with patch.object(self.mqtt_client.client, "publish") as m_pub: + self.mqtt_client.publish_float("foo", 1.5) + m_pub.assert_called_once_with("saic/foo", 1.5, retain=True) + + def test_publish_float_forwards_retain_false(self) -> None: + with patch.object(self.mqtt_client.client, "publish") as m_pub: + self.mqtt_client.publish_float("foo", 1.5, retain=False) + m_pub.assert_called_once_with("saic/foo", 1.5, retain=False) + + def test_publish_json_default_is_retained(self) -> None: + with patch.object(self.mqtt_client.client, "publish") as m_pub: + self.mqtt_client.publish_json("foo", {"a": 1}) + m_pub.assert_called_once() + args, kwargs = m_pub.call_args + assert args[0] == "saic/foo" + assert kwargs == {"retain": True} + + def test_publish_json_forwards_retain_false(self) -> None: + with patch.object(self.mqtt_client.client, "publish") as m_pub: + self.mqtt_client.publish_json("foo", {"a": 1}, retain=False) + m_pub.assert_called_once() + args, kwargs = m_pub.call_args + assert args[0] == "saic/foo" + assert kwargs == {"retain": False} + + def test_clear_topic_publishes_none_retained(self) -> None: + with patch.object(self.mqtt_client.client, "publish") as m_pub: + self.mqtt_client.clear_topic("foo") + m_pub.assert_called_once_with("saic/foo", None, retain=True)