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.jetty.client.HttpClient;
29 import org.eclipse.jetty.http.HttpStatus;
30 import org.junit.jupiter.api.AfterEach;
31 import org.junit.jupiter.api.BeforeEach;
32 import org.junit.jupiter.api.Test;
33 import org.openhab.binding.hue.internal.config.HueBridgeConfig;
34 import org.openhab.binding.hue.internal.connection.HueBridge;
35 import org.openhab.binding.hue.internal.discovery.HueDeviceDiscoveryService;
36 import org.openhab.binding.hue.internal.dto.FullLight;
37 import org.openhab.binding.hue.internal.exceptions.ApiException;
38 import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
39 import org.openhab.core.config.core.Configuration;
40 import org.openhab.core.config.discovery.DiscoveryListener;
41 import org.openhab.core.config.discovery.DiscoveryResult;
42 import org.openhab.core.config.discovery.DiscoveryResultFlag;
43 import org.openhab.core.config.discovery.DiscoveryService;
44 import org.openhab.core.i18n.CommunicationException;
45 import org.openhab.core.thing.Bridge;
46 import org.openhab.core.thing.Thing;
47 import org.openhab.core.thing.ThingRegistry;
48 import org.openhab.core.thing.ThingStatus;
49 import org.openhab.core.thing.ThingStatusDetail;
50 import org.openhab.core.thing.ThingStatusInfo;
51 import org.openhab.core.thing.ThingTypeUID;
52 import org.openhab.core.thing.ThingUID;
53 import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
56 * Tests for {@link HueDeviceDiscoveryService}.
58 * @author Kai Kreuzer - Initial contribution
59 * @author Andre Fuechsel - added test 'assert start search is called()'
60 * - modified tests after introducing the generic thing types
61 * @author Denis Dudnik - switched to internally integrated source of Jue library
62 * @author Markus Rathgeb - migrated to plain Java test
64 public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent {
66 protected DiscoveryListener discoveryListener;
67 protected ThingRegistry thingRegistry;
68 protected Bridge hueBridge;
69 protected HueBridgeHandler hueBridgeHandler;
70 protected HueDeviceDiscoveryService discoveryService;
72 protected final ThingTypeUID BRIDGE_THING_TYPE_UID = new ThingTypeUID("hue", "bridge");
73 protected final ThingUID BRIDGE_THING_UID = new ThingUID(BRIDGE_THING_TYPE_UID, "testBridge");
77 registerVolatileStorageService();
79 thingRegistry = getService(ThingRegistry.class, ThingRegistry.class);
80 assertThat(thingRegistry, is(notNullValue()));
82 Configuration configuration = new Configuration();
83 configuration.put(HOST, "1.2.3.4");
84 configuration.put(USER_NAME, "testUserName");
85 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
86 configuration.put("useSelfSignedCertificate", false);
88 hueBridge = (Bridge) thingRegistry.createThingOfType(BRIDGE_THING_TYPE_UID, BRIDGE_THING_UID, null, "Bridge",
91 assertThat(hueBridge, is(notNullValue()));
92 thingRegistry.add(hueBridge);
94 hueBridgeHandler = getThingHandler(hueBridge, HueBridgeHandler.class);
95 assertThat(hueBridgeHandler, is(notNullValue()));
97 discoveryService = getService(DiscoveryService.class, HueDeviceDiscoveryService.class);
98 assertThat(discoveryService, is(notNullValue()));
102 public void cleanUp() {
103 thingRegistry.remove(BRIDGE_THING_UID);
104 waitForAssert(() -> {
105 assertNull(getService(DiscoveryService.class, HueDeviceDiscoveryService.class));
109 private void registerDiscoveryListener(DiscoveryListener discoveryListener) {
110 unregisterCurrentDiscoveryListener();
111 this.discoveryListener = discoveryListener;
112 discoveryService.addDiscoveryListener(this.discoveryListener);
115 private void unregisterCurrentDiscoveryListener() {
116 if (this.discoveryListener != null) {
117 discoveryService.removeDiscoveryListener(this.discoveryListener);
122 public void hueLightRegistration() {
123 FullLight light = new FullLight();
125 light.setUniqueID("AA:BB:CC:DD:EE:FF:00:11-XX");
126 light.setModelID("LCT001");
127 light.setType("Extended color light");
129 AtomicReference<DiscoveryResult> resultWrapper = new AtomicReference<>();
131 registerDiscoveryListener(new DiscoveryListener() {
133 public void thingDiscovered(DiscoveryService source, DiscoveryResult result) {
134 resultWrapper.set(result);
138 public void thingRemoved(DiscoveryService source, ThingUID thingUID) {
142 public Collection<ThingUID> removeOlderResults(DiscoveryService source, long timestamp,
143 Collection<ThingTypeUID> thingTypeUIDs, ThingUID bridgeUID) {
148 discoveryService.addLightDiscovery(light);
149 waitForAssert(() -> {
150 assertTrue(resultWrapper.get() != null);
153 final DiscoveryResult result = resultWrapper.get();
154 assertThat(result.getFlag(), is(DiscoveryResultFlag.NEW));
155 assertThat(result.getThingUID().toString(), is("hue:0210:testBridge:" + light.getId()));
156 assertThat(result.getThingTypeUID(), is(THING_TYPE_EXTENDED_COLOR_LIGHT));
157 assertThat(result.getBridgeUID(), is(hueBridge.getUID()));
158 assertThat(result.getProperties().get(LIGHT_ID), is(light.getId()));
162 public void startSearchIsCalled() throws IOException, ApiException {
163 final AtomicBoolean searchHasBeenTriggered = new AtomicBoolean(false);
165 HueBridge mockedHueBridge = new HueBridge(mock(HttpClient.class), "ip", 443, HueBridgeConfig.HTTPS, "username",
166 Executors.newScheduledThreadPool(1)) {
168 public HueResult get(String address) throws CommunicationException {
169 if (address.endsWith("testUserName")) {
170 String body = "{\"lights\":{}}";
171 return new HueResult(body, HttpStatus.OK_200);
172 } else if (address.endsWith("lights") || address.endsWith("sensors") || address.endsWith("groups")) {
174 return new HueResult(body, HttpStatus.OK_200);
175 } else if (address.endsWith("testUserName/config")) {
176 String body = "{\"apiversion\": \"1.26.0\"}";
177 return new HueResult(body, HttpStatus.OK_200);
179 return new HueResult("", HttpStatus.NOT_FOUND_404);
184 public HueResult post(String address, String body) throws CommunicationException {
185 if (address.endsWith("lights")) {
186 String bodyReturn = "{\"success\": {\"/lights\": \"Searching for new devices\"}}";
187 searchHasBeenTriggered.set(true);
188 return new HueResult(bodyReturn, HttpStatus.OK_200);
190 return new HueResult("", HttpStatus.NOT_FOUND_404);
195 public HueResult put(String address, String body) throws CommunicationException {
196 return new HueResult("", HttpStatus.OK_200);
200 installHttpClientMock(hueBridgeHandler, mockedHueBridge);
202 ThingStatusInfo online = ThingStatusInfoBuilder.create(ThingStatus.ONLINE, ThingStatusDetail.NONE).build();
203 waitForAssert(() -> {
204 assertThat(hueBridge.getStatusInfo(), is(online));
207 discoveryService.startScan();
208 waitForAssert(() -> {
209 assertTrue(searchHasBeenTriggered.get());
213 private void installHttpClientMock(HueBridgeHandler hueBridgeHandler, HueBridge mockedHueBridge) {
214 waitForAssert(() -> {
217 final Field hueBridgeField = HueBridgeHandler.class.getDeclaredField("hueBridge");
218 hueBridgeField.setAccessible(true);
219 hueBridgeField.set(hueBridgeHandler, mockedHueBridge);
221 final Object hueBridgeValue = hueBridgeField.get(hueBridgeHandler);
222 assertThat(hueBridgeValue, is(notNullValue()));
224 final Field usernameField = HueBridge.class.getDeclaredField("username");
225 usernameField.setAccessible(true);
226 usernameField.set(hueBridgeValue, hueBridgeHandler.getThing().getConfiguration().get(USER_NAME));
227 } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) {
228 fail("Reflection usage error");
231 hueBridgeHandler.initialize();