]> git.basschouten.com Git - openhab-addons.git/blob
a72ab74517be60eb39be33384f0055fd640df231
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.homeassistant.internal.handler;
14
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;
24
25 import java.nio.charset.StandardCharsets;
26 import java.util.Arrays;
27 import java.util.List;
28 import java.util.stream.Collectors;
29
30 import org.hamcrest.CoreMatchers;
31 import org.junit.jupiter.api.BeforeEach;
32 import org.junit.jupiter.api.Test;
33 import org.junit.jupiter.api.extension.ExtendWith;
34 import org.mockito.Mock;
35 import org.mockito.junit.jupiter.MockitoExtension;
36 import org.openhab.binding.mqtt.homeassistant.internal.AbstractHomeAssistantTests;
37 import org.openhab.binding.mqtt.homeassistant.internal.HaID;
38 import org.openhab.binding.mqtt.homeassistant.internal.HandlerConfiguration;
39 import org.openhab.binding.mqtt.homeassistant.internal.component.Climate;
40 import org.openhab.binding.mqtt.homeassistant.internal.component.Switch;
41 import org.openhab.core.thing.binding.ThingHandlerCallback;
42
43 /**
44  * Tests for {@link HomeAssistantThingHandler}
45  *
46  * @author Anton Kharuzhy - Initial contribution
47  */
48 @SuppressWarnings({ "ConstantConditions" })
49 @ExtendWith(MockitoExtension.class)
50 public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests {
51     private static final int SUBSCRIBE_TIMEOUT = 10000;
52     private static final int ATTRIBUTE_RECEIVE_TIMEOUT = 2000;
53
54     private static final List<String> CONFIG_TOPICS = Arrays.asList("climate/0x847127fffe11dd6a_climate_zigbee2mqtt",
55             "switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt",
56
57             "sensor/0x1111111111111111_test_sensor_zigbee2mqtt", "camera/0x1111111111111111_test_camera_zigbee2mqtt",
58
59             "cover/0x2222222222222222_test_cover_zigbee2mqtt", "fan/0x2222222222222222_test_fan_zigbee2mqtt",
60             "light/0x2222222222222222_test_light_zigbee2mqtt", "lock/0x2222222222222222_test_lock_zigbee2mqtt");
61
62     private static final List<String> MQTT_TOPICS = CONFIG_TOPICS.stream()
63             .map(AbstractHomeAssistantTests::configTopicToMqtt).collect(Collectors.toList());
64
65     private @Mock ThingHandlerCallback callback;
66     private HomeAssistantThingHandler thingHandler;
67
68     @BeforeEach
69     public void setup() {
70         final var config = haThing.getConfiguration();
71
72         config.put(HandlerConfiguration.PROPERTY_BASETOPIC, HandlerConfiguration.DEFAULT_BASETOPIC);
73         config.put(HandlerConfiguration.PROPERTY_TOPICS, CONFIG_TOPICS);
74
75         when(callback.getBridge(eq(BRIDGE_UID))).thenReturn(bridgeThing);
76
77         thingHandler = new HomeAssistantThingHandler(haThing, channelTypeProvider, transformationServiceProvider,
78                 SUBSCRIBE_TIMEOUT, ATTRIBUTE_RECEIVE_TIMEOUT);
79         thingHandler.setConnection(bridgeConnection);
80         thingHandler.setCallback(callback);
81         thingHandler = spy(thingHandler);
82     }
83
84     @Test
85     public void testInitialize() {
86         // When initialize
87         thingHandler.initialize();
88
89         verify(callback).statusUpdated(eq(haThing), any());
90         // Expect a call to the bridge status changed, the start, the propertiesChanged method
91         verify(thingHandler).bridgeStatusChanged(any());
92         verify(thingHandler, timeout(SUBSCRIBE_TIMEOUT)).start(any());
93
94         // Expect subscription on each topic from config
95         MQTT_TOPICS.forEach(t -> {
96             verify(bridgeConnection, timeout(SUBSCRIBE_TIMEOUT)).subscribe(eq(t), any());
97         });
98
99         verify(thingHandler, never()).componentDiscovered(any(), any());
100         assertThat(haThing.getChannels().size(), CoreMatchers.is(0));
101         // Components discovered after messages in corresponding topics
102         var configTopic = "homeassistant/climate/0x847127fffe11dd6a_climate_zigbee2mqtt/config";
103         thingHandler.discoverComponents.processMessage(configTopic,
104                 getResourceAsByteArray("component/configTS0601ClimateThermostat.json"));
105         verify(thingHandler, times(1)).componentDiscovered(eq(new HaID(configTopic)), any(Climate.class));
106
107         thingHandler.delayedProcessing.forceProcessNow();
108         assertThat(haThing.getChannels().size(), CoreMatchers.is(6));
109         verify(channelTypeProvider, times(6)).setChannelType(any(), any());
110         verify(channelTypeProvider, times(1)).setChannelGroupType(any(), any());
111
112         configTopic = "homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config";
113         thingHandler.discoverComponents.processMessage(configTopic,
114                 getResourceAsByteArray("component/configTS0601AutoLock.json"));
115         verify(thingHandler, times(2)).componentDiscovered(any(), any());
116         verify(thingHandler, times(1)).componentDiscovered(eq(new HaID(configTopic)), any(Switch.class));
117
118         thingHandler.delayedProcessing.forceProcessNow();
119         assertThat(haThing.getChannels().size(), CoreMatchers.is(7));
120         verify(channelTypeProvider, times(7)).setChannelType(any(), any());
121         verify(channelTypeProvider, times(2)).setChannelGroupType(any(), any());
122     }
123
124     @Test
125     public void testDispose() {
126         thingHandler.initialize();
127
128         // Expect subscription on each topic from config
129         CONFIG_TOPICS.forEach(t -> {
130             var fullTopic = HandlerConfiguration.DEFAULT_BASETOPIC + "/" + t + "/config";
131             verify(bridgeConnection, timeout(SUBSCRIBE_TIMEOUT)).subscribe(eq(fullTopic), any());
132         });
133         thingHandler.discoverComponents.processMessage(
134                 "homeassistant/climate/0x847127fffe11dd6a_climate_zigbee2mqtt/config",
135                 getResourceAsByteArray("component/configTS0601ClimateThermostat.json"));
136         thingHandler.discoverComponents.processMessage(
137                 "homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config",
138                 getResourceAsByteArray("component/configTS0601AutoLock.json"));
139         thingHandler.delayedProcessing.forceProcessNow();
140         assertThat(haThing.getChannels().size(), CoreMatchers.is(7));
141         verify(channelTypeProvider, times(7)).setChannelType(any(), any());
142
143         // When dispose
144         thingHandler.dispose();
145
146         // Expect unsubscription on each topic from config
147         MQTT_TOPICS.forEach(t -> {
148             verify(bridgeConnection, timeout(SUBSCRIBE_TIMEOUT)).unsubscribe(eq(t), any());
149         });
150
151         // Expect channel types removed, 6 for climate and 1 for switch
152         verify(channelTypeProvider, times(7)).removeChannelType(any());
153         // Expect channel group types removed, 1 for each component
154         verify(channelTypeProvider, times(2)).removeChannelGroupType(any());
155     }
156
157     @Test
158     public void testProcessMessageFromUnsupportedComponent() {
159         thingHandler.initialize();
160         thingHandler.discoverComponents.processMessage("homeassistant/unsupportedType/id_zigbee2mqtt/config",
161                 "{}".getBytes(StandardCharsets.UTF_8));
162         // Ignore unsupported component
163         thingHandler.delayedProcessing.forceProcessNow();
164         assertThat(haThing.getChannels().size(), CoreMatchers.is(0));
165     }
166
167     @Test
168     public void testProcessMessageWithEmptyConfig() {
169         thingHandler.initialize();
170         thingHandler.discoverComponents.processMessage("homeassistant/sensor/id_zigbee2mqtt/config",
171                 "".getBytes(StandardCharsets.UTF_8));
172         // Ignore component with empty config
173         thingHandler.delayedProcessing.forceProcessNow();
174         assertThat(haThing.getChannels().size(), CoreMatchers.is(0));
175     }
176
177     @Test
178     public void testProcessMessageWithBadFormatConfig() {
179         thingHandler.initialize();
180         thingHandler.discoverComponents.processMessage("homeassistant/sensor/id_zigbee2mqtt/config",
181                 "{bad format}}".getBytes(StandardCharsets.UTF_8));
182         // Ignore component with bad format config
183         thingHandler.delayedProcessing.forceProcessNow();
184         assertThat(haThing.getChannels().size(), CoreMatchers.is(0));
185     }
186 }