]> git.basschouten.com Git - openhab-addons.git/blob
9d994e1996ea55d23dd52ee158c77c3561ec1dd0
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.boschshc.internal.devices.bridge;
14
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.mockito.ArgumentMatchers.*;
17 import static org.mockito.Mockito.*;
18
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;
24
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;
42
43 /**
44  * Tests cases for {@link BoschHttpClient}.
45  *
46  * @author Gerd Zanker - Initial contribution
47  */
48 @NonNullByDefault
49 class BoschHttpClientTest {
50
51     private @NonNullByDefault({}) BoschHttpClient httpClient;
52
53     @BeforeAll
54     static void beforeAll() {
55         BoschSslUtilTest.prepareTempFolderForKeyStore();
56     }
57
58     @BeforeEach
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);
63     }
64
65     @Test
66     void getPublicInformationUrl() {
67         assertEquals("https://127.0.0.1:8446/smarthome/public/information", httpClient.getPublicInformationUrl());
68     }
69
70     @Test
71     void getPairingUrl() {
72         assertEquals("https://127.0.0.1:8443/smarthome/clients", httpClient.getPairingUrl());
73     }
74
75     @Test
76     void getBoschShcUrl() {
77         assertEquals("https://127.0.0.1:8444/testEndpoint", httpClient.getBoschShcUrl("testEndpoint"));
78     }
79
80     @Test
81     void getBoschSmartHomeUrl() {
82         assertEquals("https://127.0.0.1:8444/smarthome/endpointForTest",
83                 httpClient.getBoschSmartHomeUrl("endpointForTest"));
84     }
85
86     @Test
87     void getServiceUrl() {
88         assertEquals("https://127.0.0.1:8444/smarthome/devices/testDevice/services/testService",
89                 httpClient.getServiceUrl("testService", "testDevice"));
90     }
91
92     @Test
93     void getServiceStateUrl() {
94         assertEquals("https://127.0.0.1:8444/smarthome/devices/testDevice/services/testService/state",
95                 httpClient.getServiceStateUrl("testService", "testDevice"));
96     }
97
98     @Test
99     void getServiceStateUrlForUserState() {
100         assertEquals("https://127.0.0.1:8444/smarthome/userdefinedstates/testDevice/state",
101                 httpClient.getServiceStateUrl("testService", "testDevice", UserStateServiceState.class));
102     }
103
104     @Test
105     void isAccessPossible() throws InterruptedException {
106         assertFalse(httpClient.isAccessPossible());
107     }
108
109     @Test
110     void isOnline() throws InterruptedException {
111         assertFalse(httpClient.isOnline());
112     }
113
114     @Test
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();
120
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);
128
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());
135     }
136
137     @Test
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();
143
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);
151
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());
159     }
160
161     @Test
162     void doPairing() throws InterruptedException {
163         assertFalse(httpClient.doPairing());
164     }
165
166     @Test
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"));
171     }
172
173     @Test
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());
180     }
181
182     @Test
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());
190     }
191
192     @Test
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\"}");
199
200         SubscribeResult subscribeResult = httpClient.sendRequest(request, SubscribeResult.class,
201                 SubscribeResult::isValid, null);
202         assertEquals("2.0", subscribeResult.getJsonrpc());
203         assertEquals("test result", subscribeResult.getResult());
204     }
205
206     @Test
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());
216     }
217
218     @Test
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\"}");
227
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());
231     }
232
233     @Test
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));
242         assertEquals(
243                 "Received no content in response, expected type org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult",
244                 e.getMessage());
245     }
246
247     @Test
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:"));
261     }
262
263     @Test
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));
273         assertEquals(
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",
275                 e.getMessage());
276     }
277 }