File: remote_value_test.py

package info (click to toggle)
python-xknx 3.10.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,044 kB
  • sloc: python: 40,087; javascript: 8,556; makefile: 32; sh: 12
file content (306 lines) | stat: -rw-r--r-- 12,583 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
"""Unit test for RemoveValue objects."""

from unittest.mock import AsyncMock, Mock, patch

import pytest

from xknx import XKNX
from xknx.dpt import DPT2ByteFloat, DPTArray, DPTBinary
from xknx.exceptions import ConversionError, CouldNotParseTelegram
from xknx.remote_value import RemoteValue, RemoteValueSwitch
from xknx.telegram import GroupAddress, Telegram, TelegramDecodedData
from xknx.telegram.apci import GroupValueWrite


@patch.multiple(RemoteValue, __abstractmethods__=set())
class TestRemoteValue:
    """Test class for RemoteValue objects."""

    async def test_get_set_value(self) -> None:
        """Test value getter and setter."""
        xknx = XKNX()
        remote_value = RemoteValue(xknx)
        remote_value.to_knx = DPT2ByteFloat.to_knx
        remote_value.after_update_cb = Mock()

        assert remote_value.value is None
        remote_value.value = 2.2
        assert remote_value.value == 2.2
        # invalid value raises ConversionError
        with pytest.raises(ConversionError):
            remote_value.value = "a"
        # new value is used in response Telegram
        test_payload = remote_value.to_knx(2.2)
        remote_value._send = Mock()
        remote_value.respond()
        remote_value._send.assert_called_with(test_payload, response=True)
        # callback is not called when setting value programmatically
        remote_value.after_update_cb.assert_not_called()
        # no Telegram was sent to the queue
        assert xknx.telegrams.qsize() == 0

    def test_set_value(self) -> None:
        """Test set_value awaitable."""
        xknx = XKNX()
        remote_value = RemoteValue(xknx)
        remote_value.to_knx = DPT2ByteFloat.to_knx
        remote_value.after_update_cb = Mock()

        remote_value.update_value(3.3)
        assert remote_value.value == 3.3
        remote_value.after_update_cb.assert_called_once()
        assert xknx.telegrams.qsize() == 0
        # invalid value raises ConversionError
        with pytest.raises(ConversionError):
            remote_value.update_value("a")
        assert remote_value.value == 3.3

    async def test_info_set_uninitialized(self) -> None:
        """Test for info if RemoteValue is not initialized."""
        xknx = XKNX()
        remote_value = RemoteValue(xknx)
        with patch("logging.Logger.info") as mock_info:
            remote_value.set(23)
            mock_info.assert_called_with(
                "Setting value of uninitialized device: %s - %s (value: %s)",
                "Unknown",
                "Unknown",
                23,
            )

    async def test_info_set_unwritable(self) -> None:
        """Test for warning if RemoteValue is read only."""
        xknx = XKNX()
        remote_value = RemoteValue(xknx, group_address_state=GroupAddress("1/2/3"))
        with patch("logging.Logger.warning") as mock_warning:
            remote_value.set(23)
            mock_warning.assert_called_with(
                "Attempted to set value for non-writable device: %s - %s (value: %s)",
                "Unknown",
                "Unknown",
                23,
            )

    def test_default_value_unit(self) -> None:
        """Test for the default value of unit_of_measurement."""
        xknx = XKNX()
        remote_value = RemoteValue(xknx)
        assert remote_value.unit_of_measurement is None

    async def test_process_unsupported_payload_type(self) -> None:
        """Test if exception is raised when processing telegram with unsupported payload type."""
        xknx = XKNX()
        remote_value = RemoteValue(xknx, group_address="1/2/1")

        telegram = Telegram(destination_address=GroupAddress("1/2/1"), payload=object())
        with pytest.raises(
            CouldNotParseTelegram,
            match=r".*payload not a GroupValueWrite or GroupValueResponse.*",
        ):
            remote_value.process(telegram)

    def test_process_unsupported_payload(self) -> None:
        """Test warning is logged when processing telegram with unsupported payload."""
        xknx = XKNX()
        remote_value = RemoteValue(xknx, group_address="1/2/1")

        telegram = Telegram(
            destination_address=GroupAddress("1/2/1"),
            payload=GroupValueWrite(DPTArray((0x01, 0x02))),
        )
        with (
            patch("xknx.remote_value.RemoteValue.from_knx") as patch_from,
            patch("logging.Logger.warning") as mock_warning,
        ):
            patch_from.side_effect = ConversionError("TestError")

            assert remote_value.process(telegram) is False
            mock_warning.assert_called_once_with(
                "Can not process %s for %s - %s: %s",
                telegram,
                "Unknown",
                "Unknown",
                ConversionError("TestError"),
            )

    async def test_read_state(self) -> None:
        """Test read state while waiting for the result."""
        xknx = XKNX()
        remote_value = RemoteValueSwitch(xknx, group_address_state="1/2/3")

        with patch("xknx.core.ValueReader.read", new_callable=AsyncMock) as patch_read:
            telegram = Telegram(
                destination_address=GroupAddress("1/2/3"),
                payload=GroupValueWrite(DPTBinary(1)),
            )
            patch_read.return_value = telegram

            await remote_value.read_state(wait_for_result=True)
            patch_read.assert_called_once()
            # RemoteValue.value is updated by RemoteValue.process called from Device / TelegramQueue

    async def test_read_state_none(self) -> None:
        """Test read state while waiting for the result but got None."""
        xknx = XKNX()
        remote_value = RemoteValueSwitch(xknx, group_address_state="1/2/3")

        with (
            patch("xknx.core.ValueReader.read", new_callable=AsyncMock) as patch_read,
            patch("logging.Logger.warning") as mock_warning,
        ):
            patch_read.return_value = None

            await remote_value.read_state(wait_for_result=True)
            patch_read.assert_called_once()
            mock_warning.assert_called_once_with(
                "Could not sync group address '%s' (%s - %s)",
                GroupAddress("1/2/3"),
                "Unknown",
                "State",
            )

    def test_unpacking_passive_address(self) -> None:
        """Test if passive group addresses are properly unpacked."""
        xknx = XKNX()

        remote_value_1 = RemoteValue(xknx, group_address=["1/2/3", "1/1/1"])
        assert remote_value_1.group_address == GroupAddress("1/2/3")
        assert remote_value_1.group_address_state is None
        assert remote_value_1.passive_group_addresses == [GroupAddress("1/1/1")]
        assert GroupAddress("1/2/3") in remote_value_1.group_addresses()
        assert GroupAddress("1/1/1") in remote_value_1.group_addresses()

        remote_value_2 = RemoteValue(xknx, group_address_state=["1/2/3", "1/1/1"])
        assert remote_value_2.group_address is None
        assert remote_value_2.group_address_state == GroupAddress("1/2/3")
        assert remote_value_2.passive_group_addresses == [GroupAddress("1/1/1")]
        assert GroupAddress("1/2/3") in remote_value_2.group_addresses()
        assert GroupAddress("1/1/1") in remote_value_2.group_addresses()

        remote_value_3 = RemoteValue(
            xknx,
            group_address=["1/2/3", "1/1/1", "1/1/10"],
            group_address_state=["2/3/4", "2/2/2", "2/2/20"],
        )
        assert remote_value_3.group_address == GroupAddress("1/2/3")
        assert remote_value_3.group_address_state == GroupAddress("2/3/4")
        assert remote_value_3.passive_group_addresses == [
            GroupAddress("1/1/1"),
            GroupAddress("1/1/10"),
            GroupAddress("2/2/2"),
            GroupAddress("2/2/20"),
        ]
        assert GroupAddress("1/2/3") in remote_value_3.group_addresses()
        assert GroupAddress("1/1/1") in remote_value_3.group_addresses()
        assert GroupAddress("1/1/10") in remote_value_3.group_addresses()
        assert GroupAddress("2/3/4") in remote_value_3.group_addresses()
        assert GroupAddress("2/2/2") in remote_value_3.group_addresses()
        assert GroupAddress("2/2/20") in remote_value_3.group_addresses()
        assert GroupAddress("0/0/1") not in remote_value_3.group_addresses()
        # test empty list
        remote_value_4 = RemoteValue(xknx, group_address=[])
        assert remote_value_4.group_address is None
        # test None in list
        remote_value_5 = RemoteValue(
            xknx,
            group_address=["1/2/3", "1/1/1", "1/1/10"],
            group_address_state=[None, "2/2/2", "2/2/20"],
        )
        assert remote_value_5.group_address == GroupAddress("1/2/3")
        assert remote_value_5.group_address_state is None
        assert remote_value_5.passive_group_addresses == [
            GroupAddress("1/1/1"),
            GroupAddress("1/1/10"),
            GroupAddress("2/2/2"),
            GroupAddress("2/2/20"),
        ]
        remote_value_6 = RemoteValue(
            xknx,
            group_address=None,
            group_address_state=["1/1/1", None, "2/2/2", "2/2/20"],
        )
        assert remote_value_6.group_address is None
        assert remote_value_6.group_address_state == GroupAddress("1/1/1")
        assert remote_value_6.passive_group_addresses == [
            GroupAddress("2/2/2"),
            GroupAddress("2/2/20"),
        ]

    def test_process_passive_address(self) -> None:
        """Test if passive group addresses are processed."""
        xknx = XKNX()
        remote_value = RemoteValue(xknx, group_address=["1/2/3", "1/1/1"])
        remote_value.dpt_class = DPT2ByteFloat

        assert remote_value.writable
        assert not remote_value.readable
        # RemoteValue is initialized with only passive group address
        assert remote_value.initialized

        test_payload = DPTArray((0x01, 0x02))
        telegram = Telegram(
            destination_address=GroupAddress("1/1/1"),
            payload=GroupValueWrite(test_payload),
        )
        assert remote_value.process(telegram)
        assert remote_value.telegram.payload.value == test_payload

    def test_to_from_knx(self) -> None:
        """Test to_knx and from_knx raises when not set properly."""
        xknx = XKNX()
        remote_value = RemoteValue(xknx, group_address="1/1/1")
        with pytest.raises(NotImplementedError):
            remote_value.value = 3.3  # without to_knx method

        test_payload = DPTArray((0x01, 0x02))
        telegram = Telegram(
            destination_address=GroupAddress("1/1/1"),
            payload=GroupValueWrite(test_payload),
        )
        with pytest.raises(NotImplementedError):
            remote_value.process(telegram)

        # doesn't raise with `dpt_class` set
        remote_value.dpt_class = DPT2ByteFloat
        remote_value.value = 3.3
        remote_value.process(telegram)

    def test_pre_decoded_telegram(self) -> None:
        """Test if pre-decoded Telegram is processed."""
        xknx = XKNX()
        remote_value = RemoteValue(xknx, group_address="1/1/1")
        remote_value.dpt_class = DPT2ByteFloat

        test_payload = "invalid for testing"
        telegram = Telegram(
            destination_address=GroupAddress("1/1/1"),
            payload=GroupValueWrite(test_payload),
            decoded_data=TelegramDecodedData(transcoder=DPT2ByteFloat, value=3.3),
        )
        assert remote_value.process(telegram)
        assert remote_value.value == 3.3

    def test_eq(self) -> None:
        """Test __eq__ operator."""
        xknx = XKNX()
        remote_value1 = RemoteValue(xknx, group_address=GroupAddress("1/1/1"))
        remote_value2 = RemoteValue(xknx, group_address=GroupAddress("1/1/1"))
        remote_value3 = RemoteValue(xknx, group_address=GroupAddress("1/1/2"))
        remote_value4 = RemoteValue(xknx, group_address=GroupAddress("1/1/1"))
        remote_value4.fnord = "fnord"

        def _callback() -> None:
            pass

        remote_value5 = RemoteValue(
            xknx, group_address=GroupAddress("1/1/1"), after_update_cb=_callback()
        )

        assert remote_value1 == remote_value2
        assert remote_value2 == remote_value1
        assert remote_value1 != remote_value3
        assert remote_value3 != remote_value1
        assert remote_value1 != remote_value4
        assert remote_value4 != remote_value1
        assert remote_value1 == remote_value5
        assert remote_value5 == remote_value1