]> git.basschouten.com Git - openhab-addons.git/blob
b1cac633dbcca80077e45d7c10fdcdb0b0fcf1c6
[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;
14
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.*;
20
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;
27
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;
56
57 /**
58  * Tests for {@link HueDeviceDiscoveryService}.
59  *
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
65  */
66 @NonNullByDefault
67 public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent {
68
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;
74
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");
77
78     @BeforeEach
79     public void setUp() {
80         registerVolatileStorageService();
81
82         thingRegistry = getService(ThingRegistry.class, ThingRegistry.class);
83         assertThat(thingRegistry, is(notNullValue()));
84
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);
90
91         hueBridge = (Bridge) thingRegistry.createThingOfType(BRIDGE_THING_TYPE_UID, BRIDGE_THING_UID, null, "Bridge",
92                 configuration);
93
94         assertThat(hueBridge, is(notNullValue()));
95         thingRegistry.add(hueBridge);
96
97         hueBridgeHandler = getThingHandler(hueBridge, HueBridgeHandler.class);
98         assertThat(hueBridgeHandler, is(notNullValue()));
99
100         discoveryService = getService(DiscoveryService.class, HueDeviceDiscoveryService.class);
101         assertThat(discoveryService, is(notNullValue()));
102     }
103
104     @AfterEach
105     public void cleanUp() {
106         thingRegistry.remove(BRIDGE_THING_UID);
107         waitForAssert(() -> {
108             assertNull(getService(DiscoveryService.class, HueDeviceDiscoveryService.class));
109         });
110     }
111
112     private void registerDiscoveryListener(DiscoveryListener discoveryListener) {
113         unregisterCurrentDiscoveryListener();
114         this.discoveryListener = discoveryListener;
115         discoveryService.addDiscoveryListener(this.discoveryListener);
116     }
117
118     private void unregisterCurrentDiscoveryListener() {
119         if (this.discoveryListener != null) {
120             discoveryService.removeDiscoveryListener(this.discoveryListener);
121         }
122     }
123
124     @Test
125     public void hueLightRegistration() {
126         FullLight light = new FullLight();
127         light.setId("1");
128         light.setUniqueID("AA:BB:CC:DD:EE:FF:00:11-XX");
129         light.setModelID("LCT001");
130         light.setType("Extended color light");
131
132         AtomicReference<DiscoveryResult> resultWrapper = new AtomicReference<>();
133
134         registerDiscoveryListener(new DiscoveryListener() {
135             @Override
136             public void thingDiscovered(DiscoveryService source, DiscoveryResult result) {
137                 resultWrapper.set(result);
138             }
139
140             @Override
141             public void thingRemoved(DiscoveryService source, ThingUID thingUID) {
142             }
143
144             @Override
145             public @Nullable Collection<ThingUID> removeOlderResults(DiscoveryService source, long timestamp,
146                     @Nullable Collection<ThingTypeUID> thingTypeUIDs, @Nullable ThingUID bridgeUID) {
147                 return null;
148             }
149         });
150
151         discoveryService.addLightDiscovery(light);
152         waitForAssert(() -> {
153             assertTrue(resultWrapper.get() != null);
154         });
155
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()));
162     }
163
164     @Test
165     public void startSearchIsCalled() throws IOException, ApiException {
166         final AtomicBoolean searchHasBeenTriggered = new AtomicBoolean(false);
167
168         HueBridge mockedHueBridge = new HueBridge(mock(HttpClient.class), "ip", 443, HueBridgeConfig.HTTPS, "username",
169                 Executors.newScheduledThreadPool(1)) {
170             @Override
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")) {
176                     String body = "{}";
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);
181                 } else {
182                     return new HueResult("", HttpStatus.NOT_FOUND_404);
183                 }
184             }
185
186             @Override
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);
192                 } else {
193                     return new HueResult("", HttpStatus.NOT_FOUND_404);
194                 }
195             }
196
197             @Override
198             public HueResult put(String address, String body) throws CommunicationException {
199                 return new HueResult("", HttpStatus.OK_200);
200             }
201         };
202
203         installHttpClientMock(hueBridgeHandler, mockedHueBridge);
204
205         ThingStatusInfo online = ThingStatusInfoBuilder.create(ThingStatus.ONLINE, ThingStatusDetail.NONE).build();
206         waitForAssert(() -> {
207             assertThat(hueBridge.getStatusInfo(), is(online));
208         });
209
210         discoveryService.startScan();
211         waitForAssert(() -> {
212             assertTrue(searchHasBeenTriggered.get());
213         });
214     }
215
216     private void installHttpClientMock(HueBridgeHandler hueBridgeHandler, HueBridge mockedHueBridge) {
217         waitForAssert(() -> {
218             try {
219                 // mock HttpClient
220                 final Field hueBridgeField = HueBridgeHandler.class.getDeclaredField("hueBridge");
221                 hueBridgeField.setAccessible(true);
222                 hueBridgeField.set(hueBridgeHandler, mockedHueBridge);
223
224                 final Object hueBridgeValue = hueBridgeField.get(hueBridgeHandler);
225                 assertThat(hueBridgeValue, is(notNullValue()));
226
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");
232             }
233         });
234         hueBridgeHandler.initialize();
235     }
236 }