2 * Copyright (c) 2010-2023 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.mockito.Mockito.mock;
19 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
20 import static org.openhab.core.config.discovery.inbox.InboxPredicates.forThingTypeUID;
22 import java.io.IOException;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.List;
27 import java.util.stream.Collectors;
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;
44 * @author Christoph Knauf - Initial contribution
45 * @author Markus Rathgeb - migrated to plain Java test
48 public class HueBridgeNupnpDiscoveryOSGITest extends JavaOSGiTest {
50 private @NonNullByDefault({}) HueBridgeNupnpDiscovery sut;
51 private VolatileStorageService volatileStorageService = new VolatileStorageService();
52 private @Nullable DiscoveryListener discoveryListener;
53 private @NonNullByDefault({}) Inbox inbox;
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);
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\":\"\"}";
73 private void checkDiscoveryResult(@Nullable DiscoveryResult result, String expIp, String expSn) {
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));
83 private void registerDiscoveryListener(DiscoveryListener discoveryListener) {
84 unregisterCurrentDiscoveryListener();
85 this.discoveryListener = discoveryListener;
86 sut.addDiscoveryListener(this.discoveryListener);
89 private void unregisterCurrentDiscoveryListener() {
90 if (this.discoveryListener != null) {
91 sut.removeDiscoveryListener(this.discoveryListener);
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) {
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);
112 throw new IOException();
116 protected boolean isClip2Supported(String ipAddress) {
122 public void setUp() {
123 registerService(volatileStorageService);
125 sut = getService(DiscoveryService.class, HueBridgeNupnpDiscovery.class);
126 assertThat(sut, is(notNullValue()));
128 inbox = getService(Inbox.class);
129 assertThat(inbox, is(notNullValue()));
131 unregisterCurrentDiscoveryListener();
135 public void bridgeThingTypeIsSupported() {
136 assertThat(sut.getSupportedThingTypes().size(), is(2));
137 assertThat(sut.getSupportedThingTypes().contains(THING_TYPE_BRIDGE), is(true));
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());
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() {
154 public void thingDiscovered(DiscoveryService source, DiscoveryResult result) {
155 results.put(result.getThingUID(), result);
159 public void thingRemoved(DiscoveryService source, ThingUID thingUID) {
163 public @Nullable Collection<ThingUID> removeOlderResults(DiscoveryService source, long timestamp,
164 @Nullable Collection<ThingTypeUID> thingTypeUIDs, @Nullable ThingUID bridgeUID) {
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);
180 final List<DiscoveryResult> inboxResults = inbox.stream().filter(forThingTypeUID(BRIDGE_THING_TYPE_UID))
181 .collect(Collectors.toList());
182 assertTrue(inboxResults.size() >= 3);
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()));
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());
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() {
206 public void thingDiscovered(DiscoveryService source, DiscoveryResult result) {
207 results.put(result.getThingUID(), result);
211 public void thingRemoved(DiscoveryService source, ThingUID thingUID) {
215 public @Nullable Collection<ThingUID> removeOlderResults(DiscoveryService source, long timestamp,
216 @Nullable Collection<ThingTypeUID> thingTypeUIDs, @Nullable ThingUID bridgeUID) {
222 discoveryResult = "[{\"id\":\"001788fffe20057f\",\"internalipaddress\":}]";
224 waitForAssert(() -> {
225 assertThat(results.size(), is(0));
229 discoveryResult = "[{\"id\":\"\",\"internalipaddress\":192.168.30.22}]";
231 waitForAssert(() -> {
232 assertThat(results.size(), is(0));
236 discoveryResult = "[{\"id\":\"012345678\",\"internalipaddress\":192.168.30.22}]";
238 waitForAssert(() -> {
239 assertThat(results.size(), is(0));
242 // bridge indicator not part of id
243 discoveryResult = "[{\"id\":\"0123456789\",\"internalipaddress\":192.168.30.22}]";
245 waitForAssert(() -> {
246 assertThat(results.size(), is(0));
249 // bridge indicator at wrong position (-1)
250 discoveryResult = "[{\"id\":\"01234" + HueBridgeNupnpDiscovery.BRIDGE_INDICATOR
251 + "7891\",\"internalipaddress\":192.168.30.22}]";
253 waitForAssert(() -> {
254 assertThat(results.size(), is(0));
257 // bridge indicator at wrong position (+1)
258 discoveryResult = "[{\"id\":\"0123456" + HueBridgeNupnpDiscovery.BRIDGE_INDICATOR
259 + "7891\",\"internalipaddress\":192.168.30.22}]";
261 waitForAssert(() -> {
262 assertThat(results.size(), is(0));
265 // bridge not reachable
266 discoveryResult = "[{\"id\":\"001788fffe20057f\",\"internalipaddress\":192.168.30.1}]";
268 waitForAssert(() -> {
269 assertThat(results.size(), is(0));
272 // invalid bridge description
273 expBridgeDescription1 = "";
274 discoveryResult = "[{\"id\":\"001788fffe20057f\",\"internalipaddress\":" + IP1 + "}]";
276 waitForAssert(() -> {
277 assertThat(results.size(), is(0));
280 waitForAssert(() -> {
282 inbox.stream().filter(forThingTypeUID(BRIDGE_THING_TYPE_UID)).collect(Collectors.toList()).size(),