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.slf4j.Logger;
43 * Tests cases for {@link BoschHttpClient}.
45 * @author Gerd Zanker - Initial contribution
48 class BoschHttpClientTest {
50 private @NonNullByDefault({}) BoschHttpClient httpClient;
53 static void beforeAll() {
54 BoschSslUtilTest.prepareTempFolderForKeyStore();
58 void beforeEach() throws PairingFailedException {
59 SslContextFactory sslFactory = new BoschSslUtil("127.0.0.1").getSslContextFactory();
60 httpClient = new BoschHttpClient("127.0.0.1", "dummy", sslFactory);
61 assertNotNull(httpClient);
65 void getPublicInformationUrl() {
66 assertEquals("https://127.0.0.1:8446/smarthome/public/information", httpClient.getPublicInformationUrl());
70 void getPairingUrl() {
71 assertEquals("https://127.0.0.1:8443/smarthome/clients", httpClient.getPairingUrl());
75 void getBoschShcUrl() {
76 assertEquals("https://127.0.0.1:8444/testEndpoint", httpClient.getBoschShcUrl("testEndpoint"));
80 void getBoschSmartHomeUrl() {
81 assertEquals("https://127.0.0.1:8444/smarthome/endpointForTest",
82 httpClient.getBoschSmartHomeUrl("endpointForTest"));
86 void getServiceUrl() {
87 assertEquals("https://127.0.0.1:8444/smarthome/devices/testDevice/services/testService",
88 httpClient.getServiceUrl("testService", "testDevice"));
92 void getServiceStateUrl() {
93 assertEquals("https://127.0.0.1:8444/smarthome/devices/testDevice/services/testService/state",
94 httpClient.getServiceStateUrl("testService", "testDevice"));
98 void isAccessPossible() throws InterruptedException {
99 assertFalse(httpClient.isAccessPossible());
103 void isOnline() throws InterruptedException {
104 assertFalse(httpClient.isOnline());
108 void isOnlineErrorResponse() throws InterruptedException, IllegalArgumentException, IllegalAccessException,
109 TimeoutException, ExecutionException {
110 BoschHttpClient mockedHttpClient = mock(BoschHttpClient.class);
111 when(mockedHttpClient.isOnline()).thenCallRealMethod();
112 when(mockedHttpClient.getPublicInformationUrl()).thenCallRealMethod();
114 // mock a logger using reflection to avoid NPEs during logger calls
115 Logger mockedLogger = mock(Logger.class);
116 List<Field> fields = ReflectionSupport.findFields(BoschHttpClient.class,
117 f -> f.getName().equalsIgnoreCase("logger"), HierarchyTraversalMode.TOP_DOWN);
118 Field field = fields.iterator().next();
119 field.setAccessible(true);
120 field.set(mockedHttpClient, mockedLogger);
122 Request request = mock(Request.class);
123 when(mockedHttpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
124 ContentResponse response = mock(ContentResponse.class);
125 when(request.send()).thenReturn(response);
126 when(response.getStatus()).thenReturn(500);
127 assertFalse(mockedHttpClient.isOnline());
131 void isOnlineMockedResponse() throws InterruptedException, TimeoutException, ExecutionException,
132 IllegalArgumentException, IllegalAccessException {
133 BoschHttpClient mockedHttpClient = mock(BoschHttpClient.class);
134 when(mockedHttpClient.isOnline()).thenCallRealMethod();
135 when(mockedHttpClient.getPublicInformationUrl()).thenCallRealMethod();
137 // mock a logger using reflection to avoid NPEs during logger calls
138 Logger mockedLogger = mock(Logger.class);
139 List<Field> fields = ReflectionSupport.findFields(BoschHttpClient.class,
140 f -> f.getName().equalsIgnoreCase("logger"), HierarchyTraversalMode.TOP_DOWN);
141 Field field = fields.iterator().next();
142 field.setAccessible(true);
143 field.set(mockedHttpClient, mockedLogger);
145 Request request = mock(Request.class);
146 when(mockedHttpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
147 ContentResponse response = mock(ContentResponse.class);
148 when(request.send()).thenReturn(response);
149 when(response.getStatus()).thenReturn(200);
150 when(response.getContentAsString()).thenReturn("response");
151 assertTrue(mockedHttpClient.isOnline());
155 void doPairing() throws InterruptedException {
156 assertFalse(httpClient.doPairing());
160 void createRequest() {
161 Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET);
162 assertNotNull(request);
163 assertEquals("3.2", request.getHeaders().get("api-version"));
167 void createRequestWithObject() {
168 BinarySwitchServiceState binarySwitchState = new BinarySwitchServiceState();
169 binarySwitchState.on = true;
170 Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET, binarySwitchState);
171 assertNotNull(request);
172 assertEquals("{\"on\":true,\"stateType\":\"binarySwitchState\",\"@type\":\"binarySwitchState\"}",
173 StandardCharsets.UTF_8.decode(request.getContent().iterator().next()).toString());
177 void sendRequest() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
178 Request request = mock(Request.class);
179 ContentResponse response = mock(ContentResponse.class);
180 when(request.send()).thenReturn(response);
181 when(response.getStatus()).thenReturn(200);
182 when(response.getContentAsString()).thenReturn("{\"jsonrpc\": \"2.0\", \"result\": \"test result\"}");
184 SubscribeResult subscribeResult = httpClient.sendRequest(request, SubscribeResult.class,
185 SubscribeResult::isValid, null);
186 assertEquals("2.0", subscribeResult.getJsonrpc());
187 assertEquals("test result", subscribeResult.getResult());
191 void sendRequestResponseError()
192 throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
193 Request request = mock(Request.class);
194 ContentResponse response = mock(ContentResponse.class);
195 when(request.send()).thenReturn(response);
196 when(response.getStatus()).thenReturn(500);
197 ExecutionException e = assertThrows(ExecutionException.class,
198 () -> httpClient.sendRequest(request, SubscribeResult.class, SubscribeResult::isValid, null));
199 assertEquals("Send request failed with status code 500", e.getMessage());
203 void sendRequestResponseErrorWithErrorHandler()
204 throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
205 Request request = mock(Request.class);
206 ContentResponse response = mock(ContentResponse.class);
207 when(request.send()).thenReturn(response);
208 when(response.getStatus()).thenReturn(500);
209 when(response.getContentAsString()).thenReturn(
210 "{\"@type\": \"JsonRestExceptionResponseEntity\", \"errorCode\": \"500\", \"statusCode\": \"500\"}");
212 BoschSHCException e = assertThrows(BoschSHCException.class, () -> httpClient.sendRequest(request, Device.class,
213 Device::isValid, (Integer statusCode, String content) -> {
214 return new BoschSHCException("test exception");
216 assertEquals("test exception", e.getMessage());
220 void sendRequestEmptyResponse()
221 throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
222 Request request = mock(Request.class);
223 ContentResponse response = mock(ContentResponse.class);
224 when(request.send()).thenReturn(response);
225 when(response.getStatus()).thenReturn(200);
226 ExecutionException e = assertThrows(ExecutionException.class,
227 () -> httpClient.sendRequest(request, SubscribeResult.class, SubscribeResult::isValid, null));
229 "Received no content in response, expected type org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult",
234 void sendRequestInvalidResponse()
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 when(response.getContentAsString()).thenReturn(
241 "{\"@type\": \"JsonRestExceptionResponseEntity\", \"errorCode\": \"500\", \"statusCode\": \"500\"}");
242 ExecutionException e = assertThrows(ExecutionException.class,
243 () -> httpClient.sendRequest(request, SubscribeResult.class, sr -> {
246 String actualMessage = e.getMessage();
247 assertTrue(actualMessage.contains(
248 "Received invalid content for type org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult:"));
252 void sendRequestInvalidSyntaxInResponse()
253 throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
254 Request request = mock(Request.class);
255 ContentResponse response = mock(ContentResponse.class);
256 when(request.send()).thenReturn(response);
257 when(response.getStatus()).thenReturn(200);
258 when(response.getContentAsString()).thenReturn("{\"@type\": \"JsonRestExceptionResponseEntity}");
259 ExecutionException e = assertThrows(ExecutionException.class,
260 () -> httpClient.sendRequest(request, SubscribeResult.class, sr -> {
264 "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",