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.boschshc.internal.devices.bridge;
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.mockito.ArgumentMatchers.*;
17 import static org.mockito.Mockito.*;
19 import java.lang.reflect.Field;
20 import java.nio.charset.StandardCharsets;
21 import java.util.List;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.TimeoutException;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jetty.client.api.ContentResponse;
27 import org.eclipse.jetty.client.api.Request;
28 import org.eclipse.jetty.http.HttpMethod;
29 import org.eclipse.jetty.util.ssl.SslContextFactory;
30 import org.junit.jupiter.api.BeforeAll;
31 import org.junit.jupiter.api.BeforeEach;
32 import org.junit.jupiter.api.Test;
33 import org.junit.platform.commons.support.HierarchyTraversalMode;
34 import org.junit.platform.commons.support.ReflectionSupport;
35 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device;
36 import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult;
37 import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
38 import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException;
39 import org.openhab.binding.boschshc.internal.services.binaryswitch.dto.BinarySwitchServiceState;
40 import org.openhab.binding.boschshc.internal.services.userstate.dto.UserStateServiceState;
41 import org.slf4j.Logger;
44 * Tests cases for {@link BoschHttpClient}.
46 * @author Gerd Zanker - Initial contribution
49 class BoschHttpClientTest {
51 private @NonNullByDefault({}) BoschHttpClient httpClient;
54 static void beforeAll() {
55 BoschSslUtilTest.prepareTempFolderForKeyStore();
59 void beforeEach() throws PairingFailedException {
60 SslContextFactory sslFactory = new BoschSslUtil("127.0.0.1").getSslContextFactory();
61 httpClient = new BoschHttpClient("127.0.0.1", "dummy", sslFactory);
62 assertNotNull(httpClient);
66 void getPublicInformationUrl() {
67 assertEquals("https://127.0.0.1:8446/smarthome/public/information", httpClient.getPublicInformationUrl());
71 void getPairingUrl() {
72 assertEquals("https://127.0.0.1:8443/smarthome/clients", httpClient.getPairingUrl());
76 void getBoschShcUrl() {
77 assertEquals("https://127.0.0.1:8444/testEndpoint", httpClient.getBoschShcUrl("testEndpoint"));
81 void getBoschSmartHomeUrl() {
82 assertEquals("https://127.0.0.1:8444/smarthome/endpointForTest",
83 httpClient.getBoschSmartHomeUrl("endpointForTest"));
87 void getServiceUrl() {
88 assertEquals("https://127.0.0.1:8444/smarthome/devices/testDevice/services/testService",
89 httpClient.getServiceUrl("testService", "testDevice"));
93 void getServiceStateUrl() {
94 assertEquals("https://127.0.0.1:8444/smarthome/devices/testDevice/services/testService/state",
95 httpClient.getServiceStateUrl("testService", "testDevice"));
99 void getServiceStateUrlForUserState() {
100 assertEquals("https://127.0.0.1:8444/smarthome/userdefinedstates/testDevice/state",
101 httpClient.getServiceStateUrl("testService", "testDevice", UserStateServiceState.class));
105 void isAccessPossible() throws InterruptedException {
106 assertFalse(httpClient.isAccessPossible());
110 void isOnline() throws InterruptedException {
111 assertFalse(httpClient.isOnline());
115 void isOnlineErrorResponse() throws InterruptedException, IllegalArgumentException, IllegalAccessException,
116 TimeoutException, ExecutionException {
117 BoschHttpClient mockedHttpClient = mock(BoschHttpClient.class);
118 when(mockedHttpClient.isOnline()).thenCallRealMethod();
119 when(mockedHttpClient.getPublicInformationUrl()).thenCallRealMethod();
121 // mock a logger using reflection to avoid NPEs during logger calls
122 Logger mockedLogger = mock(Logger.class);
123 List<Field> fields = ReflectionSupport.findFields(BoschHttpClient.class,
124 f -> "logger".equalsIgnoreCase(f.getName()), HierarchyTraversalMode.TOP_DOWN);
125 Field field = fields.iterator().next();
126 field.setAccessible(true);
127 field.set(mockedHttpClient, mockedLogger);
129 Request request = mock(Request.class);
130 when(mockedHttpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
131 ContentResponse response = mock(ContentResponse.class);
132 when(request.send()).thenReturn(response);
133 when(response.getStatus()).thenReturn(500);
134 assertFalse(mockedHttpClient.isOnline());
138 void isOnlineMockedResponse() throws InterruptedException, TimeoutException, ExecutionException,
139 IllegalArgumentException, IllegalAccessException {
140 BoschHttpClient mockedHttpClient = mock(BoschHttpClient.class);
141 when(mockedHttpClient.isOnline()).thenCallRealMethod();
142 when(mockedHttpClient.getPublicInformationUrl()).thenCallRealMethod();
144 // mock a logger using reflection to avoid NPEs during logger calls
145 Logger mockedLogger = mock(Logger.class);
146 List<Field> fields = ReflectionSupport.findFields(BoschHttpClient.class,
147 f -> "logger".equalsIgnoreCase(f.getName()), HierarchyTraversalMode.TOP_DOWN);
148 Field field = fields.iterator().next();
149 field.setAccessible(true);
150 field.set(mockedHttpClient, mockedLogger);
152 Request request = mock(Request.class);
153 when(mockedHttpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
154 ContentResponse response = mock(ContentResponse.class);
155 when(request.send()).thenReturn(response);
156 when(response.getStatus()).thenReturn(200);
157 when(response.getContentAsString()).thenReturn("response");
158 assertTrue(mockedHttpClient.isOnline());
162 void doPairing() throws InterruptedException {
163 assertFalse(httpClient.doPairing());
167 void createRequest() {
168 Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET);
169 assertNotNull(request);
170 assertEquals("3.2", request.getHeaders().get("api-version"));
174 void createRequestWithObject() {
175 UserStateServiceState userState = new UserStateServiceState();
176 userState.setState(true);
177 Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET, userState);
178 assertNotNull(request);
179 assertEquals("true", StandardCharsets.UTF_8.decode(request.getContent().iterator().next()).toString());
183 void createRequestForUserDefinedState() {
184 BinarySwitchServiceState binarySwitchState = new BinarySwitchServiceState();
185 binarySwitchState.on = true;
186 Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET, binarySwitchState);
187 assertNotNull(request);
188 assertEquals("{\"on\":true,\"stateType\":\"binarySwitchState\",\"@type\":\"binarySwitchState\"}",
189 StandardCharsets.UTF_8.decode(request.getContent().iterator().next()).toString());
193 void sendRequest() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
194 Request request = mock(Request.class);
195 ContentResponse response = mock(ContentResponse.class);
196 when(request.send()).thenReturn(response);
197 when(response.getStatus()).thenReturn(200);
198 when(response.getContentAsString()).thenReturn("{\"jsonrpc\": \"2.0\", \"result\": \"test result\"}");
200 SubscribeResult subscribeResult = httpClient.sendRequest(request, SubscribeResult.class,
201 SubscribeResult::isValid, null);
202 assertEquals("2.0", subscribeResult.getJsonrpc());
203 assertEquals("test result", subscribeResult.getResult());
207 void sendRequestResponseError()
208 throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
209 Request request = mock(Request.class);
210 ContentResponse response = mock(ContentResponse.class);
211 when(request.send()).thenReturn(response);
212 when(response.getStatus()).thenReturn(500);
213 ExecutionException e = assertThrows(ExecutionException.class,
214 () -> httpClient.sendRequest(request, SubscribeResult.class, SubscribeResult::isValid, null));
215 assertEquals("Send request failed with status code 500", e.getMessage());
219 void sendRequestResponseErrorWithErrorHandler()
220 throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
221 Request request = mock(Request.class);
222 ContentResponse response = mock(ContentResponse.class);
223 when(request.send()).thenReturn(response);
224 when(response.getStatus()).thenReturn(500);
225 when(response.getContentAsString()).thenReturn(
226 "{\"@type\": \"JsonRestExceptionResponseEntity\", \"errorCode\": \"500\", \"statusCode\": \"500\"}");
228 BoschSHCException e = assertThrows(BoschSHCException.class, () -> httpClient.sendRequest(request, Device.class,
229 Device::isValid, (Integer statusCode, String content) -> new BoschSHCException("test exception")));
230 assertEquals("test exception", e.getMessage());
234 void sendRequestEmptyResponse()
235 throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
236 Request request = mock(Request.class);
237 ContentResponse response = mock(ContentResponse.class);
238 when(request.send()).thenReturn(response);
239 when(response.getStatus()).thenReturn(200);
240 ExecutionException e = assertThrows(ExecutionException.class,
241 () -> httpClient.sendRequest(request, SubscribeResult.class, SubscribeResult::isValid, null));
243 "Received no content in response, expected type org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult",
248 void sendRequestInvalidResponse()
249 throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
250 Request request = mock(Request.class);
251 ContentResponse response = mock(ContentResponse.class);
252 when(request.send()).thenReturn(response);
253 when(response.getStatus()).thenReturn(200);
254 when(response.getContentAsString()).thenReturn(
255 "{\"@type\": \"JsonRestExceptionResponseEntity\", \"errorCode\": \"500\", \"statusCode\": \"500\"}");
256 ExecutionException e = assertThrows(ExecutionException.class,
257 () -> httpClient.sendRequest(request, SubscribeResult.class, sr -> false, null));
258 String actualMessage = e.getMessage();
259 assertTrue(actualMessage.contains(
260 "Received invalid content for type org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult:"));
264 void sendRequestInvalidSyntaxInResponse()
265 throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
266 Request request = mock(Request.class);
267 ContentResponse response = mock(ContentResponse.class);
268 when(request.send()).thenReturn(response);
269 when(response.getStatus()).thenReturn(200);
270 when(response.getContentAsString()).thenReturn("{\"@type\": \"JsonRestExceptionResponseEntity}");
271 ExecutionException e = assertThrows(ExecutionException.class,
272 () -> httpClient.sendRequest(request, SubscribeResult.class, sr -> false, null));
274 "Received invalid content in response, expected type org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult: com.google.gson.stream.MalformedJsonException: Unterminated string at line 1 column 44 path $.@type",