2 * Copyright (c) 2010-2022 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(), anyInt(),
90 thingHandler = spy(new GenericMQTTThingHandler(thing, mock(MqttChannelStateDescriptionProvider.class),
91 mock(TransformationServiceProvider.class), 1500));
92 thingHandler.setCallback(callback);
94 // Return the bridge handler if the thing handler asks for it
95 doReturn(bridgeHandler).when(thingHandler).getBridgeHandler();
97 // The broker connection bridge is by default online
98 doReturn(thingStatus).when(thingHandler).getBridgeStatus();
102 public void initializeWithUnknownThingUID() {
103 ChannelConfig config = textConfiguration().as(ChannelConfig.class);
104 assertThrows(IllegalArgumentException.class,
105 () -> thingHandler.createChannelState(config, new ChannelUID(testGenericThing, "test"),
106 ValueFactory.createValueState(config, unknownChannel.getId())));
110 public void initialize() {
111 thingHandler.initialize();
112 verify(thingHandler).bridgeStatusChanged(any());
113 verify(thingHandler).start(any());
114 assertThat(thingHandler.getConnection(), is(connection));
116 ChannelState channelConfig = thingHandler.channelStateByChannelUID.get(textChannelUID);
117 assertThat(channelConfig.getStateTopic(), is("test/state"));
118 assertThat(channelConfig.getCommandTopic(), is("test/command"));
120 verify(connection).subscribe(eq(channelConfig.getStateTopic()), eq(channelConfig));
122 verify(callback).statusUpdated(eq(thing), argThat((arg) -> arg.getStatus().equals(ThingStatus.ONLINE)
123 && arg.getStatusDetail().equals(ThingStatusDetail.NONE)));
127 public void handleCommandRefresh() {
128 TextValue value = spy(new TextValue());
129 value.update(new StringType("DEMOVALUE"));
131 ChannelState channelConfig = mock(ChannelState.class);
132 doReturn(CompletableFuture.completedFuture(true)).when(channelConfig).start(any(), any(), anyInt());
133 doReturn(CompletableFuture.completedFuture(true)).when(channelConfig).stop();
134 doReturn(value).when(channelConfig).getCache();
135 doReturn(channelConfig).when(thingHandler).createChannelState(any(), any(), any());
136 thingHandler.initialize();
138 ThingHandlerHelper.setConnection(thingHandler, connection);
140 thingHandler.handleCommand(textChannelUID, RefreshType.REFRESH);
141 verify(callback).stateUpdated(eq(textChannelUID), argThat(arg -> "DEMOVALUE".equals(arg.toString())));
145 public void handleCommandUpdateString() {
146 TextValue value = spy(new TextValue());
147 ChannelState channelConfig = spy(
148 new ChannelState(ChannelConfigBuilder.create("stateTopic", "commandTopic").build(), textChannelUID,
149 value, thingHandler));
150 doReturn(channelConfig).when(thingHandler).createChannelState(any(), any(), any());
151 thingHandler.initialize();
152 ThingHandlerHelper.setConnection(thingHandler, connection);
154 StringType updateValue = new StringType("UPDATE");
155 thingHandler.handleCommand(textChannelUID, updateValue);
156 verify(value).update(eq(updateValue));
157 assertThat(channelConfig.getCache().getChannelState().toString(), is("UPDATE"));
161 public void handleCommandUpdateBoolean() {
162 OnOffValue value = spy(new OnOffValue("ON", "OFF"));
163 ChannelState channelConfig = spy(
164 new ChannelState(ChannelConfigBuilder.create("stateTopic", "commandTopic").build(), textChannelUID,
165 value, thingHandler));
166 doReturn(channelConfig).when(thingHandler).createChannelState(any(), any(), any());
167 thingHandler.initialize();
168 ThingHandlerHelper.setConnection(thingHandler, connection);
170 StringType updateValue = new StringType("ON");
171 thingHandler.handleCommand(textChannelUID, updateValue);
173 verify(value).update(eq(updateValue));
174 assertThat(channelConfig.getCache().getChannelState(), is(OnOffType.ON));
178 public void processMessage() {
179 TextValue textValue = new TextValue();
180 ChannelState channelConfig = spy(
181 new ChannelState(ChannelConfigBuilder.create("test/state", "test/state/set").build(), textChannelUID,
182 textValue, thingHandler));
183 doReturn(channelConfig).when(thingHandler).createChannelState(any(), any(), any());
184 thingHandler.initialize();
185 byte payload[] = "UPDATE".getBytes();
186 // Test process message
187 channelConfig.processMessage("test/state", payload);
189 verify(callback, atLeastOnce()).statusUpdated(eq(thing),
190 argThat(arg -> arg.getStatus().equals(ThingStatus.ONLINE)));
192 verify(callback).stateUpdated(eq(textChannelUID), argThat(arg -> "UPDATE".equals(arg.toString())));
193 assertThat(textValue.getChannelState().toString(), is("UPDATE"));
197 public void handleBridgeStatusChange() {
198 Configuration config = new Configuration();
199 config.put("availabilityTopic", "test/LWT");
200 when(thing.getConfiguration()).thenReturn(config);
201 thingHandler.initialize();
203 .bridgeStatusChanged(new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null));
204 thingHandler.bridgeStatusChanged(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null));
205 verify(connection, times(2)).subscribe(eq("test/LWT"), any());