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;
15 import static org.hamcrest.CoreMatchers.*;
16 import static org.hamcrest.MatcherAssert.assertThat;
17 import static org.junit.jupiter.api.Assertions.*;
18 import static org.mockito.Mockito.mock;
19 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
21 import java.io.IOException;
22 import java.lang.reflect.Field;
23 import java.util.Collection;
24 import java.util.concurrent.Executors;
25 import java.util.concurrent.atomic.AtomicBoolean;
26 import java.util.concurrent.atomic.AtomicReference;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.eclipse.jetty.client.HttpClient;
31 import org.eclipse.jetty.http.HttpStatus;
32 import org.junit.jupiter.api.AfterEach;
33 import org.junit.jupiter.api.BeforeEach;
34 import org.junit.jupiter.api.Test;
35 import org.openhab.binding.hue.internal.api.dto.clip1.FullLight;
36 import org.openhab.binding.hue.internal.config.HueBridgeConfig;
37 import org.openhab.binding.hue.internal.connection.HueBridge;
38 import org.openhab.binding.hue.internal.discovery.HueDeviceDiscoveryService;
39 import org.openhab.binding.hue.internal.exceptions.ApiException;
40 import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
41 import org.openhab.core.config.core.Configuration;
42 import org.openhab.core.config.discovery.DiscoveryListener;
43 import org.openhab.core.config.discovery.DiscoveryResult;
44 import org.openhab.core.config.discovery.DiscoveryResultFlag;
45 import org.openhab.core.config.discovery.DiscoveryService;
46 import org.openhab.core.i18n.CommunicationException;
47 import org.openhab.core.thing.Bridge;
48 import org.openhab.core.thing.Thing;
49 import org.openhab.core.thing.ThingRegistry;
50 import org.openhab.core.thing.ThingStatus;
51 import org.openhab.core.thing.ThingStatusDetail;
52 import org.openhab.core.thing.ThingStatusInfo;
53 import org.openhab.core.thing.ThingTypeUID;
54 import org.openhab.core.thing.ThingUID;
55 import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
58 * Tests for {@link HueDeviceDiscoveryService}.
60 * @author Kai Kreuzer - Initial contribution
61 * @author Andre Fuechsel - added test 'assert start search is called()'
62 * - modified tests after introducing the generic thing types
63 * @author Denis Dudnik - switched to internally integrated source of Jue library
64 * @author Markus Rathgeb - migrated to plain Java test
67 public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent {
69 protected @Nullable DiscoveryListener discoveryListener;
70 protected @NonNullByDefault({}) ThingRegistry thingRegistry;
71 protected @NonNullByDefault({}) Bridge hueBridge;
72 protected @NonNullByDefault({}) HueBridgeHandler hueBridgeHandler;
73 protected @NonNullByDefault({}) HueDeviceDiscoveryService discoveryService;
75 protected static final ThingTypeUID BRIDGE_THING_TYPE_UID = new ThingTypeUID("hue", "bridge");
76 protected static final ThingUID BRIDGE_THING_UID = new ThingUID(BRIDGE_THING_TYPE_UID, "testBridge");
80 registerVolatileStorageService();
82 thingRegistry = getService(ThingRegistry.class, ThingRegistry.class);
83 assertThat(thingRegistry, is(notNullValue()));
85 Configuration configuration = new Configuration();
86 configuration.put(HOST, "1.2.3.4");
87 configuration.put(USER_NAME, "testUserName");
88 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
89 configuration.put("useSelfSignedCertificate", false);
91 hueBridge = (Bridge) thingRegistry.createThingOfType(BRIDGE_THING_TYPE_UID, BRIDGE_THING_UID, null, "Bridge",
94 assertThat(hueBridge, is(notNullValue()));
95 thingRegistry.add(hueBridge);
97 hueBridgeHandler = getThingHandler(hueBridge, HueBridgeHandler.class);
98 assertThat(hueBridgeHandler, is(notNullValue()));
100 discoveryService = getService(DiscoveryService.class, HueDeviceDiscoveryService.class);
101 assertThat(discoveryService, is(notNullValue()));
105 public void cleanUp() {
106 thingRegistry.remove(BRIDGE_THING_UID);
107 waitForAssert(() -> {
108 assertNull(getService(DiscoveryService.class, HueDeviceDiscoveryService.class));
112 private void registerDiscoveryListener(DiscoveryListener discoveryListener) {
113 unregisterCurrentDiscoveryListener();
114 this.discoveryListener = discoveryListener;
115 discoveryService.addDiscoveryListener(this.discoveryListener);
118 private void unregisterCurrentDiscoveryListener() {
119 if (this.discoveryListener != null) {
120 discoveryService.removeDiscoveryListener(this.discoveryListener);
125 public void hueLightRegistration() {
126 FullLight light = new FullLight();
128 light.setUniqueID("AA:BB:CC:DD:EE:FF:00:11-XX");
129 light.setModelID("LCT001");
130 light.setType("Extended color light");
132 AtomicReference<DiscoveryResult> resultWrapper = new AtomicReference<>();
134 registerDiscoveryListener(new DiscoveryListener() {
136 public void thingDiscovered(DiscoveryService source, DiscoveryResult result) {
137 resultWrapper.set(result);
141 public void thingRemoved(DiscoveryService source, ThingUID thingUID) {
145 public @Nullable Collection<ThingUID> removeOlderResults(DiscoveryService source, long timestamp,
146 @Nullable Collection<ThingTypeUID> thingTypeUIDs, @Nullable ThingUID bridgeUID) {
151 discoveryService.addLightDiscovery(light);
152 waitForAssert(() -> {
153 assertTrue(resultWrapper.get() != null);
156 final DiscoveryResult result = resultWrapper.get();
157 assertThat(result.getFlag(), is(DiscoveryResultFlag.NEW));
158 assertThat(result.getThingUID().toString(), is("hue:0210:testBridge:" + light.getId()));
159 assertThat(result.getThingTypeUID(), is(THING_TYPE_EXTENDED_COLOR_LIGHT));
160 assertThat(result.getBridgeUID(), is(hueBridge.getUID()));
161 assertThat(result.getProperties().get(LIGHT_ID), is(light.getId()));
165 public void startSearchIsCalled() throws IOException, ApiException {
166 final AtomicBoolean searchHasBeenTriggered = new AtomicBoolean(false);
168 HueBridge mockedHueBridge = new HueBridge(mock(HttpClient.class), "ip", 443, HueBridgeConfig.HTTPS, "username",
169 Executors.newScheduledThreadPool(1)) {
171 public HueResult get(String address) throws CommunicationException {
172 if (address.endsWith("testUserName")) {
173 String body = "{\"lights\":{}}";
174 return new HueResult(body, HttpStatus.OK_200);
175 } else if (address.endsWith("lights") || address.endsWith("sensors") || address.endsWith("groups")) {
177 return new HueResult(body, HttpStatus.OK_200);
178 } else if (address.endsWith("testUserName/config")) {
179 String body = "{\"apiversion\": \"1.26.0\"}";
180 return new HueResult(body, HttpStatus.OK_200);
182 return new HueResult("", HttpStatus.NOT_FOUND_404);
187 public HueResult post(String address, String body) throws CommunicationException {
188 if (address.endsWith("lights")) {
189 String bodyReturn = "{\"success\": {\"/lights\": \"Searching for new devices\"}}";
190 searchHasBeenTriggered.set(true);
191 return new HueResult(bodyReturn, HttpStatus.OK_200);
193 return new HueResult("", HttpStatus.NOT_FOUND_404);
198 public HueResult put(String address, String body) throws CommunicationException {
199 return new HueResult("", HttpStatus.OK_200);
203 installHttpClientMock(hueBridgeHandler, mockedHueBridge);
205 ThingStatusInfo online = ThingStatusInfoBuilder.create(ThingStatus.ONLINE, ThingStatusDetail.NONE).build();
206 waitForAssert(() -> {
207 assertThat(hueBridge.getStatusInfo(), is(online));
210 discoveryService.startScan();
211 waitForAssert(() -> {
212 assertTrue(searchHasBeenTriggered.get());
216 private void installHttpClientMock(HueBridgeHandler hueBridgeHandler, HueBridge mockedHueBridge) {
217 waitForAssert(() -> {
220 final Field hueBridgeField = HueBridgeHandler.class.getDeclaredField("hueBridge");
221 hueBridgeField.setAccessible(true);
222 hueBridgeField.set(hueBridgeHandler, mockedHueBridge);
224 final Object hueBridgeValue = hueBridgeField.get(hueBridgeHandler);
225 assertThat(hueBridgeValue, is(notNullValue()));
227 final Field usernameField = HueBridge.class.getDeclaredField("username");
228 usernameField.setAccessible(true);
229 usernameField.set(hueBridgeValue, hueBridgeHandler.getThing().getConfiguration().get(USER_NAME));
230 } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) {
231 fail("Reflection usage error");
234 hueBridgeHandler.initialize();