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.handler;
15 import static org.eclipse.jdt.annotation.Checks.requireNonNull;
16 import static org.hamcrest.CoreMatchers.equalTo;
17 import static org.hamcrest.MatcherAssert.assertThat;
18 import static org.junit.jupiter.api.Assertions.*;
19 import static org.mockito.Mockito.mock;
20 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
22 import java.io.IOException;
23 import java.lang.reflect.Field;
24 import java.util.concurrent.ScheduledExecutorService;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.junit.jupiter.api.BeforeEach;
28 import org.junit.jupiter.api.Test;
29 import org.openhab.binding.hue.internal.AbstractHueOSGiTestParent;
30 import org.openhab.binding.hue.internal.config.HueBridgeConfig;
31 import org.openhab.binding.hue.internal.connection.HueBridge;
32 import org.openhab.binding.hue.internal.exceptions.ApiException;
33 import org.openhab.binding.hue.internal.exceptions.LinkButtonException;
34 import org.openhab.binding.hue.internal.exceptions.UnauthorizedException;
35 import org.openhab.core.common.ThreadPoolManager;
36 import org.openhab.core.config.core.Configuration;
37 import org.openhab.core.config.core.status.ConfigStatusMessage;
38 import org.openhab.core.thing.Bridge;
39 import org.openhab.core.thing.Thing;
40 import org.openhab.core.thing.ThingRegistry;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.thing.ThingTypeUID;
44 import org.openhab.core.thing.ThingUID;
47 * Tests for {@link HueBridgeHandler}.
49 * @author Oliver Libutzki - Initial contribution
50 * @author Michael Grammling - Initial contribution
51 * @author Denis Dudnik - switched to internally integrated source of Jue library
53 public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
55 private static final ThingTypeUID BRIDGE_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "bridge");
56 private static final String TEST_USER_NAME = "eshTestUser";
57 private static final String DUMMY_HOST = "1.2.3.4";
59 private ThingRegistry thingRegistry;
61 private ScheduledExecutorService scheduler;
65 registerVolatileStorageService();
66 thingRegistry = getService(ThingRegistry.class, ThingRegistry.class);
67 assertNotNull(thingRegistry);
69 scheduler = ThreadPoolManager.getScheduledPool("hueBridgeTest");
73 public void assertThatANewUserIsAddedToConfigIfNotExistingYet() {
74 Configuration configuration = new Configuration();
75 configuration.put(HOST, DUMMY_HOST);
76 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
77 Bridge bridge = createBridgeThing(configuration);
79 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
80 hueBridgeHandler.thingUpdated(bridge);
82 injectBridge(hueBridgeHandler,
83 new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
85 public String link(String deviceType) throws IOException, ApiException {
86 return TEST_USER_NAME;
90 hueBridgeHandler.onNotAuthenticated();
92 assertThat(bridge.getConfiguration().get(USER_NAME), equalTo(TEST_USER_NAME));
96 public void assertThatAnExistingUserIsUsedIfAuthenticationWasSuccessful() {
97 Configuration configuration = new Configuration();
98 configuration.put(HOST, DUMMY_HOST);
99 configuration.put(USER_NAME, TEST_USER_NAME);
100 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
101 Bridge bridge = createBridgeThing(configuration);
103 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
104 hueBridgeHandler.thingUpdated(bridge);
106 injectBridge(hueBridgeHandler,
107 new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
109 public void authenticate(String userName) throws IOException, ApiException {
113 hueBridgeHandler.onNotAuthenticated();
115 assertThat(bridge.getConfiguration().get(USER_NAME), equalTo(TEST_USER_NAME));
119 public void assertCorrectStatusIfAuthenticationFailedForOldUser() {
120 Configuration configuration = new Configuration();
121 configuration.put(HOST, DUMMY_HOST);
122 configuration.put(USER_NAME, "notAuthenticatedUser");
123 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
124 Bridge bridge = createBridgeThing(configuration);
126 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
127 hueBridgeHandler.thingUpdated(bridge);
129 injectBridge(hueBridgeHandler,
130 new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
132 public void authenticate(String userName) throws IOException, ApiException {
133 throw new UnauthorizedException();
137 hueBridgeHandler.onNotAuthenticated();
139 assertEquals("notAuthenticatedUser", bridge.getConfiguration().get(USER_NAME));
140 waitForAssert(() -> assertEquals(ThingStatus.OFFLINE, bridge.getStatus()));
141 assertEquals(ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, bridge.getStatusInfo().getStatusDetail());
145 public void verifyStatusIfLinkButtonIsNotPressed() {
146 Configuration configuration = new Configuration();
147 configuration.put(HOST, DUMMY_HOST);
148 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
149 Bridge bridge = createBridgeThing(configuration);
151 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
152 hueBridgeHandler.thingUpdated(bridge);
154 injectBridge(hueBridgeHandler,
155 new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
157 public String link(String deviceType) throws IOException, ApiException {
158 throw new LinkButtonException();
162 hueBridgeHandler.onNotAuthenticated();
164 assertNull(bridge.getConfiguration().get(USER_NAME));
165 waitForAssert(() -> assertEquals(ThingStatus.OFFLINE, bridge.getStatus()));
166 assertEquals(ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, bridge.getStatusInfo().getStatusDetail());
170 public void verifyStatusIfNewUserCannotBeCreated() {
171 Configuration configuration = new Configuration();
172 configuration.put(HOST, DUMMY_HOST);
173 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
174 Bridge bridge = createBridgeThing(configuration);
176 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
177 hueBridgeHandler.thingUpdated(bridge);
179 injectBridge(hueBridgeHandler,
180 new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
182 public String link(String deviceType) throws IOException, ApiException {
183 throw new ApiException();
187 hueBridgeHandler.onNotAuthenticated();
189 assertNull(bridge.getConfiguration().get(USER_NAME));
190 waitForAssert(() -> assertEquals(ThingStatus.OFFLINE, bridge.getStatus()));
191 waitForAssert(() -> assertEquals(ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
192 bridge.getStatusInfo().getStatusDetail()));
196 public void verifyOfflineIsSetWithoutBridgeOfflineStatus() {
197 Configuration configuration = new Configuration();
198 configuration.put(HOST, DUMMY_HOST);
199 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
200 Bridge bridge = createBridgeThing(configuration);
202 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
203 hueBridgeHandler.thingUpdated(bridge);
205 hueBridgeHandler.onConnectionLost();
207 waitForAssert(() -> assertEquals(ThingStatus.OFFLINE, bridge.getStatus()));
208 assertNotEquals(ThingStatusDetail.BRIDGE_OFFLINE, bridge.getStatusInfo().getStatusDetail());
212 public void assertThatAStatusConfigurationMessageForMissingBridgeIPIsProperlyReturnedIPIsNull() {
213 Configuration configuration = new Configuration();
214 configuration.put(HOST, null);
215 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
216 Bridge bridge = createBridgeThing(configuration);
218 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
220 ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST).withMessageKeySuffix(IP_ADDRESS_MISSING)
221 .withArguments(HOST).build();
223 waitForAssert(() -> assertEquals(expected, hueBridgeHandler.getConfigStatus().iterator().next()));
227 public void assertThatAStatusConfigurationMessageForMissingBridgeIPIsProperlyReturnedIPIsAnEmptyString() {
228 Configuration configuration = new Configuration();
229 configuration.put(HOST, "");
230 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
231 Bridge bridge = createBridgeThing(configuration);
233 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
235 ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST).withMessageKeySuffix(IP_ADDRESS_MISSING)
236 .withArguments(HOST).build();
238 waitForAssert(() -> assertEquals(expected, hueBridgeHandler.getConfigStatus().iterator().next()));
241 private Bridge createBridgeThing(Configuration configuration) {
242 configuration.put("useSelfSignedCertificate", false);
243 Bridge bridge = (Bridge) thingRegistry.createThingOfType(BRIDGE_THING_TYPE_UID,
244 new ThingUID(BRIDGE_THING_TYPE_UID, "testBridge"), null, "Bridge", configuration);
246 bridge = requireNonNull(bridge, "Bridge is null");
247 thingRegistry.add(bridge);
251 private void injectBridge(HueBridgeHandler hueBridgeHandler, HueBridge bridge) {
253 Field hueBridgeField = hueBridgeHandler.getClass().getDeclaredField("hueBridge");
254 hueBridgeField.setAccessible(true);
255 hueBridgeField.set(hueBridgeHandler, bridge);
256 } catch (Exception e) {
257 throw new AssertionError(e);