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.hue.internal.discovery;
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.openhab.binding.hue.internal.HueBindingConstants.THING_TYPE_BRIDGE;
19 import static org.openhab.core.config.discovery.inbox.InboxPredicates.forThingTypeUID;
21 import java.io.IOException;
22 import java.util.Collection;
23 import java.util.HashMap;
24 import java.util.List;
26 import java.util.stream.Collectors;
28 import org.junit.jupiter.api.BeforeEach;
29 import org.junit.jupiter.api.Test;
30 import org.openhab.core.config.discovery.DiscoveryListener;
31 import org.openhab.core.config.discovery.DiscoveryResult;
32 import org.openhab.core.config.discovery.DiscoveryService;
33 import org.openhab.core.config.discovery.inbox.Inbox;
34 import org.openhab.core.test.java.JavaOSGiTest;
35 import org.openhab.core.test.storage.VolatileStorageService;
36 import org.openhab.core.thing.ThingTypeUID;
37 import org.openhab.core.thing.ThingUID;
41 * @author Christoph Knauf - Initial contribution
42 * @author Markus Rathgeb - migrated to plain Java test
44 public class HueBridgeNupnpDiscoveryOSGITest extends JavaOSGiTest {
46 HueBridgeNupnpDiscovery sut;
47 VolatileStorageService volatileStorageService = new VolatileStorageService();
48 DiscoveryListener discoveryListener;
51 final ThingTypeUID BRIDGE_THING_TYPE_UID = new ThingTypeUID("hue", "bridge");
52 final String ip1 = "192.168.31.17";
53 final String ip2 = "192.168.30.28";
54 final String sn1 = "00178820057f";
55 final String sn2 = "001788141b41";
56 final ThingUID BRIDGE_THING_UID_1 = new ThingUID(BRIDGE_THING_TYPE_UID, sn1);
57 final ThingUID BRIDGE_THING_UID_2 = new ThingUID(BRIDGE_THING_TYPE_UID, sn2);
58 final String validBridgeDiscoveryResult = "[{\"id\":\"001788fffe20057f\",\"internalipaddress\":" + ip1
59 + "},{\"id\":\"001788fffe141b41\",\"internalipaddress\":" + ip2 + "}]";
60 String discoveryResult;
61 String expBridgeDescription = "" + //
62 "<?xml version=\"1.0\"?>" + //
63 "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">" + //
65 " <major>1</major>" + //
66 " <minor>0</minor>" + //
67 " </specVersion>" + //
68 " <URLBase>http://$IP:80/</URLBase>" + //
70 " <deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>" + //
71 " <friendlyName>Philips hue ($IP)</friendlyName>" + //
72 " <manufacturer>Royal Philips Electronics</manufacturer>" + //
73 " <manufacturerURL>http://www.philips.com</manufacturerURL>" + //
74 "<modelDescription>Philips hue Personal Wireless Lighting</modelDescription>" + //
75 "<modelName>Philips hue bridge 2012</modelName>" + //
76 "<modelNumber>1000000000000</modelNumber>" + //
77 "<modelURL>http://www.meethue.com</modelURL>" + //
78 " <serialNumber>93eadbeef13</serialNumber>" + //
79 " <UDN>uuid:01234567-89ab-cdef-0123-456789abcdef</UDN>" + //
82 " <serviceType>(null)</serviceType>" + //
83 " <serviceId>(null)</serviceId>" + //
84 " <controlURL>(null)</controlURL>" + //
85 " <eventSubURL>(null)</eventSubURL>" + //
86 " <SCPDURL>(null)</SCPDURL>" + //
88 " </serviceList>" + //
89 " <presentationURL>index.html</presentationURL>" + //
92 " <mimetype>image/png</mimetype>" + //
93 " <height>48</height>" + //
94 " <width>48</width>" + //
95 " <depth>24</depth>" + //
96 " <url>hue_logo_0.png</url>" + //
99 " <mimetype>image/png</mimetype>" + //
100 " <height>120</height>" + //
101 " <width>120</width>" + //
102 " <depth>24</depth>" + //
103 " <url>hue_logo_3.png</url>" + //
109 private void checkDiscoveryResult(DiscoveryResult result, String expIp, String expSn) {
110 assertThat(result.getBridgeUID(), nullValue());
111 assertThat(result.getLabel(), is(HueBridgeNupnpDiscovery.LABEL_PATTERN.replace("IP", expIp)));
112 assertThat(result.getProperties().get("ipAddress"), is(expIp));
113 assertThat(result.getProperties().get("serialNumber"), is(expSn));
116 private void registerDiscoveryListener(DiscoveryListener discoveryListener) {
117 unregisterCurrentDiscoveryListener();
118 this.discoveryListener = discoveryListener;
119 sut.addDiscoveryListener(this.discoveryListener);
122 private void unregisterCurrentDiscoveryListener() {
123 if (this.discoveryListener != null) {
124 sut.removeDiscoveryListener(this.discoveryListener);
128 // Mock class which only overrides the doGetRequest method in order to make the class testable
129 class ConfigurableBridgeNupnpDiscoveryMock extends HueBridgeNupnpDiscovery {
131 protected String doGetRequest(String url) throws IOException {
132 if (url.contains("meethue")) {
133 return discoveryResult;
134 } else if (url.contains(ip1)) {
135 return expBridgeDescription.replaceAll("$IP", ip1);
136 } else if (url.contains(ip2)) {
137 return expBridgeDescription.replaceAll("$IP", ip2);
139 throw new IOException();
144 public void setUp() {
145 registerService(volatileStorageService);
147 sut = getService(DiscoveryService.class, HueBridgeNupnpDiscovery.class);
148 assertThat(sut, is(notNullValue()));
150 inbox = getService(Inbox.class);
151 assertThat(inbox, is(notNullValue()));
153 unregisterCurrentDiscoveryListener();
157 public void bridgeThingTypeIsSupported() {
158 assertThat(sut.getSupportedThingTypes().size(), is(1));
159 assertThat(sut.getSupportedThingTypes().iterator().next(), is(THING_TYPE_BRIDGE));
163 public void validBridgesAreDiscovered() {
164 List<DiscoveryResult> oldResults = inbox.stream().filter(forThingTypeUID(BRIDGE_THING_TYPE_UID))
165 .collect(Collectors.toList());
166 for (final DiscoveryResult oldResult : oldResults) {
167 inbox.remove(oldResult.getThingUID());
170 sut = new ConfigurableBridgeNupnpDiscoveryMock();
171 registerService(sut, DiscoveryService.class.getName());
172 discoveryResult = validBridgeDiscoveryResult;
173 final Map<ThingUID, DiscoveryResult> results = new HashMap<>();
174 registerDiscoveryListener(new DiscoveryListener() {
176 public void thingDiscovered(DiscoveryService source, DiscoveryResult result) {
177 results.put(result.getThingUID(), result);
181 public void thingRemoved(DiscoveryService source, ThingUID thingUID) {
185 public Collection<ThingUID> removeOlderResults(DiscoveryService source, long timestamp,
186 Collection<ThingTypeUID> thingTypeUIDs, ThingUID bridgeUID) {
193 waitForAssert(() -> {
194 assertThat(results.size(), is(2));
195 assertThat(results.get(BRIDGE_THING_UID_1), is(notNullValue()));
196 checkDiscoveryResult(results.get(BRIDGE_THING_UID_1), ip1, sn1);
197 assertThat(results.get(BRIDGE_THING_UID_2), is(notNullValue()));
198 checkDiscoveryResult(results.get(BRIDGE_THING_UID_2), ip2, sn2);
200 final List<DiscoveryResult> inboxResults = inbox.stream().filter(forThingTypeUID(BRIDGE_THING_TYPE_UID))
201 .collect(Collectors.toList());
202 assertTrue(inboxResults.size() >= 2);
204 assertThat(inboxResults.stream().filter(result -> result.getThingUID().equals(BRIDGE_THING_UID_1))
205 .findFirst().orElse(null), is(notNullValue()));
206 assertThat(inboxResults.stream().filter(result -> result.getThingUID().equals(BRIDGE_THING_UID_2))
207 .findFirst().orElse(null), is(notNullValue()));
212 public void invalidBridgesAreNotDiscovered() {
213 List<DiscoveryResult> oldResults = inbox.stream().filter(forThingTypeUID(BRIDGE_THING_TYPE_UID))
214 .collect(Collectors.toList());
215 for (final DiscoveryResult oldResult : oldResults) {
216 inbox.remove(oldResult.getThingUID());
219 sut = new ConfigurableBridgeNupnpDiscoveryMock();
220 registerService(sut, DiscoveryService.class.getName());
221 final Map<ThingUID, DiscoveryResult> results = new HashMap<>();
222 registerDiscoveryListener(new DiscoveryListener() {
224 public void thingDiscovered(DiscoveryService source, DiscoveryResult result) {
225 results.put(result.getThingUID(), result);
229 public void thingRemoved(DiscoveryService source, ThingUID thingUID) {
233 public Collection<ThingUID> removeOlderResults(DiscoveryService source, long timestamp,
234 Collection<ThingTypeUID> thingTypeUIDs, ThingUID bridgeUID) {
240 discoveryResult = "[{\"id\":\"001788fffe20057f\",\"internalipaddress\":}]";
242 waitForAssert(() -> {
243 assertThat(results.size(), is(0));
247 discoveryResult = "[{\"id\":\"\",\"internalipaddress\":192.168.30.22}]";
249 waitForAssert(() -> {
250 assertThat(results.size(), is(0));
254 discoveryResult = "[{\"id\":\"012345678\",\"internalipaddress\":192.168.30.22}]";
256 waitForAssert(() -> {
257 assertThat(results.size(), is(0));
260 // bridge indicator not part of id
261 discoveryResult = "[{\"id\":\"0123456789\",\"internalipaddress\":192.168.30.22}]";
263 waitForAssert(() -> {
264 assertThat(results.size(), is(0));
267 // bridge indicator at wrong position (-1)
268 discoveryResult = "[{\"id\":\"01234" + HueBridgeNupnpDiscovery.BRIDGE_INDICATOR
269 + "7891\",\"internalipaddress\":192.168.30.22}]";
271 waitForAssert(() -> {
272 assertThat(results.size(), is(0));
275 // bridge indicator at wrong position (+1)
276 discoveryResult = "[{\"id\":\"0123456" + HueBridgeNupnpDiscovery.BRIDGE_INDICATOR
277 + "7891\",\"internalipaddress\":192.168.30.22}]";
279 waitForAssert(() -> {
280 assertThat(results.size(), is(0));
283 // bridge not reachable
284 discoveryResult = "[{\"id\":\"001788fffe20057f\",\"internalipaddress\":192.168.30.1}]";
286 waitForAssert(() -> {
287 assertThat(results.size(), is(0));
290 // invalid bridge description
291 expBridgeDescription = "";
292 discoveryResult = "[{\"id\":\"001788fffe20057f\",\"internalipaddress\":" + ip1 + "}]";
294 waitForAssert(() -> {
295 assertThat(results.size(), is(0));
298 waitForAssert(() -> {
300 inbox.stream().filter(forThingTypeUID(BRIDGE_THING_TYPE_UID)).collect(Collectors.toList()).size(),