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.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jetty.client.HttpClient;
28 import org.junit.jupiter.api.BeforeEach;
29 import org.junit.jupiter.api.Test;
30 import org.openhab.binding.hue.internal.AbstractHueOSGiTestParent;
31 import org.openhab.binding.hue.internal.config.HueBridgeConfig;
32 import org.openhab.binding.hue.internal.connection.HueBridge;
33 import org.openhab.binding.hue.internal.exceptions.ApiException;
34 import org.openhab.binding.hue.internal.exceptions.LinkButtonException;
35 import org.openhab.binding.hue.internal.exceptions.UnauthorizedException;
36 import org.openhab.core.common.ThreadPoolManager;
37 import org.openhab.core.config.core.Configuration;
38 import org.openhab.core.config.core.status.ConfigStatusMessage;
39 import org.openhab.core.thing.Bridge;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingRegistry;
42 import org.openhab.core.thing.ThingStatus;
43 import org.openhab.core.thing.ThingStatusDetail;
44 import org.openhab.core.thing.ThingTypeUID;
45 import org.openhab.core.thing.ThingUID;
48 * Tests for {@link HueBridgeHandler}.
50 * @author Oliver Libutzki - Initial contribution
51 * @author Michael Grammling - Initial contribution
52 * @author Denis Dudnik - switched to internally integrated source of Jue library
55 public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
57 private static final ThingTypeUID BRIDGE_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "bridge");
58 private static final String TEST_USER_NAME = "eshTestUser";
59 private static final String DUMMY_HOST = "1.2.3.4";
61 private @NonNullByDefault({}) ThingRegistry thingRegistry;
62 private @NonNullByDefault({}) ScheduledExecutorService scheduler;
66 registerVolatileStorageService();
67 thingRegistry = getService(ThingRegistry.class, ThingRegistry.class);
68 assertNotNull(thingRegistry);
70 scheduler = ThreadPoolManager.getScheduledPool("hueBridgeTest");
74 public void assertThatANewUserIsAddedToConfigIfNotExistingYet() {
75 Configuration configuration = new Configuration();
76 configuration.put(HOST, DUMMY_HOST);
77 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
78 Bridge bridge = createBridgeThing(configuration);
80 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
81 hueBridgeHandler.thingUpdated(bridge);
83 injectBridge(hueBridgeHandler,
84 new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
86 public String link(String deviceType) throws IOException, ApiException {
87 return TEST_USER_NAME;
91 hueBridgeHandler.onNotAuthenticated();
93 assertThat(bridge.getConfiguration().get(USER_NAME), equalTo(TEST_USER_NAME));
97 public void assertThatAnExistingUserIsUsedIfAuthenticationWasSuccessful() {
98 Configuration configuration = new Configuration();
99 configuration.put(HOST, DUMMY_HOST);
100 configuration.put(USER_NAME, TEST_USER_NAME);
101 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
102 Bridge bridge = createBridgeThing(configuration);
104 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
105 hueBridgeHandler.thingUpdated(bridge);
107 injectBridge(hueBridgeHandler,
108 new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
110 public void authenticate(String userName) throws IOException, ApiException {
114 hueBridgeHandler.onNotAuthenticated();
116 assertThat(bridge.getConfiguration().get(USER_NAME), equalTo(TEST_USER_NAME));
120 public void assertCorrectStatusIfAuthenticationFailedForOldUser() {
121 Configuration configuration = new Configuration();
122 configuration.put(HOST, DUMMY_HOST);
123 configuration.put(USER_NAME, "notAuthenticatedUser");
124 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
125 Bridge bridge = createBridgeThing(configuration);
127 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
128 hueBridgeHandler.thingUpdated(bridge);
130 injectBridge(hueBridgeHandler,
131 new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
133 public void authenticate(String userName) throws IOException, ApiException {
134 throw new UnauthorizedException();
138 hueBridgeHandler.onNotAuthenticated();
140 assertEquals("notAuthenticatedUser", bridge.getConfiguration().get(USER_NAME));
141 waitForAssert(() -> assertEquals(ThingStatus.OFFLINE, bridge.getStatus()));
142 assertEquals(ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, bridge.getStatusInfo().getStatusDetail());
146 public void verifyStatusIfLinkButtonIsNotPressed() {
147 Configuration configuration = new Configuration();
148 configuration.put(HOST, DUMMY_HOST);
149 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
150 Bridge bridge = createBridgeThing(configuration);
152 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
153 hueBridgeHandler.thingUpdated(bridge);
155 injectBridge(hueBridgeHandler,
156 new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
158 public String link(String deviceType) throws IOException, ApiException {
159 throw new LinkButtonException();
163 hueBridgeHandler.onNotAuthenticated();
165 assertNull(bridge.getConfiguration().get(USER_NAME));
166 waitForAssert(() -> assertEquals(ThingStatus.OFFLINE, bridge.getStatus()));
167 assertEquals(ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, bridge.getStatusInfo().getStatusDetail());
171 public void verifyStatusIfNewUserCannotBeCreated() {
172 Configuration configuration = new Configuration();
173 configuration.put(HOST, DUMMY_HOST);
174 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
175 Bridge bridge = createBridgeThing(configuration);
177 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
178 hueBridgeHandler.thingUpdated(bridge);
180 injectBridge(hueBridgeHandler,
181 new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
183 public String link(String deviceType) throws IOException, ApiException {
184 throw new ApiException();
188 hueBridgeHandler.onNotAuthenticated();
190 assertNull(bridge.getConfiguration().get(USER_NAME));
191 waitForAssert(() -> assertEquals(ThingStatus.OFFLINE, bridge.getStatus()));
192 waitForAssert(() -> assertEquals(ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
193 bridge.getStatusInfo().getStatusDetail()));
197 public void verifyOfflineIsSetWithoutBridgeOfflineStatus() {
198 Configuration configuration = new Configuration();
199 configuration.put(HOST, DUMMY_HOST);
200 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
201 Bridge bridge = createBridgeThing(configuration);
203 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
204 hueBridgeHandler.thingUpdated(bridge);
206 hueBridgeHandler.onConnectionLost();
208 waitForAssert(() -> assertEquals(ThingStatus.OFFLINE, bridge.getStatus()));
209 assertNotEquals(ThingStatusDetail.BRIDGE_OFFLINE, bridge.getStatusInfo().getStatusDetail());
213 public void assertThatAStatusConfigurationMessageForMissingBridgeIPIsProperlyReturnedIPIsNull() {
214 Configuration configuration = new Configuration();
215 configuration.put(HOST, null);
216 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
217 Bridge bridge = createBridgeThing(configuration);
219 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
221 ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST).withMessageKeySuffix(IP_ADDRESS_MISSING)
222 .withArguments(HOST).build();
224 waitForAssert(() -> assertEquals(expected, hueBridgeHandler.getConfigStatus().iterator().next()));
228 public void assertThatAStatusConfigurationMessageForMissingBridgeIPIsProperlyReturnedIPIsAnEmptyString() {
229 Configuration configuration = new Configuration();
230 configuration.put(HOST, "");
231 configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
232 Bridge bridge = createBridgeThing(configuration);
234 HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
236 ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST).withMessageKeySuffix(IP_ADDRESS_MISSING)
237 .withArguments(HOST).build();
239 waitForAssert(() -> assertEquals(expected, hueBridgeHandler.getConfigStatus().iterator().next()));
242 private Bridge createBridgeThing(Configuration configuration) {
243 configuration.put("useSelfSignedCertificate", false);
244 Bridge bridge = (Bridge) thingRegistry.createThingOfType(BRIDGE_THING_TYPE_UID,
245 new ThingUID(BRIDGE_THING_TYPE_UID, "testBridge"), null, "Bridge", configuration);
247 bridge = requireNonNull(bridge, "Bridge is null");
248 thingRegistry.add(bridge);
252 private void injectBridge(HueBridgeHandler hueBridgeHandler, HueBridge bridge) {
254 Field hueBridgeField = hueBridgeHandler.getClass().getDeclaredField("hueBridge");
255 hueBridgeField.setAccessible(true);
256 hueBridgeField.set(hueBridgeHandler, bridge);
257 } catch (Exception e) {
258 throw new AssertionError(e);