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.innogysmarthome.internal.handler;
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.mockito.Mockito.*;
18 import java.net.ConnectException;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.concurrent.ScheduledExecutorService;
22 import java.util.concurrent.ScheduledFuture;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.eclipse.jetty.client.HttpClient;
26 import org.junit.jupiter.api.BeforeEach;
27 import org.junit.jupiter.api.Test;
28 import org.openhab.binding.innogysmarthome.internal.InnogyBindingConstants;
29 import org.openhab.binding.innogysmarthome.internal.InnogyWebSocket;
30 import org.openhab.binding.innogysmarthome.internal.client.InnogyClient;
31 import org.openhab.binding.innogysmarthome.internal.client.entity.device.Device;
32 import org.openhab.binding.innogysmarthome.internal.client.entity.device.DeviceConfig;
33 import org.openhab.binding.innogysmarthome.internal.manager.FullDeviceManager;
34 import org.openhab.core.auth.client.oauth2.OAuthClientService;
35 import org.openhab.core.auth.client.oauth2.OAuthFactory;
36 import org.openhab.core.config.core.Configuration;
37 import org.openhab.core.thing.Bridge;
38 import org.openhab.core.thing.ThingUID;
39 import org.openhab.core.thing.binding.BaseThingHandler;
40 import org.slf4j.LoggerFactory;
42 import ch.qos.logback.classic.Level;
43 import ch.qos.logback.classic.Logger;
46 * @author Sven Strohschein - Initial contribution
48 public class InnogyBridgeHandlerTest {
50 private static final int MAXIMUM_RETRY_EXECUTIONS = 10;
52 private InnogyBridgeHandlerAccessible bridgeHandler;
53 private Bridge bridgeMock;
54 private InnogyWebSocket webSocketMock;
57 public void before() throws Exception {
58 final Logger loggerBridge = (Logger) LoggerFactory.getLogger(InnogyBridgeHandler.class);
59 loggerBridge.setLevel(Level.OFF);
61 final Logger logerBaseHandler = (Logger) LoggerFactory.getLogger(BaseThingHandler.class);
62 logerBaseHandler.setLevel(Level.OFF);
64 bridgeMock = mock(Bridge.class);
65 when(bridgeMock.getUID()).thenReturn(new ThingUID("innogysmarthome", "bridge"));
67 webSocketMock = mock(InnogyWebSocket.class);
69 OAuthClientService oAuthService = mock(OAuthClientService.class);
71 OAuthFactory oAuthFactoryMock = mock(OAuthFactory.class);
72 when(oAuthFactoryMock.createOAuthClientService(any(), any(), any(), any(), any(), any(), any()))
73 .thenReturn(oAuthService);
75 HttpClient httpClientMock = mock(HttpClient.class);
77 bridgeHandler = new InnogyBridgeHandlerAccessible(bridgeMock, oAuthFactoryMock, httpClientMock);
81 public void testInitializeBridgeNotAvailable() throws Exception {
82 Configuration bridgeConfig = new Configuration();
83 HashMap<String, Object> map = new HashMap<>();
84 map.put("brand", "XY");
85 bridgeConfig.setProperties(map);
87 when(bridgeMock.getConfiguration()).thenReturn(bridgeConfig);
89 bridgeHandler.initialize();
91 verify(webSocketMock, never()).start();
92 assertEquals(0, bridgeHandler.getDirectExecutionCount());
96 public void testInitialize() throws Exception {
97 Configuration bridgeConfig = new Configuration();
99 when(bridgeMock.getConfiguration()).thenReturn(bridgeConfig);
101 bridgeHandler.initialize();
103 verify(webSocketMock).start();
104 assertEquals(1, bridgeHandler.getDirectExecutionCount());
108 public void testInitializeErrorOnStartingWebSocket() throws Exception {
109 Configuration bridgeConfig = new Configuration();
111 when(bridgeMock.getConfiguration()).thenReturn(bridgeConfig);
113 doThrow(new RuntimeException("Test-Exception")).when(webSocketMock).start();
115 bridgeHandler.initialize();
117 verify(webSocketMock, times(MAXIMUM_RETRY_EXECUTIONS)).start();
118 assertEquals(1, bridgeHandler.getDirectExecutionCount()); // only the first execution should be without a delay
122 public void testConnectionClosed() throws Exception {
123 Configuration bridgeConfig = new Configuration();
125 when(bridgeMock.getConfiguration()).thenReturn(bridgeConfig);
127 bridgeHandler.initialize();
129 verify(webSocketMock).start();
130 assertEquals(1, bridgeHandler.getDirectExecutionCount());
132 bridgeHandler.connectionClosed();
134 verify(webSocketMock, times(2)).start(); // automatically restarted (with a delay)
135 assertEquals(1, bridgeHandler.getDirectExecutionCount());
137 bridgeHandler.connectionClosed();
139 verify(webSocketMock, times(3)).start(); // automatically restarted (with a delay)
140 assertEquals(1, bridgeHandler.getDirectExecutionCount());
144 public void testConnectionClosedReconnectNotPossible() throws Exception {
145 Configuration bridgeConfig = new Configuration();
147 when(bridgeMock.getConfiguration()).thenReturn(bridgeConfig);
149 bridgeHandler.initialize();
151 verify(webSocketMock).start();
152 assertEquals(1, bridgeHandler.getDirectExecutionCount());
154 doThrow(new ConnectException("Connection refused")).when(webSocketMock).start();
156 bridgeHandler.connectionClosed();
158 verify(webSocketMock, times(10)).start(); // automatic reconnect attempts (with a delay)
159 assertEquals(1, bridgeHandler.getDirectExecutionCount());
163 public void testOnEventDisconnect() throws Exception {
164 final String disconnectEventJSON = "{ type: \"Disconnect\" }";
166 Configuration bridgeConfig = new Configuration();
168 when(bridgeMock.getConfiguration()).thenReturn(bridgeConfig);
170 bridgeHandler.initialize();
172 verify(webSocketMock).start();
173 assertEquals(1, bridgeHandler.getDirectExecutionCount());
175 bridgeHandler.onEvent(disconnectEventJSON);
177 verify(webSocketMock, times(2)).start(); // automatically restarted (with a delay)
178 assertEquals(1, bridgeHandler.getDirectExecutionCount());
180 bridgeHandler.onEvent(disconnectEventJSON);
182 verify(webSocketMock, times(3)).start(); // automatically restarted (with a delay)
183 assertEquals(1, bridgeHandler.getDirectExecutionCount());
186 private class InnogyBridgeHandlerAccessible extends InnogyBridgeHandler {
188 private final InnogyClient innogyClientMock;
189 private final FullDeviceManager fullDeviceManagerMock;
190 private final ScheduledExecutorService schedulerMock;
191 private int executionCount;
192 private int directExecutionCount;
194 private InnogyBridgeHandlerAccessible(Bridge bridge, OAuthFactory oAuthFactory, HttpClient httpClient)
196 super(bridge, oAuthFactory, httpClient);
198 Device bridgeDevice = new Device();
199 bridgeDevice.setId("bridgeId");
200 bridgeDevice.setType(InnogyBindingConstants.DEVICE_SHC);
201 bridgeDevice.setConfig(new DeviceConfig());
203 innogyClientMock = mock(InnogyClient.class);
204 fullDeviceManagerMock = mock(FullDeviceManager.class);
205 when(fullDeviceManagerMock.getFullDevices()).thenReturn(Collections.singletonList(bridgeDevice));
207 schedulerMock = mock(ScheduledExecutorService.class);
209 doAnswer(invocationOnMock -> {
210 if (executionCount <= MAXIMUM_RETRY_EXECUTIONS) {
212 invocationOnMock.getArgument(0, Runnable.class).run();
215 }).when(schedulerMock).execute(any());
217 doAnswer(invocationOnMock -> {
218 if (executionCount <= MAXIMUM_RETRY_EXECUTIONS) {
220 long seconds = invocationOnMock.getArgument(1);
222 directExecutionCount++;
225 invocationOnMock.getArgument(0, Runnable.class).run();
227 return mock(ScheduledFuture.class);
228 }).when(schedulerMock).schedule(any(Runnable.class), anyLong(), any());
231 public int getDirectExecutionCount() {
232 return directExecutionCount;
237 FullDeviceManager createFullDeviceManager(@NonNull InnogyClient client) {
238 return fullDeviceManagerMock;
243 InnogyClient createInnogyClient(@NonNull OAuthClientService oAuthService, @NonNull HttpClient httpClient) {
244 return innogyClientMock;
249 InnogyWebSocket createWebSocket() {
250 return webSocketMock;
255 ScheduledExecutorService getScheduler() {
256 return schedulerMock;