]> git.basschouten.com Git - openhab-addons.git/blob
734551a778427a3682d085a22976dc053596240e
[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.hamcrest.MatcherAssert.assertThat;
16 import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
17 import static org.junit.jupiter.api.Assertions.assertEquals;
18 import static org.junit.jupiter.api.Assertions.assertFalse;
19 import static org.junit.jupiter.api.Assertions.assertNotNull;
20 import static org.junit.jupiter.api.Assertions.assertSame;
21 import static org.junit.jupiter.api.Assertions.assertThrows;
22 import static org.junit.jupiter.api.Assertions.assertTrue;
23 import static org.mockito.ArgumentMatchers.any;
24 import static org.mockito.ArgumentMatchers.anyString;
25 import static org.mockito.ArgumentMatchers.argThat;
26 import static org.mockito.ArgumentMatchers.contains;
27 import static org.mockito.ArgumentMatchers.eq;
28 import static org.mockito.ArgumentMatchers.isNull;
29 import static org.mockito.ArgumentMatchers.same;
30 import static org.mockito.Mockito.mock;
31 import static org.mockito.Mockito.verify;
32 import static org.mockito.Mockito.verifyNoInteractions;
33 import static org.mockito.Mockito.verifyNoMoreInteractions;
34 import static org.mockito.Mockito.when;
35
36 import java.io.IOException;
37 import java.nio.file.Files;
38 import java.nio.file.Path;
39 import java.nio.file.Paths;
40 import java.util.ArrayList;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.UUID;
45 import java.util.concurrent.ExecutionException;
46 import java.util.concurrent.TimeoutException;
47 import java.util.function.BiFunction;
48
49 import org.eclipse.jdt.annotation.NonNullByDefault;
50 import org.eclipse.jdt.annotation.Nullable;
51 import org.eclipse.jetty.client.api.ContentResponse;
52 import org.eclipse.jetty.client.api.Request;
53 import org.eclipse.jetty.http.HttpMethod;
54 import org.junit.jupiter.api.AfterEach;
55 import org.junit.jupiter.api.BeforeAll;
56 import org.junit.jupiter.api.BeforeEach;
57 import org.junit.jupiter.api.Test;
58 import org.junit.jupiter.params.ParameterizedTest;
59 import org.junit.jupiter.params.provider.MethodSource;
60 import org.mockito.ArgumentCaptor;
61 import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
62 import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
63 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device;
64 import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
65 import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceTest;
66 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Faults;
67 import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult;
68 import org.openhab.binding.boschshc.internal.devices.bridge.dto.PublicInformation;
69 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Room;
70 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario;
71 import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult;
72 import org.openhab.binding.boschshc.internal.devices.bridge.dto.UserDefinedState;
73 import org.openhab.binding.boschshc.internal.devices.bridge.dto.UserDefinedStateTest;
74 import org.openhab.binding.boschshc.internal.discovery.ThingDiscoveryService;
75 import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
76 import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
77 import org.openhab.binding.boschshc.internal.services.binaryswitch.dto.BinarySwitchServiceState;
78 import org.openhab.binding.boschshc.internal.services.intrusion.actions.arm.dto.ArmActionRequest;
79 import org.openhab.binding.boschshc.internal.services.intrusion.dto.AlarmState;
80 import org.openhab.binding.boschshc.internal.services.intrusion.dto.ArmingState;
81 import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDetectionSystemState;
82 import org.openhab.binding.boschshc.internal.services.shuttercontact.ShutterContactState;
83 import org.openhab.binding.boschshc.internal.services.shuttercontact.dto.ShutterContactServiceState;
84 import org.openhab.core.config.core.Configuration;
85 import org.openhab.core.library.types.OnOffType;
86 import org.openhab.core.library.types.StringType;
87 import org.openhab.core.thing.Bridge;
88 import org.openhab.core.thing.Channel;
89 import org.openhab.core.thing.ChannelUID;
90 import org.openhab.core.thing.Thing;
91 import org.openhab.core.thing.ThingStatus;
92 import org.openhab.core.thing.ThingStatusDetail;
93 import org.openhab.core.thing.ThingStatusInfo;
94 import org.openhab.core.thing.binding.ThingHandlerCallback;
95 import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
96
97 import com.google.gson.JsonElement;
98 import com.google.gson.JsonParser;
99 import com.google.gson.JsonPrimitive;
100
101 /**
102  * Unit tests for the {@link BridgeHandler}.
103  *
104  * @author David Pace - Initial contribution
105  *
106  */
107 @NonNullByDefault
108 class BridgeHandlerTest {
109
110     private @NonNullByDefault({}) BridgeHandler fixture;
111
112     private @NonNullByDefault({}) BoschHttpClient httpClient;
113     private @NonNullByDefault({}) ThingHandlerCallback thingHandlerCallback;
114     private @NonNullByDefault({}) Bridge thing;
115     private @NonNullByDefault({}) Configuration bridgeConfiguration;
116
117     @BeforeAll
118     static void beforeAll() throws IOException {
119         Path mavenTargetFolder = Paths.get("target");
120         assertTrue(Files.exists(mavenTargetFolder), "Maven target folder does not exist.");
121         System.setProperty("openhab.userdata", mavenTargetFolder.toFile().getAbsolutePath());
122         Path etc = mavenTargetFolder.resolve("etc");
123         if (!Files.exists(etc)) {
124             Files.createDirectory(etc);
125         }
126     }
127
128     @BeforeEach
129     void beforeEach() throws Exception {
130         Bridge bridge = mock(Bridge.class);
131         fixture = new BridgeHandler(bridge);
132
133         thingHandlerCallback = mock(ThingHandlerCallback.class);
134         fixture.setCallback(thingHandlerCallback);
135
136         bridgeConfiguration = new Configuration();
137         Map<@Nullable String, @Nullable Object> properties = new HashMap<>();
138         properties.put("ipAddress", "localhost");
139         properties.put("password", "test");
140         bridgeConfiguration.setProperties(properties);
141
142         thing = mock(Bridge.class);
143         when(thing.getConfiguration()).thenReturn(bridgeConfiguration);
144         // this calls initialize() as well
145         fixture.thingUpdated(thing);
146
147         // shut down the real HTTP client
148         if (fixture.httpClient != null) {
149             fixture.httpClient.stop();
150         }
151
152         // use a mocked HTTP client
153         httpClient = mock(BoschHttpClient.class);
154         fixture.httpClient = httpClient;
155     }
156
157     @Test
158     void postAction() throws InterruptedException, TimeoutException, ExecutionException {
159         String endpoint = "/intrusion/actions/arm";
160         String url = "https://127.0.0.1:8444/smarthome/intrusion/actions/arm";
161         when(httpClient.getBoschSmartHomeUrl(endpoint)).thenReturn(url);
162         Request mockRequest = mock(Request.class);
163         when(httpClient.createRequest(anyString(), any(), any())).thenReturn(mockRequest);
164         ArmActionRequest request = new ArmActionRequest();
165         request.profileId = "0";
166
167         fixture.postAction(endpoint, request);
168         verify(httpClient).createRequest(eq(url), same(HttpMethod.POST), same(request));
169         verify(mockRequest).send();
170     }
171
172     @Test
173     void postActionWithoutRequestBody() throws InterruptedException, TimeoutException, ExecutionException {
174         String endpoint = "/intrusion/actions/disarm";
175         String url = "https://127.0.0.1:8444/smarthome/intrusion/actions/disarm";
176         when(httpClient.getBoschSmartHomeUrl(endpoint)).thenReturn(url);
177         Request mockRequest = mock(Request.class);
178         when(httpClient.createRequest(anyString(), any(), any())).thenReturn(mockRequest);
179
180         fixture.postAction(endpoint);
181         verify(httpClient).createRequest(eq(url), same(HttpMethod.POST), isNull());
182         verify(mockRequest).send();
183     }
184
185     @Test
186     void initialAccessHttpClientOffline() {
187         fixture.initialAccess(httpClient);
188     }
189
190     @Test
191     void initialAccessHttpClientOnline() throws InterruptedException {
192         when(httpClient.isOnline()).thenReturn(true);
193         fixture.initialAccess(httpClient);
194     }
195
196     @Test
197     void initialAccessAccessPossible()
198             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
199         when(httpClient.isOnline()).thenReturn(true);
200         when(httpClient.isAccessPossible()).thenReturn(true);
201         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
202         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
203
204         // mock a request and response to obtain rooms
205         Request roomsRequest = mock(Request.class);
206         ContentResponse roomsResponse = mock(ContentResponse.class);
207         when(roomsResponse.getStatus()).thenReturn(200);
208         when(roomsResponse.getContentAsString()).thenReturn(
209                 "[{\"@type\":\"room\",\"id\":\"hz_1\",\"iconId\":\"icon_room_bedroom\",\"name\":\"Bedroom\"}]");
210         when(roomsRequest.send()).thenReturn(roomsResponse);
211         when(httpClient.createRequest(contains("/rooms"), same(HttpMethod.GET))).thenReturn(roomsRequest);
212
213         // mock a request and response to obtain devices
214         Request devicesRequest = mock(Request.class);
215         ContentResponse devicesResponse = mock(ContentResponse.class);
216         when(devicesResponse.getStatus()).thenReturn(200);
217         when(devicesResponse.getContentAsString()).thenReturn("""
218                 [{"@type":"device",
219                  "rootDeviceId":"64-da-a0-02-14-9b",
220                  "id":"hdm:HomeMaticIP:3014F711A00004953859F31B",
221                  "deviceServiceIds":["PowerMeter","PowerSwitch","PowerSwitchProgram","Routing"],
222                  "manufacturer":"BOSCH",
223                  "roomId":"hz_3",
224                  "deviceModel":"PSM",
225                  "serial":"3014F711A00004953859F31B",
226                  "profile":"GENERIC",
227                  "name":"Coffee Machine",
228                  "status":"AVAILABLE",
229                  "childDeviceIds":[]
230                  }]\
231                 """);
232         when(devicesRequest.send()).thenReturn(devicesResponse);
233         when(httpClient.createRequest(contains("/devices"), same(HttpMethod.GET))).thenReturn(devicesRequest);
234
235         SubscribeResult subscribeResult = new SubscribeResult();
236         when(httpClient.sendRequest(any(), same(SubscribeResult.class), any(), any())).thenReturn(subscribeResult);
237
238         Request longPollRequest = mock(Request.class);
239         when(httpClient.createRequest(anyString(), same(HttpMethod.POST),
240                 argThat((JsonRpcRequest r) -> "RE/longPoll".equals(r.method)))).thenReturn(longPollRequest);
241
242         ThingDiscoveryService thingDiscoveryListener = mock(ThingDiscoveryService.class);
243         fixture.registerDiscoveryListener(thingDiscoveryListener);
244
245         fixture.initialAccess(httpClient);
246
247         verify(thingHandlerCallback).statusUpdated(any(),
248                 eq(ThingStatusInfoBuilder.create(ThingStatus.ONLINE, ThingStatusDetail.NONE).build()));
249         verify(thingDiscoveryListener).doScan();
250     }
251
252     @Test
253     void initialAccessNoBridgeAccess() throws InterruptedException, TimeoutException, ExecutionException {
254         when(httpClient.isOnline()).thenReturn(true);
255         when(httpClient.isAccessPossible()).thenReturn(true);
256         Request request = mock(Request.class);
257         when(httpClient.createRequest(any(), same(HttpMethod.GET))).thenReturn(request);
258         ContentResponse response = mock(ContentResponse.class);
259         when(request.send()).thenReturn(response);
260         when(response.getStatus()).thenReturn(400);
261
262         fixture.initialAccess(httpClient);
263
264         verify(thingHandlerCallback).statusUpdated(same(thing),
265                 argThat(status -> status.getStatus().equals(ThingStatus.OFFLINE)
266                         && status.getStatusDetail().equals(ThingStatusDetail.COMMUNICATION_ERROR)));
267     }
268
269     @Test
270     void getState() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
271         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
272         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
273         Request request = mock(Request.class);
274         when(request.header(anyString(), anyString())).thenReturn(request);
275         ContentResponse response = mock(ContentResponse.class);
276         when(response.getStatus()).thenReturn(200);
277         when(response.getContentAsString()).thenReturn("""
278                 {
279                      "@type": "systemState",
280                      "systemAvailability": {
281                          "@type": "systemAvailabilityState",
282                          "available": true,
283                          "deleted": false
284                      },
285                      "armingState": {
286                          "@type": "armingState",
287                          "state": "SYSTEM_DISARMED",
288                          "deleted": false
289                      },
290                      "alarmState": {
291                          "@type": "alarmState",
292                          "value": "ALARM_OFF",
293                          "incidents": [],
294                          "deleted": false
295                      },
296                      "activeConfigurationProfile": {
297                          "@type": "activeConfigurationProfile",
298                          "deleted": false
299                      },
300                      "securityGapState": {
301                          "@type": "securityGapState",
302                          "securityGaps": [],
303                          "deleted": false
304                      },
305                      "deleted": false
306                  }\
307                 """);
308         when(request.send()).thenReturn(response);
309         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
310
311         IntrusionDetectionSystemState state = fixture.getState("intrusion/states/system",
312                 IntrusionDetectionSystemState.class);
313         assertNotNull(state);
314         assertTrue(state.systemAvailability.available);
315         assertSame(AlarmState.ALARM_OFF, state.alarmState.value);
316         assertSame(ArmingState.SYSTEM_DISARMED, state.armingState.state);
317     }
318
319     @Test
320     void getDeviceState() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
321         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
322         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
323         when(httpClient.getServiceStateUrl(anyString(), anyString(), any())).thenCallRealMethod();
324         when(httpClient.getServiceStateUrl(anyString(), anyString())).thenCallRealMethod();
325
326         Request request = mock(Request.class);
327         when(request.header(anyString(), anyString())).thenReturn(request);
328         ContentResponse response = mock(ContentResponse.class);
329         when(response.getStatus()).thenReturn(200);
330         when(response.getContentAsString())
331                 .thenReturn("{\n" + "   \"@type\": \"shutterContactState\",\n" + "   \"value\": \"OPEN\"\n" + " }");
332         when(request.send()).thenReturn(response);
333         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
334
335         ShutterContactServiceState state = fixture.getState("hdm:HomeMaticIP:3014D711A000009D545DEB39D",
336                 "ShutterContact", ShutterContactServiceState.class);
337         assertNotNull(state);
338         assertSame(ShutterContactState.OPEN, state.value);
339     }
340
341     @Test
342     void getDeviceInfo() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
343         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
344         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
345
346         Request request = mock(Request.class);
347         when(request.header(anyString(), anyString())).thenReturn(request);
348         ContentResponse response = mock(ContentResponse.class);
349         when(response.getStatus()).thenReturn(200);
350         when(request.send()).thenReturn(response);
351         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
352         when(httpClient.sendRequest(same(request), same(Device.class), any(), any()))
353                 .thenReturn(DeviceTest.createTestDevice());
354
355         String deviceId = "hdm:HomeMaticIP:3014F711A00004953859F31B";
356         Device device = fixture.getDeviceInfo(deviceId);
357         assertEquals(deviceId, device.id);
358     }
359
360     @Test
361     void getDeviceInfoErrorCases()
362             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
363         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
364         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
365
366         Request request = mock(Request.class);
367         when(request.header(anyString(), anyString())).thenReturn(request);
368         ContentResponse response = mock(ContentResponse.class);
369         when(response.getStatus()).thenReturn(200);
370         when(request.send()).thenReturn(response);
371         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
372
373         @SuppressWarnings("unchecked")
374         ArgumentCaptor<BiFunction<Integer, String, BoschSHCException>> errorResponseHandlerCaptor = ArgumentCaptor
375                 .forClass(BiFunction.class);
376
377         when(httpClient.sendRequest(same(request), same(Device.class), any(), errorResponseHandlerCaptor.capture()))
378                 .thenReturn(DeviceTest.createTestDevice());
379
380         String deviceId = "hdm:HomeMaticIP:3014F711A00004953859F31B";
381         fixture.getDeviceInfo(deviceId);
382
383         BiFunction<Integer, String, BoschSHCException> errorResponseHandler = errorResponseHandlerCaptor.getValue();
384         Exception e = errorResponseHandler.apply(500,
385                 "{\"@type\":\"JsonRestExceptionResponseEntity\",\"errorCode\": \"testErrorCode\",\"statusCode\": 500}");
386         assertEquals(
387                 "Request for info of device hdm:HomeMaticIP:3014F711A00004953859F31B failed with status code 500 and error code testErrorCode",
388                 e.getMessage());
389
390         e = errorResponseHandler.apply(404,
391                 "{\"@type\":\"JsonRestExceptionResponseEntity\",\"errorCode\": \"ENTITY_NOT_FOUND\",\"statusCode\": 404}");
392         assertNotNull(e);
393
394         e = errorResponseHandler.apply(500, "");
395         assertEquals("Request for info of device hdm:HomeMaticIP:3014F711A00004953859F31B failed with status code 500",
396                 e.getMessage());
397     }
398
399     @Test
400     void getServiceData() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
401         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
402         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
403         when(httpClient.getServiceUrl(anyString(), anyString())).thenCallRealMethod();
404
405         Request request = mock(Request.class);
406         when(request.header(anyString(), anyString())).thenReturn(request);
407         ContentResponse response = mock(ContentResponse.class);
408         when(response.getStatus()).thenReturn(200);
409         when(response.getContentAsString()).thenReturn("""
410                 {
411                     "@type":"DeviceServiceData",
412                     "path":"/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel",
413                     "id":"BatteryLevel",
414                     "deviceId":"hdm:ZigBee:000d6f0004b93361",
415                     "faults":{\s
416                         "entries":[
417                           {
418                             "type":"LOW_BATTERY",
419                             "category":"WARNING"
420                           }
421                         ]
422                     }
423                 }\
424                 """);
425         when(request.send()).thenReturn(response);
426         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
427
428         DeviceServiceData serviceData = fixture.getServiceData("hdm:ZigBee:000d6f0004b93361", "BatteryLevel");
429         assertNotNull(serviceData);
430         Faults faults = serviceData.faults;
431         assertNotNull(faults);
432         assertEquals("LOW_BATTERY", faults.entries.get(0).type);
433     }
434
435     @Test
436     void getServiceDataError() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
437         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
438         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
439         when(httpClient.getServiceUrl(anyString(), anyString())).thenCallRealMethod();
440
441         Request request = mock(Request.class);
442         when(request.header(anyString(), anyString())).thenReturn(request);
443         ContentResponse response = mock(ContentResponse.class);
444         when(response.getStatus()).thenReturn(500);
445         when(response.getContentAsString()).thenReturn(
446                 "{\"@type\":\"JsonRestExceptionResponseEntity\",\"errorCode\": \"testErrorCode\",\"statusCode\": 500}");
447         when(request.send()).thenReturn(response);
448         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
449         when(httpClient.sendRequest(same(request), same(Device.class), any(), any()))
450                 .thenReturn(DeviceTest.createTestDevice());
451
452         BoschSHCException e = assertThrows(BoschSHCException.class,
453                 () -> fixture.getServiceData("hdm:ZigBee:000d6f0004b93361", "BatteryLevel"));
454         assertEquals(
455                 "State request with URL https://null:8444/smarthome/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel failed with status code 500 and error code testErrorCode",
456                 e.getMessage());
457     }
458
459     @Test
460     void getServiceDataErrorNoRestExceptionResponse()
461             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
462         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
463         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
464         when(httpClient.getServiceUrl(anyString(), anyString())).thenCallRealMethod();
465
466         Request request = mock(Request.class);
467         when(request.header(anyString(), anyString())).thenReturn(request);
468         ContentResponse response = mock(ContentResponse.class);
469         when(response.getStatus()).thenReturn(500);
470         when(response.getContentAsString()).thenReturn("");
471         when(request.send()).thenReturn(response);
472         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
473
474         BoschSHCException e = assertThrows(BoschSHCException.class,
475                 () -> fixture.getServiceData("hdm:ZigBee:000d6f0004b93361", "BatteryLevel"));
476         assertEquals(
477                 "State request with URL https://null:8444/smarthome/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel failed with status code 500",
478                 e.getMessage());
479     }
480
481     @Test
482     void putState() throws InterruptedException, TimeoutException, ExecutionException {
483         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
484         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
485         when(httpClient.getServiceStateUrl(anyString(), anyString())).thenCallRealMethod();
486         when(httpClient.getServiceStateUrl(anyString(), anyString(), any())).thenCallRealMethod();
487
488         Request request = mock(Request.class);
489         when(request.header(anyString(), anyString())).thenReturn(request);
490         ContentResponse response = mock(ContentResponse.class);
491
492         when(httpClient.createRequest(anyString(), same(HttpMethod.PUT), any(BinarySwitchServiceState.class)))
493                 .thenReturn(request);
494         when(request.send()).thenReturn(response);
495
496         BinarySwitchServiceState binarySwitchState = new BinarySwitchServiceState();
497         binarySwitchState.on = true;
498         fixture.putState("hdm:ZigBee:f0d1b80000f2a3e9", "BinarySwitch", binarySwitchState);
499     }
500
501     @Test
502     void getUserStateInfo() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
503         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
504         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
505         String stateId = UUID.randomUUID().toString();
506
507         Request request = mock(Request.class);
508         when(request.header(anyString(), anyString())).thenReturn(request);
509         ContentResponse response = mock(ContentResponse.class);
510         when(response.getStatus()).thenReturn(200);
511         when(request.send()).thenReturn(response);
512         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
513         when(httpClient.sendRequest(same(request), same(UserDefinedState.class), any(), any()))
514                 .thenReturn(UserDefinedStateTest.createTestState(stateId));
515
516         UserDefinedState userState = fixture.getUserStateInfo(stateId);
517         assertEquals(stateId, userState.getId());
518     }
519
520     @Test
521     void getUserStateInfoErrorCases()
522             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
523         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
524         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
525
526         Request request = mock(Request.class);
527         when(request.header(anyString(), anyString())).thenReturn(request);
528         ContentResponse response = mock(ContentResponse.class);
529         when(response.getStatus()).thenReturn(200);
530         when(request.send()).thenReturn(response);
531         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
532
533         @SuppressWarnings("unchecked")
534         ArgumentCaptor<BiFunction<Integer, String, BoschSHCException>> errorResponseHandlerCaptor = ArgumentCaptor
535                 .forClass(BiFunction.class);
536
537         String stateId = "abcdef";
538         when(httpClient.sendRequest(same(request), same(UserDefinedState.class), any(),
539                 errorResponseHandlerCaptor.capture())).thenReturn(UserDefinedStateTest.createTestState(stateId));
540
541         fixture.getUserStateInfo(stateId);
542
543         BiFunction<Integer, String, BoschSHCException> errorResponseHandler = errorResponseHandlerCaptor.getValue();
544         Exception e = errorResponseHandler.apply(500,
545                 "{\"@type\":\"JsonRestExceptionResponseEntity\",\"errorCode\": \"testErrorCode\",\"statusCode\": 500}");
546         assertEquals(
547                 "Request for info of user-defined state abcdef failed with status code 500 and error code testErrorCode",
548                 e.getMessage());
549
550         e = errorResponseHandler.apply(404,
551                 "{\"@type\":\"JsonRestExceptionResponseEntity\",\"errorCode\": \"ENTITY_NOT_FOUND\",\"statusCode\": 404}");
552         assertNotNull(e);
553
554         e = errorResponseHandler.apply(500, "");
555         assertEquals("Request for info of user-defined state abcdef failed with status code 500", e.getMessage());
556     }
557
558     @Test
559     void getUserStates() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
560         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
561         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
562         String stateId = UUID.randomUUID().toString();
563
564         Request request = mock(Request.class);
565         when(request.header(anyString(), anyString())).thenReturn(request);
566         ContentResponse response = mock(ContentResponse.class);
567         when(response.getStatus()).thenReturn(200);
568         when(request.send()).thenReturn(response);
569         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
570         when(response.getContentAsString()).thenReturn(
571                 GsonUtils.DEFAULT_GSON_INSTANCE.toJson(List.of(UserDefinedStateTest.createTestState(stateId))));
572
573         List<UserDefinedState> userStates = fixture.getUserStates();
574         assertEquals(1, userStates.size());
575     }
576
577     @Test
578     void getUserStatesReturnsEmptyListIfRequestNotSuccessful()
579             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
580         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
581         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
582
583         Request request = mock(Request.class);
584         when(request.header(anyString(), anyString())).thenReturn(request);
585         ContentResponse response = mock(ContentResponse.class);
586         when(response.getStatus()).thenReturn(401);
587         when(request.send()).thenReturn(response);
588         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
589
590         List<UserDefinedState> userStates = fixture.getUserStates();
591         assertTrue(userStates.isEmpty());
592     }
593
594     @Test
595     void getUserStatesReturnsEmptyListIfExceptionHappened()
596             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
597         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
598         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
599
600         Request request = mock(Request.class);
601         when(request.header(anyString(), anyString())).thenReturn(request);
602         ContentResponse response = mock(ContentResponse.class);
603         when(response.getStatus()).thenReturn(401);
604         when(request.send()).thenThrow(new TimeoutException("text exception"));
605         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
606
607         List<UserDefinedState> userStates = fixture.getUserStates();
608         assertTrue(userStates.isEmpty());
609     }
610
611     @AfterEach
612     void afterEach() throws Exception {
613         fixture.dispose();
614     }
615
616     @Test
617     void handleLongPollResultNoDeviceId() {
618         List<Thing> things = new ArrayList<Thing>();
619         when(thing.getThings()).thenReturn(things);
620
621         Thing thing = mock(Thing.class);
622         things.add(thing);
623
624         BoschSHCHandler thingHandler = mock(BoschSHCHandler.class);
625         when(thing.getHandler()).thenReturn(thingHandler);
626
627         String json = """
628                 {
629                   "result": [{
630                     "path": "/devices/hdm:HomeMaticIP:3014F711A0001916D859A8A9/services/PowerSwitch",
631                     "@type": "DeviceServiceData",
632                     "id": "PowerSwitch",
633                     "state": {
634                        "@type": "powerSwitchState",
635                        "switchState": "ON"
636                     },
637                     "deviceId": "hdm:HomeMaticIP:3014F711A0001916D859A8A9"
638                   }],
639                   "jsonrpc": "2.0"
640                 }
641                 """;
642         LongPollResult longPollResult = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, LongPollResult.class);
643         assertNotNull(longPollResult);
644
645         fixture.handleLongPollResult(longPollResult);
646
647         verify(thingHandler).getBoschID();
648         verifyNoMoreInteractions(thingHandler);
649     }
650
651     @Test
652     void handleLongPollResult() {
653         List<Thing> things = new ArrayList<Thing>();
654         when(thing.getThings()).thenReturn(things);
655
656         Thing thing = mock(Thing.class);
657         things.add(thing);
658
659         BoschSHCHandler thingHandler = mock(BoschSHCHandler.class);
660         when(thing.getHandler()).thenReturn(thingHandler);
661
662         when(thingHandler.getBoschID()).thenReturn("hdm:HomeMaticIP:3014F711A0001916D859A8A9");
663
664         String json = """
665                 {
666                   "result": [{
667                     "path": "/devices/hdm:HomeMaticIP:3014F711A0001916D859A8A9/services/PowerSwitch",
668                     "@type": "DeviceServiceData",
669                     "id": "PowerSwitch",
670                     "state": {
671                        "@type": "powerSwitchState",
672                        "switchState": "ON"
673                     },
674                     "deviceId": "hdm:HomeMaticIP:3014F711A0001916D859A8A9"
675                   }],
676                   "jsonrpc": "2.0"
677                 }
678                 """;
679         LongPollResult longPollResult = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, LongPollResult.class);
680         assertNotNull(longPollResult);
681
682         fixture.handleLongPollResult(longPollResult);
683
684         verify(thingHandler).getBoschID();
685
686         JsonElement expectedState = JsonParser.parseString("""
687                 {
688                     "@type": "powerSwitchState",
689                     "switchState": "ON"
690                 }
691                 """);
692
693         verify(thingHandler).processUpdate("PowerSwitch", expectedState);
694     }
695
696     @Test
697     void handleLongPollResultHandleChildUpdate() {
698         List<Thing> things = new ArrayList<Thing>();
699         when(thing.getThings()).thenReturn(things);
700
701         Thing thing = mock(Thing.class);
702         things.add(thing);
703
704         BoschSHCHandler thingHandler = mock(BoschSHCHandler.class);
705         when(thing.getHandler()).thenReturn(thingHandler);
706
707         when(thingHandler.getBoschID()).thenReturn("hdm:ZigBee:70ac08fffefead2d");
708
709         String json = """
710                 {
711                   "result": [{
712                     "path": "/devices/hdm:ZigBee:70ac08fffefead2d#3/services/PowerSwitch",
713                     "@type": "DeviceServiceData",
714                     "id": "PowerSwitch",
715                     "state": {
716                        "@type": "powerSwitchState",
717                        "switchState": "ON"
718                     },
719                     "deviceId": "hdm:ZigBee:70ac08fffefead2d#3"
720                   }],
721                   "jsonrpc": "2.0"
722                 }
723                 """;
724         LongPollResult longPollResult = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, LongPollResult.class);
725         assertNotNull(longPollResult);
726
727         fixture.handleLongPollResult(longPollResult);
728
729         verify(thingHandler).getBoschID();
730
731         JsonElement expectedState = JsonParser.parseString("""
732                 {
733                     "@type": "powerSwitchState",
734                     "switchState": "ON"
735                 }
736                 """);
737
738         verify(thingHandler).processChildUpdate("hdm:ZigBee:70ac08fffefead2d#3", "PowerSwitch", expectedState);
739     }
740
741     @Test
742     void handleLongPollResultScenarioTriggered() {
743         Channel channel = mock(Channel.class);
744         when(thing.getChannel(BoschSHCBindingConstants.CHANNEL_SCENARIO_TRIGGERED)).thenReturn(channel);
745         when(thingHandlerCallback.isChannelLinked(any())).thenReturn(true);
746
747         String json = """
748                 {
749                   "result": [{
750                     "@type": "scenarioTriggered",
751                     "name": "My Scenario",
752                     "id": "509bd737-eed0-40b7-8caa-e8686a714399",
753                     "lastTimeTriggered": "1693758693032"
754                   }],
755                   "jsonrpc": "2.0"
756                 }
757                 """;
758         LongPollResult longPollResult = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, LongPollResult.class);
759         assertNotNull(longPollResult);
760
761         fixture.handleLongPollResult(longPollResult);
762
763         verify(thingHandlerCallback).stateUpdated(any(), eq(new StringType("My Scenario")));
764     }
765
766     @Test
767     void handleLongPollResultUserDefinedState() {
768         List<Thing> things = new ArrayList<Thing>();
769         when(thing.getThings()).thenReturn(things);
770
771         Thing thing = mock(Thing.class);
772         things.add(thing);
773
774         BoschSHCHandler thingHandler = mock(BoschSHCHandler.class);
775         when(thing.getHandler()).thenReturn(thingHandler);
776
777         when(thingHandler.getBoschID()).thenReturn("3d8023d6-69ca-4e79-89dd-7090295cefbf");
778
779         String json = """
780                 {
781                     "result": [{
782                         "deleted": false,
783                         "@type": "userDefinedState",
784                         "name": "Test State",
785                         "id": "3d8023d6-69ca-4e79-89dd-7090295cefbf",
786                         "state": true
787                     }],
788                     "jsonrpc": "2.0"
789                 }
790                 """;
791         LongPollResult longPollResult = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, LongPollResult.class);
792         assertNotNull(longPollResult);
793
794         fixture.handleLongPollResult(longPollResult);
795
796         JsonElement expectedState = new JsonPrimitive(true);
797
798         verify(thingHandler).processUpdate("3d8023d6-69ca-4e79-89dd-7090295cefbf", expectedState);
799     }
800
801     @Test
802     void handleLongPollFailure() {
803         Throwable e = new RuntimeException("Test exception");
804         fixture.handleLongPollFailure(e);
805
806         ThingStatusInfo expectedStatus = ThingStatusInfoBuilder
807                 .create(ThingStatus.UNKNOWN, ThingStatusDetail.UNKNOWN.NONE).build();
808         verify(thingHandlerCallback).statusUpdated(thing, expectedStatus);
809     }
810
811     @Test
812     void getDevices() throws InterruptedException, TimeoutException, ExecutionException {
813         Request request = mock(Request.class);
814         when(httpClient.createRequest(any(), eq(HttpMethod.GET))).thenReturn(request);
815         ContentResponse contentResponse = mock(ContentResponse.class);
816         when(request.send()).thenReturn(contentResponse);
817         when(contentResponse.getStatus()).thenReturn(200);
818         String devicesJson = """
819                 [
820                     {
821                         "@type": "device",
822                         "rootDeviceId": "64-da-a0-3e-81-0c",
823                         "id": "hdm:ZigBee:0c4314fffea15de7",
824                         "deviceServiceIds": [
825                             "CommunicationQuality",
826                             "PowerMeter",
827                             "PowerSwitch",
828                             "PowerSwitchConfiguration",
829                             "PowerSwitchProgram"
830                         ],
831                         "manufacturer": "BOSCH",
832                         "roomId": "hz_1",
833                         "deviceModel": "PLUG_COMPACT",
834                         "serial": "0C4314FFFE802BE2",
835                         "profile": "LIGHT",
836                         "iconId": "icon_plug_lamp_table",
837                         "name": "My Lamp Plug",
838                         "status": "AVAILABLE",
839                         "childDeviceIds": [],
840                         "supportedProfiles": [
841                             "LIGHT",
842                             "GENERIC",
843                             "HEATING_RCC"
844                         ]
845                     },
846                     {
847                         "@type": "device",
848                         "rootDeviceId": "64-da-a0-3e-81-0c",
849                         "id": "hdm:ZigBee:000d6f0012f13bfa",
850                         "deviceServiceIds": [
851                             "LatestMotion",
852                             "CommunicationQuality",
853                             "WalkTest",
854                             "BatteryLevel",
855                             "MultiLevelSensor",
856                             "DeviceDefect"
857                         ],
858                         "manufacturer": "BOSCH",
859                         "roomId": "hz_5",
860                         "deviceModel": "MD",
861                         "serial": "000D6F0012F0da96",
862                         "profile": "GENERIC",
863                         "name": "My Motion Detector",
864                         "status": "AVAILABLE",
865                         "childDeviceIds": [],
866                         "supportedProfiles": []
867                     }
868                 ]
869                 """;
870         when(contentResponse.getContentAsString()).thenReturn(devicesJson);
871
872         List<Device> devices = fixture.getDevices();
873
874         assertEquals(2, devices.size());
875
876         Device plugDevice = devices.get(0);
877         assertEquals("hdm:ZigBee:0c4314fffea15de7", plugDevice.id);
878         assertEquals(5, plugDevice.deviceServiceIds.size());
879         assertEquals(0, plugDevice.childDeviceIds.size());
880
881         Device motionDetectorDevice = devices.get(1);
882         assertEquals("hdm:ZigBee:000d6f0012f13bfa", motionDetectorDevice.id);
883         assertEquals(6, motionDetectorDevice.deviceServiceIds.size());
884         assertEquals(0, motionDetectorDevice.childDeviceIds.size());
885     }
886
887     @Test
888     void getDevicesErrorRestResponse() throws InterruptedException, TimeoutException, ExecutionException {
889         Request request = mock(Request.class);
890         when(httpClient.createRequest(any(), eq(HttpMethod.GET))).thenReturn(request);
891         ContentResponse contentResponse = mock(ContentResponse.class);
892         when(request.send()).thenReturn(contentResponse);
893         when(contentResponse.getStatus()).thenReturn(400); // bad request
894
895         List<Device> devices = fixture.getDevices();
896
897         assertThat(devices, hasSize(0));
898     }
899
900     @ParameterizedTest
901     @MethodSource("org.openhab.binding.boschshc.internal.tests.common.CommonTestUtils#getExecutionAndTimeoutExceptionArguments()")
902     void getDevicesHandleExceptions() throws InterruptedException, TimeoutException, ExecutionException {
903         Request request = mock(Request.class);
904         when(httpClient.createRequest(any(), eq(HttpMethod.GET))).thenReturn(request);
905         when(request.send()).thenThrow(new ExecutionException(new RuntimeException("Test Exception")));
906
907         List<Device> devices = fixture.getDevices();
908
909         assertThat(devices, hasSize(0));
910     }
911
912     @Test
913     void getRooms() throws InterruptedException, TimeoutException, ExecutionException {
914         Request request = mock(Request.class);
915         when(httpClient.createRequest(any(), eq(HttpMethod.GET))).thenReturn(request);
916         ContentResponse contentResponse = mock(ContentResponse.class);
917         when(request.send()).thenReturn(contentResponse);
918         when(contentResponse.getStatus()).thenReturn(200);
919         String roomsJson = """
920                 [
921                     {
922                         "@type": "room",
923                         "id": "hz_1",
924                         "iconId": "icon_room_living_room",
925                         "name": "Living Room"
926                     },
927                     {
928                         "@type": "room",
929                         "id": "hz_2",
930                         "iconId": "icon_room_dining_room",
931                         "name": "Dining Room"
932                     }
933                 ]
934                 """;
935         when(contentResponse.getContentAsString()).thenReturn(roomsJson);
936
937         List<Room> rooms = fixture.getRooms();
938
939         assertEquals(2, rooms.size());
940
941         Room livingRoom = rooms.get(0);
942         assertEquals("hz_1", livingRoom.id);
943         assertEquals("Living Room", livingRoom.name);
944
945         Room diningRoom = rooms.get(1);
946         assertEquals("hz_2", diningRoom.id);
947         assertEquals("Dining Room", diningRoom.name);
948     }
949
950     @Test
951     void getRoomsErrorRestResponse() throws InterruptedException, TimeoutException, ExecutionException {
952         Request request = mock(Request.class);
953         when(httpClient.createRequest(any(), eq(HttpMethod.GET))).thenReturn(request);
954         ContentResponse contentResponse = mock(ContentResponse.class);
955         when(request.send()).thenReturn(contentResponse);
956         when(contentResponse.getStatus()).thenReturn(400); // bad request
957
958         List<Room> rooms = fixture.getRooms();
959
960         assertThat(rooms, hasSize(0));
961     }
962
963     @ParameterizedTest
964     @MethodSource("org.openhab.binding.boschshc.internal.tests.common.CommonTestUtils#getExecutionAndTimeoutExceptionArguments()")
965     void getRoomsHandleExceptions() throws InterruptedException, TimeoutException, ExecutionException {
966         Request request = mock(Request.class);
967         when(httpClient.createRequest(any(), eq(HttpMethod.GET))).thenReturn(request);
968         when(request.send()).thenThrow(new ExecutionException(new RuntimeException("Test Exception")));
969
970         List<Room> rooms = fixture.getRooms();
971
972         assertThat(rooms, hasSize(0));
973     }
974
975     @Test
976     void getServices() {
977         assertTrue(fixture.getServices().contains(ThingDiscoveryService.class));
978     }
979
980     @Test
981     void handleCommandIrrelevantChannel() {
982         ChannelUID channelUID = mock(ChannelUID.class);
983         when(channelUID.getId()).thenReturn(BoschSHCBindingConstants.CHANNEL_POWER_SWITCH);
984
985         fixture.handleCommand(channelUID, OnOffType.ON);
986
987         verifyNoInteractions(httpClient);
988     }
989
990     @Test
991     void handleCommandTriggerScenario()
992             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
993         ChannelUID channelUID = mock(ChannelUID.class);
994         when(channelUID.getId()).thenReturn(BoschSHCBindingConstants.CHANNEL_TRIGGER_SCENARIO);
995
996         // required to prevent NPE
997         when(httpClient.sendRequest(any(), eq(Scenario[].class), any(), any())).thenReturn(new Scenario[] {});
998
999         fixture.handleCommand(channelUID, OnOffType.ON);
1000
1001         verify(httpClient).sendRequest(any(), eq(Scenario[].class), any(), any());
1002     }
1003
1004     @Test
1005     void registerDiscoveryListener() {
1006         ThingDiscoveryService listener = mock(ThingDiscoveryService.class);
1007         assertTrue(fixture.registerDiscoveryListener(listener));
1008         assertFalse(fixture.registerDiscoveryListener(listener));
1009     }
1010
1011     @Test
1012     void unregisterDiscoveryListener() {
1013         assertFalse(fixture.unregisterDiscoveryListener());
1014         fixture.registerDiscoveryListener(mock(ThingDiscoveryService.class));
1015         assertTrue(fixture.unregisterDiscoveryListener());
1016     }
1017
1018     @Test
1019     void initializeNoIpAddress() {
1020         bridgeConfiguration.setProperties(new HashMap<String, Object>());
1021
1022         fixture.initialize();
1023
1024         ThingStatusInfo expectedStatus = ThingStatusInfoBuilder
1025                 .create(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR)
1026                 .withDescription("@text/offline.conf-error-empty-ip").build();
1027         verify(thingHandlerCallback).statusUpdated(thing, expectedStatus);
1028     }
1029
1030     @Test
1031     void initializeNoPassword() {
1032         HashMap<String, Object> properties = new HashMap<String, Object>();
1033         properties.put("ipAddress", "localhost");
1034         bridgeConfiguration.setProperties(properties);
1035
1036         fixture.initialize();
1037
1038         ThingStatusInfo expectedStatus = ThingStatusInfoBuilder
1039                 .create(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR)
1040                 .withDescription("@text/offline.conf-error-empty-password").build();
1041         verify(thingHandlerCallback).statusUpdated(thing, expectedStatus);
1042     }
1043
1044     @Test
1045     void checkBridgeAccess() throws InterruptedException, TimeoutException, ExecutionException {
1046         Request request = mock(Request.class);
1047         when(httpClient.createRequest(any(), eq(HttpMethod.GET))).thenReturn(request);
1048         ContentResponse contentResponse = mock(ContentResponse.class);
1049         when(request.send()).thenReturn(contentResponse);
1050         when(contentResponse.getStatus()).thenReturn(200);
1051
1052         assertTrue(fixture.checkBridgeAccess());
1053     }
1054
1055     @Test
1056     void checkBridgeAccessRestResponseError() throws InterruptedException, TimeoutException, ExecutionException {
1057         Request request = mock(Request.class);
1058         when(httpClient.createRequest(any(), eq(HttpMethod.GET))).thenReturn(request);
1059         ContentResponse contentResponse = mock(ContentResponse.class);
1060         when(request.send()).thenReturn(contentResponse);
1061         when(contentResponse.getStatus()).thenReturn(400);
1062
1063         assertFalse(fixture.checkBridgeAccess());
1064     }
1065
1066     @ParameterizedTest
1067     @MethodSource("org.openhab.binding.boschshc.internal.tests.common.CommonTestUtils#getExecutionAndTimeoutExceptionArguments()")
1068     void checkBridgeAccessRestException(Exception e) throws InterruptedException, TimeoutException, ExecutionException {
1069         Request request = mock(Request.class);
1070         when(httpClient.createRequest(any(), eq(HttpMethod.GET))).thenReturn(request);
1071         when(request.send()).thenThrow(e);
1072
1073         assertFalse(fixture.checkBridgeAccess());
1074     }
1075
1076     @Test
1077     void getPublicInformation() throws InterruptedException, BoschSHCException, ExecutionException, TimeoutException {
1078         fixture.getPublicInformation();
1079
1080         verify(httpClient).createRequest(any(), same(HttpMethod.GET));
1081         verify(httpClient).sendRequest(any(), same(PublicInformation.class), any(), isNull());
1082     }
1083 }