]> git.basschouten.com Git - openhab-addons.git/blob
ed29f5980826be1bf9a3b664fd21891e5a0743e2
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.mqtt.generic;
14
15 import static org.hamcrest.CoreMatchers.*;
16 import static org.hamcrest.MatcherAssert.assertThat;
17 import static org.junit.jupiter.api.Assertions.assertTrue;
18 import static org.mockito.ArgumentMatchers.*;
19 import static org.mockito.ArgumentMatchers.any;
20 import static org.mockito.Mockito.*;
21
22 import java.math.BigDecimal;
23 import java.time.ZonedDateTime;
24 import java.time.format.DateTimeFormatter;
25 import java.util.Arrays;
26 import java.util.concurrent.CompletableFuture;
27 import java.util.concurrent.ScheduledExecutorService;
28 import java.util.concurrent.ScheduledThreadPoolExecutor;
29 import java.util.concurrent.TimeUnit;
30
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.junit.jupiter.api.AfterEach;
34 import org.junit.jupiter.api.BeforeEach;
35 import org.junit.jupiter.api.Test;
36 import org.junit.jupiter.api.extension.ExtendWith;
37 import org.mockito.Mock;
38 import org.mockito.Spy;
39 import org.mockito.junit.jupiter.MockitoExtension;
40 import org.mockito.junit.jupiter.MockitoSettings;
41 import org.mockito.quality.Strictness;
42 import org.openhab.binding.mqtt.generic.mapping.ColorMode;
43 import org.openhab.binding.mqtt.generic.values.ColorValue;
44 import org.openhab.binding.mqtt.generic.values.DateTimeValue;
45 import org.openhab.binding.mqtt.generic.values.ImageValue;
46 import org.openhab.binding.mqtt.generic.values.LocationValue;
47 import org.openhab.binding.mqtt.generic.values.NumberValue;
48 import org.openhab.binding.mqtt.generic.values.PercentageValue;
49 import org.openhab.binding.mqtt.generic.values.TextValue;
50 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
51 import org.openhab.core.library.types.HSBType;
52 import org.openhab.core.library.types.RawType;
53 import org.openhab.core.library.types.StringType;
54 import org.openhab.core.library.unit.Units;
55 import org.openhab.core.thing.ChannelUID;
56 import org.openhab.core.types.Command;
57
58 /**
59  * Tests the {@link ChannelState} class.
60  *
61  * @author David Graeff - Initial contribution
62  */
63 @ExtendWith(MockitoExtension.class)
64 @MockitoSettings(strictness = Strictness.LENIENT)
65 @NonNullByDefault
66 public class ChannelStateTests {
67
68     private @Mock @NonNullByDefault({}) MqttBrokerConnection connectionMock;
69     private @Mock @NonNullByDefault({}) ChannelStateUpdateListener channelStateUpdateListenerMock;
70     private @Mock @NonNullByDefault({}) ChannelUID channelUIDMock;
71     private @Spy @NonNullByDefault({}) TextValue textValue;
72
73     private @NonNullByDefault({}) ScheduledExecutorService scheduler;
74
75     private ChannelConfig config = ChannelConfigBuilder.create("state", "command").build();
76
77     @BeforeEach
78     public void setUp() {
79         CompletableFuture<@Nullable Void> voidFutureComplete = new CompletableFuture<>();
80         voidFutureComplete.complete(null);
81         doReturn(voidFutureComplete).when(connectionMock).unsubscribeAll();
82         doReturn(CompletableFuture.completedFuture(true)).when(connectionMock).subscribe(any(), any());
83         doReturn(CompletableFuture.completedFuture(true)).when(connectionMock).unsubscribe(any(), any());
84         doReturn(CompletableFuture.completedFuture(true)).when(connectionMock).publish(any(), any(), anyInt(),
85                 anyBoolean());
86
87         scheduler = new ScheduledThreadPoolExecutor(1);
88     }
89
90     @AfterEach
91     public void tearDown() {
92         scheduler.shutdownNow();
93     }
94
95     @Test
96     public void noInteractionTimeoutTest() throws Exception {
97         ChannelState c = spy(new ChannelState(config, channelUIDMock, textValue, channelStateUpdateListenerMock));
98         c.start(connectionMock, scheduler, 50).get(100, TimeUnit.MILLISECONDS);
99         verify(connectionMock).subscribe(eq("state"), eq(c));
100         c.stop().get();
101         verify(connectionMock).unsubscribe(eq("state"), eq(c));
102     }
103
104     @Test
105     public void publishFormatTest() throws Exception {
106         ChannelState c = spy(new ChannelState(config, channelUIDMock, textValue, channelStateUpdateListenerMock));
107
108         c.start(connectionMock, scheduler, 0).get(50, TimeUnit.MILLISECONDS);
109         verify(connectionMock).subscribe(eq("state"), eq(c));
110
111         c.publishValue(new StringType("UPDATE")).get();
112         verify(connectionMock).publish(eq("command"), argThat(p -> Arrays.equals(p, "UPDATE".getBytes())), anyInt(),
113                 eq(false));
114
115         c.config.formatBeforePublish = "prefix%s";
116         c.publishValue(new StringType("UPDATE")).get();
117         verify(connectionMock).publish(eq("command"), argThat(p -> Arrays.equals(p, "prefixUPDATE".getBytes())),
118                 anyInt(), eq(false));
119
120         c.config.formatBeforePublish = "%1$s-%1$s";
121         c.publishValue(new StringType("UPDATE")).get();
122         verify(connectionMock).publish(eq("command"), argThat(p -> Arrays.equals(p, "UPDATE-UPDATE".getBytes())),
123                 anyInt(), eq(false));
124
125         c.config.formatBeforePublish = "%s";
126         c.config.retained = true;
127         c.publishValue(new StringType("UPDATE")).get();
128         verify(connectionMock).publish(eq("command"), any(), anyInt(), eq(true));
129
130         c.stop().get();
131         verify(connectionMock).unsubscribe(eq("state"), eq(c));
132     }
133
134     @Test
135     public void receiveWildcardTest() throws Exception {
136         ChannelState c = spy(new ChannelState(ChannelConfigBuilder.create("state/+/topic", "command").build(),
137                 channelUIDMock, textValue, channelStateUpdateListenerMock));
138
139         CompletableFuture<@Nullable Void> future = c.start(connectionMock, scheduler, 100);
140         c.processMessage("state/bla/topic", "A TEST".getBytes());
141         future.get(300, TimeUnit.MILLISECONDS);
142
143         assertThat(textValue.getChannelState().toString(), is("A TEST"));
144         verify(channelStateUpdateListenerMock).updateChannelState(eq(channelUIDMock), any());
145     }
146
147     @Test
148     public void receiveStringTest() throws Exception {
149         ChannelState c = spy(new ChannelState(config, channelUIDMock, textValue, channelStateUpdateListenerMock));
150
151         CompletableFuture<@Nullable Void> future = c.start(connectionMock, scheduler, 100);
152         c.processMessage("state", "A TEST".getBytes());
153         future.get(300, TimeUnit.MILLISECONDS);
154
155         assertThat(textValue.getChannelState().toString(), is("A TEST"));
156         verify(channelStateUpdateListenerMock).updateChannelState(eq(channelUIDMock), any());
157     }
158
159     @Test
160     public void receiveDecimalTest() {
161         NumberValue value = new NumberValue(null, null, new BigDecimal(10), null);
162         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
163         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
164
165         c.processMessage("state", "15".getBytes());
166         assertThat(value.getChannelState().toString(), is("15"));
167
168         c.processMessage("state", "INCREASE".getBytes());
169         assertThat(value.getChannelState().toString(), is("25"));
170
171         c.processMessage("state", "DECREASE".getBytes());
172         assertThat(value.getChannelState().toString(), is("15"));
173
174         verify(channelStateUpdateListenerMock, times(3)).updateChannelState(eq(channelUIDMock), any());
175     }
176
177     @Test
178     public void receiveDecimalFractionalTest() {
179         NumberValue value = new NumberValue(null, null, new BigDecimal(10.5), null);
180         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
181         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
182
183         c.processMessage("state", "5.5".getBytes());
184         assertThat(value.getChannelState().toString(), is("5.5"));
185
186         c.processMessage("state", "INCREASE".getBytes());
187         assertThat(value.getChannelState().toString(), is("16.0"));
188     }
189
190     @Test
191     public void receiveDecimalUnitTest() {
192         NumberValue value = new NumberValue(null, null, new BigDecimal(10), Units.WATT);
193         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
194         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
195
196         c.processMessage("state", "15".getBytes());
197         assertThat(value.getChannelState().toString(), is("15 W"));
198
199         c.processMessage("state", "INCREASE".getBytes());
200         assertThat(value.getChannelState().toString(), is("25 W"));
201
202         c.processMessage("state", "DECREASE".getBytes());
203         assertThat(value.getChannelState().toString(), is("15 W"));
204
205         verify(channelStateUpdateListenerMock, times(3)).updateChannelState(eq(channelUIDMock), any());
206     }
207
208     @Test
209     public void receiveDecimalAsPercentageUnitTest() {
210         NumberValue value = new NumberValue(null, null, new BigDecimal(10), Units.PERCENT);
211         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
212         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
213
214         c.processMessage("state", "63.7".getBytes());
215         assertThat(value.getChannelState().toString(), is("63.7 %"));
216
217         verify(channelStateUpdateListenerMock, times(1)).updateChannelState(eq(channelUIDMock), any());
218     }
219
220     @Test
221     public void receivePercentageTest() {
222         PercentageValue value = new PercentageValue(new BigDecimal(-100), new BigDecimal(100), new BigDecimal(10), null,
223                 null);
224         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
225         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
226
227         c.processMessage("state", "-100".getBytes()); // 0%
228         assertThat(value.getChannelState().toString(), is("0"));
229
230         c.processMessage("state", "100".getBytes()); // 100%
231         assertThat(value.getChannelState().toString(), is("100"));
232
233         c.processMessage("state", "0".getBytes()); // 50%
234         assertThat(value.getChannelState().toString(), is("50"));
235
236         c.processMessage("state", "INCREASE".getBytes());
237         assertThat(value.getChannelState().toString(), is("55"));
238         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), null), is("10"));
239         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), "%03.0f"), is("010"));
240     }
241
242     @Test
243     public void receiveRGBColorTest() {
244         ColorValue value = new ColorValue(ColorMode.RGB, "FON", "FOFF", 10);
245         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
246         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
247
248         c.processMessage("state", "ON".getBytes()); // Normal on state
249         assertThat(value.getChannelState().toString(), is("0,0,10"));
250         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), null), is("25,25,25"));
251
252         c.processMessage("state", "FOFF".getBytes()); // Custom off state
253         assertThat(value.getChannelState().toString(), is("0,0,0"));
254         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), null), is("0,0,0"));
255
256         c.processMessage("state", "10".getBytes()); // Brightness only
257         assertThat(value.getChannelState().toString(), is("0,0,10"));
258         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), null), is("25,25,25"));
259
260         HSBType t = HSBType.fromRGB(12, 18, 231);
261
262         c.processMessage("state", "12,18,231".getBytes());
263         assertThat(value.getChannelState(), is(t)); // HSB
264         // rgb -> hsv -> rgb is quite lossy
265         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), null), is("13,20,229"));
266         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), "%3$d,%2$d,%1$d"), is("229,20,13"));
267     }
268
269     @Test
270     public void receiveHSBColorTest() {
271         ColorValue value = new ColorValue(ColorMode.HSB, "FON", "FOFF", 10);
272         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
273         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
274
275         c.processMessage("state", "ON".getBytes()); // Normal on state
276         assertThat(value.getChannelState().toString(), is("0,0,10"));
277         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), null), is("0,0,10"));
278
279         c.processMessage("state", "FOFF".getBytes()); // Custom off state
280         assertThat(value.getChannelState().toString(), is("0,0,0"));
281         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), null), is("0,0,0"));
282
283         c.processMessage("state", "10".getBytes()); // Brightness only
284         assertThat(value.getChannelState().toString(), is("0,0,10"));
285         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), null), is("0,0,10"));
286
287         c.processMessage("state", "12,18,100".getBytes());
288         assertThat(value.getChannelState().toString(), is("12,18,100"));
289         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), null), is("12,18,100"));
290     }
291
292     @Test
293     public void receiveXYYColorTest() {
294         ColorValue value = new ColorValue(ColorMode.XYY, "FON", "FOFF", 10);
295         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
296         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
297
298         c.processMessage("state", "ON".getBytes()); // Normal on state
299         assertThat(value.getChannelState().toString(), is("0,0,10"));
300         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), null), is("0.312716,0.329002,10.00"));
301
302         c.processMessage("state", "FOFF".getBytes()); // Custom off state
303         assertThat(value.getChannelState().toString(), is("0,0,0"));
304         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), null), is("0.312716,0.329002,0.00"));
305
306         c.processMessage("state", "10".getBytes()); // Brightness only
307         assertThat(value.getChannelState().toString(), is("0,0,10"));
308         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), null), is("0.312716,0.329002,10.00"));
309
310         HSBType t = HSBType.fromXY(0.3f, 0.6f);
311
312         c.processMessage("state", "0.3,0.6,100".getBytes());
313         assertThat(value.getChannelState(), is(t)); // HSB
314         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), null), is("0.300000,0.600000,100.00"));
315         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), "%3$.1f,%2$.4f,%1$.4f"),
316                 is("100.0,0.6000,0.3000"));
317     }
318
319     @Test
320     public void receiveLocationTest() {
321         LocationValue value = new LocationValue();
322         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
323         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
324
325         c.processMessage("state", "46.833974, 7.108433".getBytes());
326         assertThat(value.getChannelState().toString(), is("46.833974,7.108433"));
327         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), null), is("46.833974,7.108433"));
328     }
329
330     @Test
331     public void receiveDateTimeTest() {
332         DateTimeValue value = new DateTimeValue();
333         ChannelState subject = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
334         subject.start(connectionMock, mock(ScheduledExecutorService.class), 100);
335
336         ZonedDateTime zd = ZonedDateTime.now();
337         String datetime = zd.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
338
339         subject.processMessage("state", datetime.getBytes());
340
341         String channelState = value.getChannelState().toString();
342         assertTrue(channelState.startsWith(datetime),
343                 "Expected '" + channelState + "' to start with '" + datetime + "'");
344         assertThat(value.getMQTTpublishValue((Command) value.getChannelState(), null), is(datetime));
345     }
346
347     @Test
348     public void receiveImageTest() {
349         ImageValue value = new ImageValue();
350         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
351         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
352
353         byte[] payload = { (byte) 0xFF, (byte) 0xD8, 0x01, 0x02, (byte) 0xFF, (byte) 0xD9 };
354         c.processMessage("state", payload);
355         assertThat(value.getChannelState(), is(instanceOf(RawType.class)));
356         assertThat(((RawType) value.getChannelState()).getMimeType(), is("image/jpeg"));
357     }
358 }