]> git.basschouten.com Git - openhab-addons.git/blob
2ce1a5f0d528cc5f33c813c3b831e74aa4e9390c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.hue.internal.discovery;
14
15 import static org.hamcrest.CoreMatchers.*;
16 import static org.hamcrest.MatcherAssert.assertThat;
17 import static org.junit.jupiter.api.Assertions.assertTrue;
18 import static org.mockito.Mockito.mock;
19 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
20 import static org.openhab.core.config.discovery.inbox.InboxPredicates.forThingTypeUID;
21
22 import java.io.IOException;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.stream.Collectors;
28
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.junit.jupiter.api.BeforeEach;
32 import org.junit.jupiter.api.Test;
33 import org.openhab.core.config.discovery.DiscoveryListener;
34 import org.openhab.core.config.discovery.DiscoveryResult;
35 import org.openhab.core.config.discovery.DiscoveryService;
36 import org.openhab.core.config.discovery.inbox.Inbox;
37 import org.openhab.core.test.java.JavaOSGiTest;
38 import org.openhab.core.test.storage.VolatileStorageService;
39 import org.openhab.core.thing.ThingRegistry;
40 import org.openhab.core.thing.ThingTypeUID;
41 import org.openhab.core.thing.ThingUID;
42
43 /**
44  * @author Christoph Knauf - Initial contribution
45  * @author Markus Rathgeb - migrated to plain Java test
46  */
47 @NonNullByDefault
48 public class HueBridgeNupnpDiscoveryOSGITest extends JavaOSGiTest {
49
50     private @NonNullByDefault({}) HueBridgeNupnpDiscovery sut;
51     private VolatileStorageService volatileStorageService = new VolatileStorageService();
52     private @Nullable DiscoveryListener discoveryListener;
53     private @NonNullByDefault({}) Inbox inbox;
54
55     private static final ThingTypeUID BRIDGE_THING_TYPE_UID = new ThingTypeUID("hue", "bridge");
56     private static final String IP1 = "192.168.31.17";
57     private static final String IP2 = "192.168.30.28";
58     private static final String IP3 = "192.168.30.29";
59     private static final String SN1 = "001788fffe20057f";
60     private static final String SN2 = "001788fffe141b41";
61     private static final String SN3 = "001788fffe141b42";
62     private static final ThingUID BRIDGE_THING_UID_1 = new ThingUID(BRIDGE_THING_TYPE_UID, SN1);
63     private static final ThingUID BRIDGE_THING_UID_2 = new ThingUID(BRIDGE_THING_TYPE_UID, SN2);
64     private static final ThingUID BRIDGE_THING_UID_3 = new ThingUID(BRIDGE_THING_TYPE_UID, SN3);
65
66     private final String validBridgeDiscoveryResult = "[{\"id\":\"" + SN1 + "\",\"internalipaddress\":" + IP1
67             + "},{\"id\":\"" + SN2 + "\",\"internalipaddress\":" + IP2 + "},{\"id\":\"" + SN3
68             + "\",\"internalipaddress\":" + IP3 + "}]";
69     private @Nullable String discoveryResult;
70     private String expBridgeDescription1 = "{\"name\":\"Philips hue\",\"datastoreversion\":\"149\",\"swversion\":\"1957113050\",\"apiversion\":\"1.57.0\",\"mac\":\"00:11:22:33:44\",\"bridgeid\":\"$SN\",\"factorynew\":false,\"replacesbridgeid\":null,\"modelid\":\"BSB002\",\"starterkitid\":\"\"}";
71     private String expBridgeDescription2 = "{\"name\":\"Hue Bridge\",\"datastoreversion\":\"161\",\"swversion\":\"1959194040\",\"apiversion\":\"1.59.0\",\"mac\":\"00:11:22:33:44\",\"bridgeid\":\"$SN\",\"factorynew\":false,\"replacesbridgeid\":null,\"modelid\":\"BSB002\",\"starterkitid\":\"\"}";
72
73     private void checkDiscoveryResult(@Nullable DiscoveryResult result, String expIp, String expSn) {
74         if (result == null) {
75             return;
76         }
77         assertThat(result.getBridgeUID(), nullValue());
78         assertThat(result.getLabel(), is(String.format(DISCOVERY_LABEL_PATTERN, expIp)));
79         assertThat(result.getProperties().get("ipAddress"), is(expIp));
80         assertThat(result.getProperties().get("serialNumber"), is(expSn));
81     }
82
83     private void registerDiscoveryListener(DiscoveryListener discoveryListener) {
84         unregisterCurrentDiscoveryListener();
85         this.discoveryListener = discoveryListener;
86         sut.addDiscoveryListener(this.discoveryListener);
87     }
88
89     private void unregisterCurrentDiscoveryListener() {
90         if (this.discoveryListener != null) {
91             sut.removeDiscoveryListener(this.discoveryListener);
92         }
93     }
94
95     // Mock class which only overrides the doGetRequest method in order to make the class testable
96     class ConfigurableBridgeNupnpDiscoveryMock extends HueBridgeNupnpDiscovery {
97         public ConfigurableBridgeNupnpDiscoveryMock(ThingRegistry thingRegistry) {
98             super(thingRegistry);
99         }
100
101         @Override
102         protected @Nullable String doGetRequest(String url) throws IOException {
103             if (url.contains("meethue")) {
104                 return discoveryResult;
105             } else if (url.contains(IP1)) {
106                 return expBridgeDescription1.replaceAll("$SN", SN1);
107             } else if (url.contains(IP2)) {
108                 return expBridgeDescription1.replaceAll("$SN", SN2);
109             } else if (url.contains(IP3)) {
110                 return expBridgeDescription2.replaceAll("$SN", SN3);
111             }
112             throw new IOException();
113         }
114
115         @Override
116         protected boolean isClip2Supported(String ipAddress) {
117             return false;
118         }
119     }
120
121     @BeforeEach
122     public void setUp() {
123         registerService(volatileStorageService);
124
125         sut = getService(DiscoveryService.class, HueBridgeNupnpDiscovery.class);
126         assertThat(sut, is(notNullValue()));
127
128         inbox = getService(Inbox.class);
129         assertThat(inbox, is(notNullValue()));
130
131         unregisterCurrentDiscoveryListener();
132     }
133
134     @Test
135     public void bridgeThingTypeIsSupported() {
136         assertThat(sut.getSupportedThingTypes().size(), is(2));
137         assertThat(sut.getSupportedThingTypes().contains(THING_TYPE_BRIDGE), is(true));
138     }
139
140     @Test
141     public void validBridgesAreDiscovered() {
142         List<DiscoveryResult> oldResults = inbox.stream().filter(forThingTypeUID(BRIDGE_THING_TYPE_UID))
143                 .collect(Collectors.toList());
144         for (final DiscoveryResult oldResult : oldResults) {
145             inbox.remove(oldResult.getThingUID());
146         }
147
148         sut = new ConfigurableBridgeNupnpDiscoveryMock(mock(ThingRegistry.class));
149         registerService(sut, DiscoveryService.class.getName());
150         discoveryResult = validBridgeDiscoveryResult;
151         final Map<ThingUID, DiscoveryResult> results = new HashMap<>();
152         registerDiscoveryListener(new DiscoveryListener() {
153             @Override
154             public void thingDiscovered(DiscoveryService source, DiscoveryResult result) {
155                 results.put(result.getThingUID(), result);
156             }
157
158             @Override
159             public void thingRemoved(DiscoveryService source, ThingUID thingUID) {
160             }
161
162             @Override
163             public @Nullable Collection<ThingUID> removeOlderResults(DiscoveryService source, long timestamp,
164                     @Nullable Collection<ThingTypeUID> thingTypeUIDs, @Nullable ThingUID bridgeUID) {
165                 return null;
166             }
167         });
168
169         sut.startScan();
170
171         waitForAssert(() -> {
172             assertThat(results.size(), is(3));
173             assertThat(results.get(BRIDGE_THING_UID_1), is(notNullValue()));
174             checkDiscoveryResult(results.get(BRIDGE_THING_UID_1), IP1, SN1);
175             assertThat(results.get(BRIDGE_THING_UID_2), is(notNullValue()));
176             checkDiscoveryResult(results.get(BRIDGE_THING_UID_2), IP2, SN2);
177             assertThat(results.get(BRIDGE_THING_UID_3), is(notNullValue()));
178             checkDiscoveryResult(results.get(BRIDGE_THING_UID_3), IP3, SN3);
179
180             final List<DiscoveryResult> inboxResults = inbox.stream().filter(forThingTypeUID(BRIDGE_THING_TYPE_UID))
181                     .collect(Collectors.toList());
182             assertTrue(inboxResults.size() >= 3);
183
184             assertThat(inboxResults.stream().filter(result -> result.getThingUID().equals(BRIDGE_THING_UID_1))
185                     .findFirst().orElse(null), is(notNullValue()));
186             assertThat(inboxResults.stream().filter(result -> result.getThingUID().equals(BRIDGE_THING_UID_2))
187                     .findFirst().orElse(null), is(notNullValue()));
188             assertThat(inboxResults.stream().filter(result -> result.getThingUID().equals(BRIDGE_THING_UID_3))
189                     .findFirst().orElse(null), is(notNullValue()));
190         });
191     }
192
193     @Test
194     public void invalidBridgesAreNotDiscovered() {
195         List<DiscoveryResult> oldResults = inbox.stream().filter(forThingTypeUID(BRIDGE_THING_TYPE_UID))
196                 .collect(Collectors.toList());
197         for (final DiscoveryResult oldResult : oldResults) {
198             inbox.remove(oldResult.getThingUID());
199         }
200
201         sut = new ConfigurableBridgeNupnpDiscoveryMock(mock(ThingRegistry.class));
202         registerService(sut, DiscoveryService.class.getName());
203         final Map<ThingUID, DiscoveryResult> results = new HashMap<>();
204         registerDiscoveryListener(new DiscoveryListener() {
205             @Override
206             public void thingDiscovered(DiscoveryService source, DiscoveryResult result) {
207                 results.put(result.getThingUID(), result);
208             }
209
210             @Override
211             public void thingRemoved(DiscoveryService source, ThingUID thingUID) {
212             }
213
214             @Override
215             public @Nullable Collection<ThingUID> removeOlderResults(DiscoveryService source, long timestamp,
216                     @Nullable Collection<ThingTypeUID> thingTypeUIDs, @Nullable ThingUID bridgeUID) {
217                 return null;
218             }
219         });
220
221         // missing ip
222         discoveryResult = "[{\"id\":\"001788fffe20057f\",\"internalipaddress\":}]";
223         sut.startScan();
224         waitForAssert(() -> {
225             assertThat(results.size(), is(0));
226         });
227
228         // missing id
229         discoveryResult = "[{\"id\":\"\",\"internalipaddress\":192.168.30.22}]";
230         sut.startScan();
231         waitForAssert(() -> {
232             assertThat(results.size(), is(0));
233         });
234
235         // id < 10
236         discoveryResult = "[{\"id\":\"012345678\",\"internalipaddress\":192.168.30.22}]";
237         sut.startScan();
238         waitForAssert(() -> {
239             assertThat(results.size(), is(0));
240         });
241
242         // bridge indicator not part of id
243         discoveryResult = "[{\"id\":\"0123456789\",\"internalipaddress\":192.168.30.22}]";
244         sut.startScan();
245         waitForAssert(() -> {
246             assertThat(results.size(), is(0));
247         });
248
249         // bridge indicator at wrong position (-1)
250         discoveryResult = "[{\"id\":\"01234" + HueBridgeNupnpDiscovery.BRIDGE_INDICATOR
251                 + "7891\",\"internalipaddress\":192.168.30.22}]";
252         sut.startScan();
253         waitForAssert(() -> {
254             assertThat(results.size(), is(0));
255         });
256
257         // bridge indicator at wrong position (+1)
258         discoveryResult = "[{\"id\":\"0123456" + HueBridgeNupnpDiscovery.BRIDGE_INDICATOR
259                 + "7891\",\"internalipaddress\":192.168.30.22}]";
260         sut.startScan();
261         waitForAssert(() -> {
262             assertThat(results.size(), is(0));
263         });
264
265         // bridge not reachable
266         discoveryResult = "[{\"id\":\"001788fffe20057f\",\"internalipaddress\":192.168.30.1}]";
267         sut.startScan();
268         waitForAssert(() -> {
269             assertThat(results.size(), is(0));
270         });
271
272         // invalid bridge description
273         expBridgeDescription1 = "";
274         discoveryResult = "[{\"id\":\"001788fffe20057f\",\"internalipaddress\":" + IP1 + "}]";
275         sut.startScan();
276         waitForAssert(() -> {
277             assertThat(results.size(), is(0));
278         });
279
280         waitForAssert(() -> {
281             assertThat(
282                     inbox.stream().filter(forThingTypeUID(BRIDGE_THING_TYPE_UID)).collect(Collectors.toList()).size(),
283                     is(0));
284         });
285     }
286 }