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