2 * Copyright (c) 2010-2021 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.homeassistant.internal.handler;
15 import static org.hamcrest.MatcherAssert.assertThat;
16 import static org.mockito.Mockito.any;
17 import static org.mockito.Mockito.eq;
18 import static org.mockito.Mockito.never;
19 import static org.mockito.Mockito.spy;
20 import static org.mockito.Mockito.timeout;
21 import static org.mockito.Mockito.times;
22 import static org.mockito.Mockito.verify;
23 import static org.mockito.Mockito.when;
25 import java.util.Arrays;
26 import java.util.List;
27 import java.util.stream.Collectors;
29 import org.hamcrest.CoreMatchers;
30 import org.junit.jupiter.api.BeforeEach;
31 import org.junit.jupiter.api.Test;
32 import org.junit.jupiter.api.extension.ExtendWith;
33 import org.mockito.Mock;
34 import org.mockito.junit.jupiter.MockitoExtension;
35 import org.openhab.binding.mqtt.homeassistant.internal.AbstractHomeAssistantTests;
36 import org.openhab.binding.mqtt.homeassistant.internal.HaID;
37 import org.openhab.binding.mqtt.homeassistant.internal.HandlerConfiguration;
38 import org.openhab.binding.mqtt.homeassistant.internal.component.Climate;
39 import org.openhab.binding.mqtt.homeassistant.internal.component.Switch;
40 import org.openhab.core.thing.binding.ThingHandlerCallback;
43 * Tests for {@link HomeAssistantThingHandler}
45 * @author Anton Kharuzhy - Initial contribution
47 @SuppressWarnings({ "ConstantConditions" })
48 @ExtendWith(MockitoExtension.class)
49 public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests {
50 private final static int SUBSCRIBE_TIMEOUT = 10000;
51 private final static int ATTRIBUTE_RECEIVE_TIMEOUT = 2000;
53 private static final List<String> CONFIG_TOPICS = Arrays.asList("climate/0x847127fffe11dd6a_climate_zigbee2mqtt",
54 "switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt",
56 "sensor/0x1111111111111111_test_sensor_zigbee2mqtt", "camera/0x1111111111111111_test_camera_zigbee2mqtt",
58 "cover/0x2222222222222222_test_cover_zigbee2mqtt", "fan/0x2222222222222222_test_fan_zigbee2mqtt",
59 "light/0x2222222222222222_test_light_zigbee2mqtt", "lock/0x2222222222222222_test_lock_zigbee2mqtt");
61 private static final List<String> MQTT_TOPICS = CONFIG_TOPICS.stream()
62 .map(AbstractHomeAssistantTests::configTopicToMqtt).collect(Collectors.toList());
64 private @Mock ThingHandlerCallback callback;
65 private HomeAssistantThingHandler thingHandler;
69 final var config = haThing.getConfiguration();
71 config.put(HandlerConfiguration.PROPERTY_BASETOPIC, HandlerConfiguration.DEFAULT_BASETOPIC);
72 config.put(HandlerConfiguration.PROPERTY_TOPICS, CONFIG_TOPICS);
74 when(callback.getBridge(eq(BRIDGE_UID))).thenReturn(bridgeThing);
76 thingHandler = new HomeAssistantThingHandler(haThing, channelTypeProvider, transformationServiceProvider,
77 SUBSCRIBE_TIMEOUT, ATTRIBUTE_RECEIVE_TIMEOUT);
78 thingHandler.setConnection(bridgeConnection);
79 thingHandler.setCallback(callback);
80 thingHandler = spy(thingHandler);
84 public void testInitialize() {
86 thingHandler.initialize();
88 verify(callback).statusUpdated(eq(haThing), any());
89 // Expect a call to the bridge status changed, the start, the propertiesChanged method
90 verify(thingHandler).bridgeStatusChanged(any());
91 verify(thingHandler, timeout(SUBSCRIBE_TIMEOUT)).start(any());
93 // Expect subscription on each topic from config
94 MQTT_TOPICS.forEach(t -> {
95 verify(bridgeConnection, timeout(SUBSCRIBE_TIMEOUT)).subscribe(eq(t), any());
98 verify(thingHandler, never()).componentDiscovered(any(), any());
99 assertThat(haThing.getChannels().size(), CoreMatchers.is(0));
100 // Components discovered after messages in corresponding topics
101 var configTopic = "homeassistant/climate/0x847127fffe11dd6a_climate_zigbee2mqtt/config";
102 thingHandler.discoverComponents.processMessage(configTopic,
103 getResourceAsByteArray("component/configTS0601ClimateThermostat.json"));
104 verify(thingHandler, times(1)).componentDiscovered(eq(new HaID(configTopic)), any(Climate.class));
106 thingHandler.delayedProcessing.forceProcessNow();
107 assertThat(haThing.getChannels().size(), CoreMatchers.is(6));
108 verify(channelTypeProvider, times(6)).setChannelType(any(), any());
109 verify(channelTypeProvider, times(1)).setChannelGroupType(any(), any());
111 configTopic = "homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config";
112 thingHandler.discoverComponents.processMessage(configTopic,
113 getResourceAsByteArray("component/configTS0601AutoLock.json"));
114 verify(thingHandler, times(2)).componentDiscovered(any(), any());
115 verify(thingHandler, times(1)).componentDiscovered(eq(new HaID(configTopic)), any(Switch.class));
117 thingHandler.delayedProcessing.forceProcessNow();
118 assertThat(haThing.getChannels().size(), CoreMatchers.is(7));
119 verify(channelTypeProvider, times(7)).setChannelType(any(), any());
120 verify(channelTypeProvider, times(2)).setChannelGroupType(any(), any());
124 public void testDispose() {
125 thingHandler.initialize();
127 // Expect subscription on each topic from config
128 CONFIG_TOPICS.forEach(t -> {
129 var fullTopic = HandlerConfiguration.DEFAULT_BASETOPIC + "/" + t + "/config";
130 verify(bridgeConnection, timeout(SUBSCRIBE_TIMEOUT)).subscribe(eq(fullTopic), any());
132 thingHandler.discoverComponents.processMessage(
133 "homeassistant/climate/0x847127fffe11dd6a_climate_zigbee2mqtt/config",
134 getResourceAsByteArray("component/configTS0601ClimateThermostat.json"));
135 thingHandler.discoverComponents.processMessage(
136 "homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config",
137 getResourceAsByteArray("component/configTS0601AutoLock.json"));
138 thingHandler.delayedProcessing.forceProcessNow();
139 assertThat(haThing.getChannels().size(), CoreMatchers.is(7));
140 verify(channelTypeProvider, times(7)).setChannelType(any(), any());
143 thingHandler.dispose();
145 // Expect unsubscription on each topic from config
146 MQTT_TOPICS.forEach(t -> {
147 verify(bridgeConnection, timeout(SUBSCRIBE_TIMEOUT)).unsubscribe(eq(t), any());
150 // Expect channel types removed, 6 for climate and 1 for switch
151 verify(channelTypeProvider, times(7)).removeChannelType(any());
152 // Expect channel group types removed, 1 for each component
153 verify(channelTypeProvider, times(2)).removeChannelGroupType(any());