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);
166 void createRequestWithObject() {
167 BinarySwitchServiceState binarySwitchState = new BinarySwitchServiceState();
168 binarySwitchState.on = true;
169 Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET, binarySwitchState);
170 assertNotNull(request);
171 assertEquals("{\"on\":true,\"stateType\":\"binarySwitchState\",\"@type\":\"binarySwitchState\"}",
172 StandardCharsets.UTF_8.decode(request.getContent().iterator().next()).toString());
176 void sendRequest() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
177 Request request = mock(Request.class);
178 ContentResponse response = mock(ContentResponse.class);
179 when(request.send()).thenReturn(response);
180 when(response.getStatus()).thenReturn(200);
181 when(response.getContentAsString()).thenReturn("{\"jsonrpc\": \"2.0\", \"result\": \"test result\"}");
183 SubscribeResult subscribeResult = httpClient.sendRequest(request, SubscribeResult.class,
184 SubscribeResult::isValid, null);
185 assertEquals("2.0", subscribeResult.getJsonrpc());
186 assertEquals("test result", subscribeResult.getResult());
190 void sendRequestResponseError()
191 throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
192 Request request = mock(Request.class);
193 ContentResponse response = mock(ContentResponse.class);
194 when(request.send()).thenReturn(response);
195 when(response.getStatus()).thenReturn(500);
196 ExecutionException e = assertThrows(ExecutionException.class,
197 () -> httpClient.sendRequest(request, SubscribeResult.class, SubscribeResult::isValid, null));
198 assertEquals("Request failed with status code 500", e.getMessage());
202 void sendRequestResponseErrorWithErrorHandler()
203 throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
204 Request request = mock(Request.class);
205 ContentResponse response = mock(ContentResponse.class);
206 when(request.send()).thenReturn(response);
207 when(response.getStatus()).thenReturn(500);
208 when(response.getContentAsString()).thenReturn(
209 "{\"@type\": \"JsonRestExceptionResponseEntity\", \"errorCode\": \"500\", \"statusCode\": \"500\"}");
211 BoschSHCException e = assertThrows(BoschSHCException.class, () -> httpClient.sendRequest(request, Device.class,
212 Device::isValid, (Integer statusCode, String content) -> {
213 return new BoschSHCException("test exception");
215 assertEquals("test exception", e.getMessage());
219 void sendRequestEmptyResponse()
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(200);
225 ExecutionException e = assertThrows(ExecutionException.class,
226 () -> httpClient.sendRequest(request, SubscribeResult.class, SubscribeResult::isValid, null));
228 "Received no content in response, expected type org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult",
233 void sendRequestInvalidResponse()
234 throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
235 Request request = mock(Request.class);
236 ContentResponse response = mock(ContentResponse.class);
237 when(request.send()).thenReturn(response);
238 when(response.getStatus()).thenReturn(200);
239 when(response.getContentAsString()).thenReturn(
240 "{\"@type\": \"JsonRestExceptionResponseEntity\", \"errorCode\": \"500\", \"statusCode\": \"500\"}");
241 ExecutionException e = assertThrows(ExecutionException.class,
242 () -> httpClient.sendRequest(request, SubscribeResult.class, sr -> {
245 String actualMessage = e.getMessage();
246 assertTrue(actualMessage.contains(
247 "Received invalid content for type org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult:"));
251 void sendRequestInvalidSyntaxInResponse()
252 throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
253 Request request = mock(Request.class);
254 ContentResponse response = mock(ContentResponse.class);
255 when(request.send()).thenReturn(response);
256 when(response.getStatus()).thenReturn(200);
257 when(response.getContentAsString()).thenReturn("{\"@type\": \"JsonRestExceptionResponseEntity}");
258 ExecutionException e = assertThrows(ExecutionException.class,
259 () -> httpClient.sendRequest(request, SubscribeResult.class, sr -> {
263 "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",