]> git.basschouten.com Git - openhab-addons.git/blob
520e7dd037f17b833fb2538467f04d5b43a82d9e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.netatmo.internal.presence;
14
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.mockito.ArgumentMatchers.any;
17 import static org.mockito.Mockito.*;
18
19 import java.util.Optional;
20
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.junit.jupiter.api.BeforeEach;
23 import org.junit.jupiter.api.Test;
24 import org.junit.jupiter.api.extension.ExtendWith;
25 import org.mockito.Mock;
26 import org.mockito.junit.jupiter.MockitoExtension;
27 import org.mockito.junit.jupiter.MockitoSettings;
28 import org.mockito.quality.Strictness;
29 import org.openhab.binding.netatmo.internal.NetatmoBindingConstants;
30 import org.openhab.core.i18n.TimeZoneProvider;
31 import org.openhab.core.library.types.OnOffType;
32 import org.openhab.core.library.types.StringType;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingTypeUID;
36 import org.openhab.core.thing.internal.ThingImpl;
37 import org.openhab.core.types.RefreshType;
38 import org.openhab.core.types.State;
39 import org.openhab.core.types.UnDefType;
40
41 import io.swagger.client.model.NAWelcomeCamera;
42
43 /**
44  * @author Sven Strohschein - Initial contribution
45  */
46 @ExtendWith(MockitoExtension.class)
47 @MockitoSettings(strictness = Strictness.WARN)
48 public class NAPresenceCameraHandlerTest {
49
50     private static final String DUMMY_VPN_URL = "https://dummytestvpnaddress.net/restricted/10.255.89.96/9826069dc689e8327ac3ed2ced4ff089/MTU5MTgzMzYwMDrQ7eHHhG0_OJ4TgmPhGlnK7QQ5pZ,,";
51     private static final String DUMMY_LOCAL_URL = "http://192.168.178.76/9826069dc689e8327ac3ed2ced4ff089";
52     private static final Optional<String> DUMMY_PING_RESPONSE = createPingResponseContent(DUMMY_LOCAL_URL);
53
54     private @Mock RequestExecutor requestExecutorMock;
55     private @Mock TimeZoneProvider timeZoneProviderMock;
56
57     private Thing presenceCameraThing;
58     private NAWelcomeCamera presenceCamera;
59     private ChannelUID cameraStatusChannelUID;
60     private ChannelUID floodlightChannelUID;
61     private ChannelUID floodlightAutoModeChannelUID;
62     private NAPresenceCameraHandlerAccessible handler;
63
64     @BeforeEach
65     public void before() {
66         presenceCameraThing = new ThingImpl(new ThingTypeUID("netatmo", "NOC"), "1");
67         presenceCamera = new NAWelcomeCamera();
68
69         cameraStatusChannelUID = new ChannelUID(presenceCameraThing.getUID(),
70                 NetatmoBindingConstants.CHANNEL_CAMERA_STATUS);
71         floodlightChannelUID = new ChannelUID(presenceCameraThing.getUID(),
72                 NetatmoBindingConstants.CHANNEL_CAMERA_FLOODLIGHT);
73         floodlightAutoModeChannelUID = new ChannelUID(presenceCameraThing.getUID(),
74                 NetatmoBindingConstants.CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE);
75
76         handler = new NAPresenceCameraHandlerAccessible(presenceCameraThing, presenceCamera);
77     }
78
79     @Test
80     public void testHandleCommandSwitchSurveillanceOn() {
81         when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
82
83         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
84         handler.handleCommand(cameraStatusChannelUID, OnOffType.ON);
85
86         verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch on
87         verify(requestExecutorMock).executeGETRequest(DUMMY_LOCAL_URL + "/command/changestatus?status=on");
88     }
89
90     @Test
91     public void testHandleCommandSwitchSurveillanceOff() {
92         when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
93
94         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
95         handler.handleCommand(cameraStatusChannelUID, OnOffType.OFF);
96
97         verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
98         verify(requestExecutorMock).executeGETRequest(DUMMY_LOCAL_URL + "/command/changestatus?status=off");
99     }
100
101     @Test
102     public void testHandleCommandSwitchSurveillanceUnknownCommand() {
103         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
104         handler.handleCommand(cameraStatusChannelUID, RefreshType.REFRESH);
105
106         verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed on a refresh
107                                                                        // command
108     }
109
110     @Test
111     public void testHandleCommandSwitchSurveillanceWithoutVPN() {
112         handler.handleCommand(cameraStatusChannelUID, OnOffType.ON);
113
114         verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed when no VPN
115                                                                        // address is set
116     }
117
118     @Test
119     public void testHandleCommandSwitchFloodlightOn() {
120         when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
121
122         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
123         handler.handleCommand(floodlightChannelUID, OnOffType.ON);
124
125         verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch on
126         verify(requestExecutorMock)
127                 .executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
128     }
129
130     @Test
131     public void testHandleCommandSwitchFloodlightOff() {
132         when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
133
134         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
135         handler.handleCommand(floodlightChannelUID, OnOffType.OFF);
136
137         verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
138         verify(requestExecutorMock).executeGETRequest(
139                 DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
140     }
141
142     @Test
143     public void testHandleCommandSwitchFloodlightOffWithAutoModeOn() {
144         when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
145
146         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
147         presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
148         assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
149
150         handler.handleCommand(floodlightChannelUID, OnOffType.OFF);
151
152         verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
153         verify(requestExecutorMock).executeGETRequest(
154                 DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22auto%22%7D");
155     }
156
157     @Test
158     public void testHandleCommandSwitchFloodlightOnAddressChanged() {
159         when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
160
161         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
162         handler.handleCommand(floodlightChannelUID, OnOffType.ON);
163         // 1.) execute ping + 2.) execute switch on
164         verify(requestExecutorMock, times(2)).executeGETRequest(any());
165         verify(requestExecutorMock)
166                 .executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
167
168         handler.handleCommand(floodlightChannelUID, OnOffType.OFF);
169         // 1.) execute ping + 2.) execute switch on + 3.) execute switch off
170         verify(requestExecutorMock, times(3)).executeGETRequest(any());
171         verify(requestExecutorMock)
172                 .executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
173         verify(requestExecutorMock).executeGETRequest(
174                 DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
175
176         final String newDummyVPNURL = DUMMY_VPN_URL + "2";
177         final String newDummyLocalURL = DUMMY_LOCAL_URL + "2";
178         final Optional<String> newDummyPingResponse = createPingResponseContent(newDummyLocalURL);
179
180         when(requestExecutorMock.executeGETRequest(newDummyVPNURL + "/command/ping")).thenReturn(newDummyPingResponse);
181
182         presenceCamera.setVpnUrl(newDummyVPNURL);
183         handler.handleCommand(floodlightChannelUID, OnOffType.ON);
184         // 1.) execute ping + 2.) execute switch on + 3.) execute switch off + 4.) execute ping + 5.) execute switch on
185         verify(requestExecutorMock, times(5)).executeGETRequest(any());
186         verify(requestExecutorMock)
187                 .executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
188         verify(requestExecutorMock).executeGETRequest(
189                 DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
190         verify(requestExecutorMock).executeGETRequest(
191                 newDummyLocalURL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
192     }
193
194     @Test
195     public void testHandleCommandSwitchFloodlightUnknownCommand() {
196         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
197         handler.handleCommand(floodlightChannelUID, RefreshType.REFRESH);
198
199         verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed on a refresh
200                                                                        // command
201     }
202
203     @Test
204     public void testHandleCommandSwitchFloodlightAutoModeOn() {
205         when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
206
207         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
208
209         handler.handleCommand(floodlightAutoModeChannelUID, OnOffType.ON);
210
211         verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch
212                                                                         // auto-mode on
213         verify(requestExecutorMock).executeGETRequest(
214                 DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22auto%22%7D");
215     }
216
217     @Test
218     public void testHandleCommandSwitchFloodlightAutoModeOff() {
219         when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
220
221         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
222
223         handler.handleCommand(floodlightAutoModeChannelUID, OnOffType.OFF);
224
225         verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
226         verify(requestExecutorMock).executeGETRequest(
227                 DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
228     }
229
230     @Test
231     public void testHandleCommandSwitchFloodlightAutoModeUnknownCommand() {
232         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
233         handler.handleCommand(floodlightAutoModeChannelUID, RefreshType.REFRESH);
234
235         verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed on a refresh
236                                                                        // command
237     }
238
239     /**
240      * The request "fails" because there is no response content of the ping command.
241      */
242     @Test
243     public void testHandleCommandRequestFailed() {
244         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
245         handler.handleCommand(floodlightChannelUID, OnOffType.ON);
246
247         verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
248     }
249
250     @Test
251     public void testHandleCommandWithoutVPN() {
252         handler.handleCommand(floodlightChannelUID, OnOffType.ON);
253
254         verify(requestExecutorMock, never()).executeGETRequest(any()); // no executions because the VPN URL is still
255                                                                        // unknown
256     }
257
258     @Test
259     public void testHandleCommandPingFailedNULLResponse() {
260         when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(Optional.of(""));
261
262         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
263         handler.handleCommand(floodlightChannelUID, OnOffType.ON);
264
265         verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
266     }
267
268     @Test
269     public void testHandleCommandPingFailedEmptyResponse() {
270         when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(Optional.of(""));
271
272         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
273         handler.handleCommand(floodlightChannelUID, OnOffType.ON);
274
275         verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
276     }
277
278     @Test
279     public void testHandleCommandPingFailedWrongResponse() {
280         when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping"))
281                 .thenReturn(Optional.of("{ \"message\":  \"error\" }"));
282
283         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
284         handler.handleCommand(floodlightChannelUID, OnOffType.ON);
285
286         verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
287     }
288
289     @Test
290     public void testHandleCommandModuleNULL() {
291         NAPresenceCameraHandler handlerWithoutModule = new NAPresenceCameraHandler(presenceCameraThing,
292                 timeZoneProviderMock);
293         handlerWithoutModule.handleCommand(floodlightChannelUID, OnOffType.ON);
294
295         verify(requestExecutorMock, never()).executeGETRequest(any()); // no executions because the thing isn't
296                                                                        // initialized
297     }
298
299     @Test
300     public void testGetNAThingPropertyCommonChannel() {
301         assertEquals(OnOffType.OFF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_CAMERA_STATUS));
302     }
303
304     @Test
305     public void testGetNAThingPropertyFloodlightOn() {
306         presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
307         assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightChannelUID.getId()));
308     }
309
310     @Test
311     public void testGetNAThingPropertyFloodlightOff() {
312         presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
313         assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
314     }
315
316     @Test
317     public void testGetNAThingPropertyFloodlightAuto() {
318         presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
319         // When the floodlight is set to auto-mode it is currently off.
320         assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
321     }
322
323     @Test
324     public void testGetNAThingPropertyFloodlightWithoutLightModeState() {
325         assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
326     }
327
328     @Test
329     public void testGetNAThingPropertyFloodlightModuleNULL() {
330         NAPresenceCameraHandler handlerWithoutModule = new NAPresenceCameraHandler(presenceCameraThing,
331                 timeZoneProviderMock);
332         assertEquals(UnDefType.UNDEF, handlerWithoutModule.getNAThingProperty(floodlightChannelUID.getId()));
333     }
334
335     @Test
336     public void testGetNAThingPropertyFloodlightAutoModeFloodlightAuto() {
337         presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
338         assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
339     }
340
341     @Test
342     public void testGetNAThingPropertyFloodlightAutoModeFloodlightOn() {
343         presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
344         // When the floodlight is initially on (on starting the binding), there is no information about if the auto-mode
345         // was set before. Therefore the auto-mode is detected as deactivated / off.
346         assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
347     }
348
349     @Test
350     public void testGetNAThingPropertyFloodlightAutoModeFloodlightOff() {
351         presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
352         // When the floodlight is initially off (on starting the binding), the auto-mode isn't set.
353         assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
354     }
355
356     @Test
357     public void testGetNAThingPropertyFloodlightScenarioWithAutoMode() {
358         presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
359         assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
360         assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
361
362         // The auto-mode was initially set, after that the floodlight was switched on by the user.
363         // In this case the binding should still know that the auto-mode is/was set.
364         presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
365         assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
366         assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightChannelUID.getId()));
367
368         // After that the user switched off the floodlight.
369         // In this case the binding should still know that the auto-mode is/was set.
370         presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
371         assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
372         assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
373     }
374
375     @Test
376     public void testGetNAThingPropertyFloodlightScenarioWithoutAutoMode() {
377         presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
378         assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
379         assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
380
381         // The auto-mode wasn't set, after that the floodlight was switched on by the user.
382         // In this case the binding should still know that the auto-mode isn't/wasn't set.
383         presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
384         assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
385         assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightChannelUID.getId()));
386
387         // After that the user switched off the floodlight.
388         // In this case the binding should still know that the auto-mode isn't/wasn't set.
389         presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
390         assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
391         assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
392     }
393
394     @Test
395     public void testGetNAThingPropertyFloodlightAutoModeModuleNULL() {
396         NAPresenceCameraHandler handlerWithoutModule = new NAPresenceCameraHandler(presenceCameraThing,
397                 timeZoneProviderMock);
398         assertEquals(UnDefType.UNDEF, handlerWithoutModule.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
399     }
400
401     @Test
402     public void testGetStreamURL() {
403         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
404         Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
405         assertTrue(streamURL.isPresent());
406         assertEquals(DUMMY_VPN_URL + "/vod/dummyVideoId/index.m3u8", streamURL.get());
407     }
408
409     @Test
410     public void testGetStreamURLLocal() {
411         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
412         presenceCamera.setIsLocal(true);
413
414         Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
415         assertTrue(streamURL.isPresent());
416         assertEquals(DUMMY_VPN_URL + "/vod/dummyVideoId/index_local.m3u8", streamURL.get());
417     }
418
419     @Test
420     public void testGetStreamURLNotLocal() {
421         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
422         presenceCamera.setIsLocal(false);
423
424         Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
425         assertTrue(streamURL.isPresent());
426         assertEquals(DUMMY_VPN_URL + "/vod/dummyVideoId/index.m3u8", streamURL.get());
427     }
428
429     @Test
430     public void testGetStreamURLWithoutVPN() {
431         Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
432         assertFalse(streamURL.isPresent());
433     }
434
435     @Test
436     public void testGetLivePictureURLState() {
437         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
438
439         State livePictureURLState = handler.getLivePictureURLState();
440         assertEquals(new StringType(DUMMY_VPN_URL + "/live/snapshot_720.jpg"), livePictureURLState);
441     }
442
443     @Test
444     public void testGetLivePictureURLStateWithoutVPN() {
445         State livePictureURLState = handler.getLivePictureURLState();
446         assertEquals(UnDefType.UNDEF, livePictureURLState);
447     }
448
449     @Test
450     public void testGetLiveStreamState() {
451         presenceCamera.setVpnUrl(DUMMY_VPN_URL);
452
453         State liveStreamState = handler.getLiveStreamState();
454         assertEquals(new StringType(DUMMY_VPN_URL + "/live/index.m3u8"), liveStreamState);
455     }
456
457     @Test
458     public void testGetLiveStreamStateWithoutVPN() {
459         State liveStreamState = handler.getLiveStreamState();
460         assertEquals(UnDefType.UNDEF, liveStreamState);
461     }
462
463     private static Optional<String> createPingResponseContent(final String localURL) {
464         return Optional.of("{\"local_url\":\"" + localURL + "\",\"product_name\":\"Welcome Netatmo\"}");
465     }
466
467     private interface RequestExecutor {
468
469         Optional<String> executeGETRequest(String url);
470     }
471
472     private class NAPresenceCameraHandlerAccessible extends NAPresenceCameraHandler {
473
474         private NAPresenceCameraHandlerAccessible(Thing thing, NAWelcomeCamera presenceCamera) {
475             super(thing, timeZoneProviderMock);
476             setModule(presenceCamera);
477         }
478
479         @Override
480         protected @NonNull Optional<@NonNull String> executeGETRequest(@NonNull String url) {
481             return requestExecutorMock.executeGETRequest(url);
482         }
483
484         @Override
485         protected @NonNull State getLivePictureURLState() {
486             return super.getLivePictureURLState();
487         }
488
489         @Override
490         protected @NonNull State getLiveStreamState() {
491             return super.getLiveStreamState();
492         }
493     }
494 }