]> git.basschouten.com Git - openhab-addons.git/blob
592bfa8b14094c2fe80620b31665167e89cecc92
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.io.IOException;
20 import java.nio.file.Files;
21 import java.nio.file.Path;
22 import java.nio.file.Paths;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.UUID;
27 import java.util.concurrent.ExecutionException;
28 import java.util.concurrent.TimeoutException;
29 import java.util.function.BiFunction;
30
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.eclipse.jetty.client.api.ContentResponse;
34 import org.eclipse.jetty.client.api.Request;
35 import org.eclipse.jetty.http.HttpMethod;
36 import org.junit.jupiter.api.AfterEach;
37 import org.junit.jupiter.api.BeforeAll;
38 import org.junit.jupiter.api.BeforeEach;
39 import org.junit.jupiter.api.Test;
40 import org.mockito.ArgumentCaptor;
41 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device;
42 import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
43 import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceTest;
44 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Faults;
45 import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult;
46 import org.openhab.binding.boschshc.internal.devices.bridge.dto.UserDefinedState;
47 import org.openhab.binding.boschshc.internal.devices.bridge.dto.UserDefinedStateTest;
48 import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
49 import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
50 import org.openhab.binding.boschshc.internal.services.binaryswitch.dto.BinarySwitchServiceState;
51 import org.openhab.binding.boschshc.internal.services.intrusion.actions.arm.dto.ArmActionRequest;
52 import org.openhab.binding.boschshc.internal.services.intrusion.dto.AlarmState;
53 import org.openhab.binding.boschshc.internal.services.intrusion.dto.ArmingState;
54 import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDetectionSystemState;
55 import org.openhab.binding.boschshc.internal.services.shuttercontact.ShutterContactState;
56 import org.openhab.binding.boschshc.internal.services.shuttercontact.dto.ShutterContactServiceState;
57 import org.openhab.core.config.core.Configuration;
58 import org.openhab.core.thing.Bridge;
59 import org.openhab.core.thing.Thing;
60 import org.openhab.core.thing.ThingStatus;
61 import org.openhab.core.thing.ThingStatusDetail;
62 import org.openhab.core.thing.binding.ThingHandlerCallback;
63 import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
64
65 /**
66  * Unit tests for the {@link BridgeHandler}.
67  *
68  * @author David Pace - Initial contribution
69  *
70  */
71 @NonNullByDefault
72 class BridgeHandlerTest {
73
74     private @NonNullByDefault({}) BridgeHandler fixture;
75
76     private @NonNullByDefault({}) BoschHttpClient httpClient;
77
78     private @NonNullByDefault({}) ThingHandlerCallback thingHandlerCallback;
79
80     @BeforeAll
81     static void beforeAll() throws IOException {
82         Path mavenTargetFolder = Paths.get("target");
83         assertTrue(Files.exists(mavenTargetFolder), "Maven target folder does not exist.");
84         System.setProperty("openhab.userdata", mavenTargetFolder.toFile().getAbsolutePath());
85         Path etc = mavenTargetFolder.resolve("etc");
86         if (!Files.exists(etc)) {
87             Files.createDirectory(etc);
88         }
89     }
90
91     @BeforeEach
92     void beforeEach() throws Exception {
93         Bridge bridge = mock(Bridge.class);
94         fixture = new BridgeHandler(bridge);
95
96         thingHandlerCallback = mock(ThingHandlerCallback.class);
97         fixture.setCallback(thingHandlerCallback);
98
99         Configuration bridgeConfiguration = new Configuration();
100         Map<@Nullable String, @Nullable Object> properties = new HashMap<>();
101         properties.put("ipAddress", "localhost");
102         properties.put("password", "test");
103         bridgeConfiguration.setProperties(properties);
104
105         Thing thing = mock(Bridge.class);
106         when(thing.getConfiguration()).thenReturn(bridgeConfiguration);
107         // this calls initialize() as well
108         fixture.thingUpdated(thing);
109
110         // shut down the real HTTP client
111         if (fixture.httpClient != null) {
112             fixture.httpClient.stop();
113         }
114
115         // use a mocked HTTP client
116         httpClient = mock(BoschHttpClient.class);
117         fixture.httpClient = httpClient;
118     }
119
120     @Test
121     void postAction() throws InterruptedException, TimeoutException, ExecutionException {
122         String endpoint = "/intrusion/actions/arm";
123         String url = "https://127.0.0.1:8444/smarthome/intrusion/actions/arm";
124         when(httpClient.getBoschSmartHomeUrl(endpoint)).thenReturn(url);
125         Request mockRequest = mock(Request.class);
126         when(httpClient.createRequest(anyString(), any(), any())).thenReturn(mockRequest);
127         ArmActionRequest request = new ArmActionRequest();
128         request.profileId = "0";
129
130         fixture.postAction(endpoint, request);
131         verify(httpClient).createRequest(eq(url), same(HttpMethod.POST), same(request));
132         verify(mockRequest).send();
133     }
134
135     @Test
136     void initialAccessHttpClientOffline() {
137         fixture.initialAccess(httpClient);
138     }
139
140     @Test
141     void initialAccessHttpClientOnline() throws InterruptedException {
142         when(httpClient.isOnline()).thenReturn(true);
143         fixture.initialAccess(httpClient);
144     }
145
146     @Test
147     void initialAccessAccessPossible()
148             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
149         when(httpClient.isOnline()).thenReturn(true);
150         when(httpClient.isAccessPossible()).thenReturn(true);
151         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
152         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
153
154         // mock a request and response to obtain rooms
155         Request roomsRequest = mock(Request.class);
156         ContentResponse roomsResponse = mock(ContentResponse.class);
157         when(roomsResponse.getStatus()).thenReturn(200);
158         when(roomsResponse.getContentAsString()).thenReturn(
159                 "[{\"@type\":\"room\",\"id\":\"hz_1\",\"iconId\":\"icon_room_bedroom\",\"name\":\"Bedroom\"}]");
160         when(roomsRequest.send()).thenReturn(roomsResponse);
161         when(httpClient.createRequest(contains("/rooms"), same(HttpMethod.GET))).thenReturn(roomsRequest);
162
163         // mock a request and response to obtain devices
164         Request devicesRequest = mock(Request.class);
165         ContentResponse devicesResponse = mock(ContentResponse.class);
166         when(devicesResponse.getStatus()).thenReturn(200);
167         when(devicesResponse.getContentAsString()).thenReturn("""
168                 [{"@type":"device",
169                  "rootDeviceId":"64-da-a0-02-14-9b",
170                  "id":"hdm:HomeMaticIP:3014F711A00004953859F31B",
171                  "deviceServiceIds":["PowerMeter","PowerSwitch","PowerSwitchProgram","Routing"],
172                  "manufacturer":"BOSCH",
173                  "roomId":"hz_3",
174                  "deviceModel":"PSM",
175                  "serial":"3014F711A00004953859F31B",
176                  "profile":"GENERIC",
177                  "name":"Coffee Machine",
178                  "status":"AVAILABLE",
179                  "childDeviceIds":[]
180                  }]\
181                 """);
182         when(devicesRequest.send()).thenReturn(devicesResponse);
183         when(httpClient.createRequest(contains("/devices"), same(HttpMethod.GET))).thenReturn(devicesRequest);
184
185         SubscribeResult subscribeResult = new SubscribeResult();
186         when(httpClient.sendRequest(any(), same(SubscribeResult.class), any(), any())).thenReturn(subscribeResult);
187
188         Request longPollRequest = mock(Request.class);
189         when(httpClient.createRequest(anyString(), same(HttpMethod.POST),
190                 argThat((JsonRpcRequest r) -> "RE/longPoll".equals(r.method)))).thenReturn(longPollRequest);
191
192         fixture.initialAccess(httpClient);
193         verify(thingHandlerCallback).statusUpdated(any(),
194                 eq(ThingStatusInfoBuilder.create(ThingStatus.ONLINE, ThingStatusDetail.NONE).build()));
195     }
196
197     @Test
198     void getState() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
199         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
200         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
201         Request request = mock(Request.class);
202         when(request.header(anyString(), anyString())).thenReturn(request);
203         ContentResponse response = mock(ContentResponse.class);
204         when(response.getStatus()).thenReturn(200);
205         when(response.getContentAsString()).thenReturn("""
206                 {
207                      "@type": "systemState",
208                      "systemAvailability": {
209                          "@type": "systemAvailabilityState",
210                          "available": true,
211                          "deleted": false
212                      },
213                      "armingState": {
214                          "@type": "armingState",
215                          "state": "SYSTEM_DISARMED",
216                          "deleted": false
217                      },
218                      "alarmState": {
219                          "@type": "alarmState",
220                          "value": "ALARM_OFF",
221                          "incidents": [],
222                          "deleted": false
223                      },
224                      "activeConfigurationProfile": {
225                          "@type": "activeConfigurationProfile",
226                          "deleted": false
227                      },
228                      "securityGapState": {
229                          "@type": "securityGapState",
230                          "securityGaps": [],
231                          "deleted": false
232                      },
233                      "deleted": false
234                  }\
235                 """);
236         when(request.send()).thenReturn(response);
237         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
238
239         IntrusionDetectionSystemState state = fixture.getState("intrusion/states/system",
240                 IntrusionDetectionSystemState.class);
241         assertNotNull(state);
242         assertTrue(state.systemAvailability.available);
243         assertSame(AlarmState.ALARM_OFF, state.alarmState.value);
244         assertSame(ArmingState.SYSTEM_DISARMED, state.armingState.state);
245     }
246
247     @Test
248     void getDeviceState() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
249         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
250         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
251         when(httpClient.getServiceStateUrl(anyString(), anyString(), any())).thenCallRealMethod();
252         when(httpClient.getServiceStateUrl(anyString(), anyString())).thenCallRealMethod();
253
254         Request request = mock(Request.class);
255         when(request.header(anyString(), anyString())).thenReturn(request);
256         ContentResponse response = mock(ContentResponse.class);
257         when(response.getStatus()).thenReturn(200);
258         when(response.getContentAsString())
259                 .thenReturn("{\n" + "   \"@type\": \"shutterContactState\",\n" + "   \"value\": \"OPEN\"\n" + " }");
260         when(request.send()).thenReturn(response);
261         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
262
263         ShutterContactServiceState state = fixture.getState("hdm:HomeMaticIP:3014D711A000009D545DEB39D",
264                 "ShutterContact", ShutterContactServiceState.class);
265         assertNotNull(state);
266         assertSame(ShutterContactState.OPEN, state.value);
267     }
268
269     @Test
270     void getDeviceInfo() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
271         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
272         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
273
274         Request request = mock(Request.class);
275         when(request.header(anyString(), anyString())).thenReturn(request);
276         ContentResponse response = mock(ContentResponse.class);
277         when(response.getStatus()).thenReturn(200);
278         when(request.send()).thenReturn(response);
279         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
280         when(httpClient.sendRequest(same(request), same(Device.class), any(), any()))
281                 .thenReturn(DeviceTest.createTestDevice());
282
283         String deviceId = "hdm:HomeMaticIP:3014F711A00004953859F31B";
284         Device device = fixture.getDeviceInfo(deviceId);
285         assertEquals(deviceId, device.id);
286     }
287
288     @Test
289     void getDeviceInfoErrorCases()
290             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
291         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
292         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
293
294         Request request = mock(Request.class);
295         when(request.header(anyString(), anyString())).thenReturn(request);
296         ContentResponse response = mock(ContentResponse.class);
297         when(response.getStatus()).thenReturn(200);
298         when(request.send()).thenReturn(response);
299         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
300
301         @SuppressWarnings("unchecked")
302         ArgumentCaptor<BiFunction<Integer, String, BoschSHCException>> errorResponseHandlerCaptor = ArgumentCaptor
303                 .forClass(BiFunction.class);
304
305         when(httpClient.sendRequest(same(request), same(Device.class), any(), errorResponseHandlerCaptor.capture()))
306                 .thenReturn(DeviceTest.createTestDevice());
307
308         String deviceId = "hdm:HomeMaticIP:3014F711A00004953859F31B";
309         fixture.getDeviceInfo(deviceId);
310
311         BiFunction<Integer, String, BoschSHCException> errorResponseHandler = errorResponseHandlerCaptor.getValue();
312         Exception e = errorResponseHandler.apply(500,
313                 "{\"@type\":\"JsonRestExceptionResponseEntity\",\"errorCode\": \"testErrorCode\",\"statusCode\": 500}");
314         assertEquals(
315                 "Request for info of device hdm:HomeMaticIP:3014F711A00004953859F31B failed with status code 500 and error code testErrorCode",
316                 e.getMessage());
317
318         e = errorResponseHandler.apply(404,
319                 "{\"@type\":\"JsonRestExceptionResponseEntity\",\"errorCode\": \"ENTITY_NOT_FOUND\",\"statusCode\": 404}");
320         assertNotNull(e);
321
322         e = errorResponseHandler.apply(500, "");
323         assertEquals("Request for info of device hdm:HomeMaticIP:3014F711A00004953859F31B failed with status code 500",
324                 e.getMessage());
325     }
326
327     @Test
328     void getServiceData() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
329         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
330         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
331         when(httpClient.getServiceUrl(anyString(), anyString())).thenCallRealMethod();
332
333         Request request = mock(Request.class);
334         when(request.header(anyString(), anyString())).thenReturn(request);
335         ContentResponse response = mock(ContentResponse.class);
336         when(response.getStatus()).thenReturn(200);
337         when(response.getContentAsString()).thenReturn("""
338                 {
339                     "@type":"DeviceServiceData",
340                     "path":"/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel",
341                     "id":"BatteryLevel",
342                     "deviceId":"hdm:ZigBee:000d6f0004b93361",
343                     "faults":{\s
344                         "entries":[
345                           {
346                             "type":"LOW_BATTERY",
347                             "category":"WARNING"
348                           }
349                         ]
350                     }
351                 }\
352                 """);
353         when(request.send()).thenReturn(response);
354         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
355
356         DeviceServiceData serviceData = fixture.getServiceData("hdm:ZigBee:000d6f0004b93361", "BatteryLevel");
357         assertNotNull(serviceData);
358         Faults faults = serviceData.faults;
359         assertNotNull(faults);
360         assertEquals("LOW_BATTERY", faults.entries.get(0).type);
361     }
362
363     @Test
364     void getServiceDataError() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
365         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
366         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
367         when(httpClient.getServiceUrl(anyString(), anyString())).thenCallRealMethod();
368
369         Request request = mock(Request.class);
370         when(request.header(anyString(), anyString())).thenReturn(request);
371         ContentResponse response = mock(ContentResponse.class);
372         when(response.getStatus()).thenReturn(500);
373         when(response.getContentAsString()).thenReturn(
374                 "{\"@type\":\"JsonRestExceptionResponseEntity\",\"errorCode\": \"testErrorCode\",\"statusCode\": 500}");
375         when(request.send()).thenReturn(response);
376         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
377         when(httpClient.sendRequest(same(request), same(Device.class), any(), any()))
378                 .thenReturn(DeviceTest.createTestDevice());
379
380         BoschSHCException e = assertThrows(BoschSHCException.class,
381                 () -> fixture.getServiceData("hdm:ZigBee:000d6f0004b93361", "BatteryLevel"));
382         assertEquals(
383                 "State request with URL https://null:8444/smarthome/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel failed with status code 500 and error code testErrorCode",
384                 e.getMessage());
385     }
386
387     @Test
388     void getServiceDataErrorNoRestExceptionResponse()
389             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
390         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
391         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
392         when(httpClient.getServiceUrl(anyString(), anyString())).thenCallRealMethod();
393
394         Request request = mock(Request.class);
395         when(request.header(anyString(), anyString())).thenReturn(request);
396         ContentResponse response = mock(ContentResponse.class);
397         when(response.getStatus()).thenReturn(500);
398         when(response.getContentAsString()).thenReturn("");
399         when(request.send()).thenReturn(response);
400         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
401
402         BoschSHCException e = assertThrows(BoschSHCException.class,
403                 () -> fixture.getServiceData("hdm:ZigBee:000d6f0004b93361", "BatteryLevel"));
404         assertEquals(
405                 "State request with URL https://null:8444/smarthome/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel failed with status code 500",
406                 e.getMessage());
407     }
408
409     @Test
410     void putState() throws InterruptedException, TimeoutException, ExecutionException {
411         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
412         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
413         when(httpClient.getServiceStateUrl(anyString(), anyString())).thenCallRealMethod();
414         when(httpClient.getServiceStateUrl(anyString(), anyString(), any())).thenCallRealMethod();
415
416         Request request = mock(Request.class);
417         when(request.header(anyString(), anyString())).thenReturn(request);
418         ContentResponse response = mock(ContentResponse.class);
419
420         when(httpClient.createRequest(anyString(), same(HttpMethod.PUT), any(BinarySwitchServiceState.class)))
421                 .thenReturn(request);
422         when(request.send()).thenReturn(response);
423
424         BinarySwitchServiceState binarySwitchState = new BinarySwitchServiceState();
425         binarySwitchState.on = true;
426         fixture.putState("hdm:ZigBee:f0d1b80000f2a3e9", "BinarySwitch", binarySwitchState);
427     }
428
429     @Test
430     void getUserStateInfo() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
431         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
432         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
433         String stateId = UUID.randomUUID().toString();
434
435         Request request = mock(Request.class);
436         when(request.header(anyString(), anyString())).thenReturn(request);
437         ContentResponse response = mock(ContentResponse.class);
438         when(response.getStatus()).thenReturn(200);
439         when(request.send()).thenReturn(response);
440         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
441         when(httpClient.sendRequest(same(request), same(UserDefinedState.class), any(), any()))
442                 .thenReturn(UserDefinedStateTest.createTestState(stateId));
443
444         UserDefinedState userState = fixture.getUserStateInfo(stateId);
445         assertEquals(stateId, userState.getId());
446     }
447
448     @Test
449     void getUserStates() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
450         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
451         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
452         String stateId = UUID.randomUUID().toString();
453
454         Request request = mock(Request.class);
455         when(request.header(anyString(), anyString())).thenReturn(request);
456         ContentResponse response = mock(ContentResponse.class);
457         when(response.getStatus()).thenReturn(200);
458         when(request.send()).thenReturn(response);
459         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
460         when(response.getContentAsString()).thenReturn(
461                 GsonUtils.DEFAULT_GSON_INSTANCE.toJson(List.of(UserDefinedStateTest.createTestState(stateId))));
462
463         List<UserDefinedState> userStates = fixture.getUserStates();
464         assertEquals(1, userStates.size());
465     }
466
467     @Test
468     void getUserStatesReturnsEmptyListIfRequestNotSuccessful()
469             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
470         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
471         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
472
473         Request request = mock(Request.class);
474         when(request.header(anyString(), anyString())).thenReturn(request);
475         ContentResponse response = mock(ContentResponse.class);
476         when(response.getStatus()).thenReturn(401);
477         when(request.send()).thenReturn(response);
478         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
479
480         List<UserDefinedState> userStates = fixture.getUserStates();
481         assertTrue(userStates.isEmpty());
482     }
483
484     @Test
485     void getUserStatesReturnsEmptyListIfExceptionHappened()
486             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
487         when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
488         when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
489
490         Request request = mock(Request.class);
491         when(request.header(anyString(), anyString())).thenReturn(request);
492         ContentResponse response = mock(ContentResponse.class);
493         when(response.getStatus()).thenReturn(401);
494         when(request.send()).thenThrow(new TimeoutException("text exception"));
495         when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
496
497         List<UserDefinedState> userStates = fixture.getUserStates();
498         assertTrue(userStates.isEmpty());
499     }
500
501     @AfterEach
502     void afterEach() throws Exception {
503         fixture.dispose();
504     }
505 }