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