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