2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.mqtt.generic.internal.handler;
15 import static org.hamcrest.CoreMatchers.is;
16 import static org.hamcrest.MatcherAssert.assertThat;
17 import static org.junit.jupiter.api.Assertions.assertThrows;
18 import static org.mockito.ArgumentMatchers.*;
19 import static org.mockito.Mockito.*;
20 import static org.openhab.binding.mqtt.generic.internal.handler.ThingChannelConstants.*;
22 import java.util.concurrent.CompletableFuture;
24 import org.junit.jupiter.api.BeforeEach;
25 import org.junit.jupiter.api.Test;
26 import org.junit.jupiter.api.extension.ExtendWith;
27 import org.mockito.Mock;
28 import org.mockito.junit.jupiter.MockitoExtension;
29 import org.mockito.junit.jupiter.MockitoSettings;
30 import org.mockito.quality.Strictness;
31 import org.openhab.binding.mqtt.generic.ChannelConfig;
32 import org.openhab.binding.mqtt.generic.ChannelConfigBuilder;
33 import org.openhab.binding.mqtt.generic.ChannelState;
34 import org.openhab.binding.mqtt.generic.MqttChannelStateDescriptionProvider;
35 import org.openhab.binding.mqtt.generic.ThingHandlerHelper;
36 import org.openhab.binding.mqtt.generic.TransformationServiceProvider;
37 import org.openhab.binding.mqtt.generic.values.OnOffValue;
38 import org.openhab.binding.mqtt.generic.values.TextValue;
39 import org.openhab.binding.mqtt.generic.values.ValueFactory;
40 import org.openhab.binding.mqtt.handler.AbstractBrokerHandler;
41 import org.openhab.core.config.core.Configuration;
42 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
43 import org.openhab.core.library.types.OnOffType;
44 import org.openhab.core.library.types.StringType;
45 import org.openhab.core.thing.ChannelUID;
46 import org.openhab.core.thing.Thing;
47 import org.openhab.core.thing.ThingStatus;
48 import org.openhab.core.thing.ThingStatusDetail;
49 import org.openhab.core.thing.ThingStatusInfo;
50 import org.openhab.core.thing.binding.ThingHandlerCallback;
51 import org.openhab.core.types.RefreshType;
54 * Tests cases for {@link GenericMQTTThingHandler}.
56 * @author David Graeff - Initial contribution
58 @ExtendWith(MockitoExtension.class)
59 @MockitoSettings(strictness = Strictness.WARN)
60 public class GenericThingHandlerTests {
62 private @Mock ThingHandlerCallback callback;
63 private @Mock Thing thing;
64 private @Mock AbstractBrokerHandler bridgeHandler;
65 private @Mock MqttBrokerConnection connection;
67 private GenericMQTTThingHandler thingHandler;
71 ThingStatusInfo thingStatus = new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
73 // Mock the thing: We need the thingUID and the bridgeUID
74 when(thing.getUID()).thenReturn(testGenericThing);
75 when(thing.getChannels()).thenReturn(thingChannelList);
76 when(thing.getStatusInfo()).thenReturn(thingStatus);
77 when(thing.getConfiguration()).thenReturn(new Configuration());
79 // Return the mocked connection object if the bridge handler is asked for it
80 when(bridgeHandler.getConnectionAsync()).thenReturn(CompletableFuture.completedFuture(connection));
82 CompletableFuture<Void> voidFutureComplete = new CompletableFuture<>();
83 voidFutureComplete.complete(null);
84 doReturn(voidFutureComplete).when(connection).unsubscribeAll();
85 doReturn(CompletableFuture.completedFuture(true)).when(connection).subscribe(any(), any());
86 doReturn(CompletableFuture.completedFuture(true)).when(connection).unsubscribe(any(), any());
87 doReturn(CompletableFuture.completedFuture(true)).when(connection).publish(any(), any());
88 doReturn(CompletableFuture.completedFuture(true)).when(connection).publish(any(), any(), anyInt(),
91 thingHandler = spy(new GenericMQTTThingHandler(thing, mock(MqttChannelStateDescriptionProvider.class),
92 mock(TransformationServiceProvider.class), 1500));
93 thingHandler.setCallback(callback);
95 // Return the bridge handler if the thing handler asks for it
96 doReturn(bridgeHandler).when(thingHandler).getBridgeHandler();
98 // The broker connection bridge is by default online
99 doReturn(thingStatus).when(thingHandler).getBridgeStatus();
103 public void initializeWithUnknownThingUID() {
104 ChannelConfig config = textConfiguration().as(ChannelConfig.class);
105 assertThrows(IllegalArgumentException.class,
106 () -> thingHandler.createChannelState(config, new ChannelUID(testGenericThing, "test"),
107 ValueFactory.createValueState(config, unknownChannel.getId())));
111 public void initialize() {
112 thingHandler.initialize();
113 verify(thingHandler).bridgeStatusChanged(any());
114 verify(thingHandler).start(any());
115 assertThat(thingHandler.getConnection(), is(connection));
117 ChannelState channelConfig = thingHandler.channelStateByChannelUID.get(textChannelUID);
118 assertThat(channelConfig.getStateTopic(), is("test/state"));
119 assertThat(channelConfig.getCommandTopic(), is("test/command"));
121 verify(connection).subscribe(eq(channelConfig.getStateTopic()), eq(channelConfig));
123 verify(callback).statusUpdated(eq(thing), argThat((arg) -> arg.getStatus().equals(ThingStatus.ONLINE)
124 && arg.getStatusDetail().equals(ThingStatusDetail.NONE)));
128 public void handleCommandRefresh() {
129 TextValue value = spy(new TextValue());
130 value.update(new StringType("DEMOVALUE"));
132 ChannelState channelConfig = mock(ChannelState.class);
133 doReturn(CompletableFuture.completedFuture(true)).when(channelConfig).start(any(), any(), anyInt());
134 doReturn(CompletableFuture.completedFuture(true)).when(channelConfig).stop();
135 doReturn(value).when(channelConfig).getCache();
136 doReturn(channelConfig).when(thingHandler).createChannelState(any(), any(), any());
137 thingHandler.initialize();
139 ThingHandlerHelper.setConnection(thingHandler, connection);
141 thingHandler.handleCommand(textChannelUID, RefreshType.REFRESH);
142 verify(callback).stateUpdated(eq(textChannelUID), argThat(arg -> "DEMOVALUE".equals(arg.toString())));
146 public void handleCommandUpdateString() {
147 TextValue value = spy(new TextValue());
148 ChannelState channelConfig = spy(
149 new ChannelState(ChannelConfigBuilder.create("stateTopic", "commandTopic").build(), textChannelUID,
150 value, thingHandler));
151 doReturn(channelConfig).when(thingHandler).createChannelState(any(), any(), any());
152 thingHandler.initialize();
153 ThingHandlerHelper.setConnection(thingHandler, connection);
155 StringType updateValue = new StringType("UPDATE");
156 thingHandler.handleCommand(textChannelUID, updateValue);
157 verify(value).update(eq(updateValue));
158 assertThat(channelConfig.getCache().getChannelState().toString(), is("UPDATE"));
162 public void handleCommandUpdateBoolean() {
163 OnOffValue value = spy(new OnOffValue("ON", "OFF"));
164 ChannelState channelConfig = spy(
165 new ChannelState(ChannelConfigBuilder.create("stateTopic", "commandTopic").build(), textChannelUID,
166 value, thingHandler));
167 doReturn(channelConfig).when(thingHandler).createChannelState(any(), any(), any());
168 thingHandler.initialize();
169 ThingHandlerHelper.setConnection(thingHandler, connection);
171 StringType updateValue = new StringType("ON");
172 thingHandler.handleCommand(textChannelUID, updateValue);
174 verify(value).update(eq(updateValue));
175 assertThat(channelConfig.getCache().getChannelState(), is(OnOffType.ON));
179 public void processMessage() {
180 TextValue textValue = new TextValue();
181 ChannelState channelConfig = spy(
182 new ChannelState(ChannelConfigBuilder.create("test/state", "test/state/set").build(), textChannelUID,
183 textValue, thingHandler));
184 doReturn(channelConfig).when(thingHandler).createChannelState(any(), any(), any());
185 thingHandler.initialize();
186 byte payload[] = "UPDATE".getBytes();
187 // Test process message
188 channelConfig.processMessage("test/state", payload);
190 verify(callback, atLeastOnce()).statusUpdated(eq(thing),
191 argThat(arg -> arg.getStatus().equals(ThingStatus.ONLINE)));
193 verify(callback).stateUpdated(eq(textChannelUID), argThat(arg -> "UPDATE".equals(arg.toString())));
194 assertThat(textValue.getChannelState().toString(), is("UPDATE"));