2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.netatmo.internal.presence;
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.mockito.ArgumentMatchers.any;
17 import static org.mockito.Mockito.*;
19 import java.util.Optional;
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;
41 import io.swagger.client.model.NAWelcomeCamera;
44 * @author Sven Strohschein - Initial contribution
46 @ExtendWith(MockitoExtension.class)
47 @MockitoSettings(strictness = Strictness.WARN)
48 public class NAPresenceCameraHandlerTest {
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);
54 private @Mock RequestExecutor requestExecutorMock;
55 private @Mock TimeZoneProvider timeZoneProviderMock;
57 private Thing presenceCameraThing;
58 private NAWelcomeCamera presenceCamera;
59 private ChannelUID cameraStatusChannelUID;
60 private ChannelUID floodlightChannelUID;
61 private ChannelUID floodlightAutoModeChannelUID;
62 private NAPresenceCameraHandlerAccessible handler;
65 public void before() {
66 presenceCameraThing = new ThingImpl(new ThingTypeUID("netatmo", "NOC"), "1");
67 presenceCamera = new NAWelcomeCamera();
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);
76 handler = new NAPresenceCameraHandlerAccessible(presenceCameraThing, presenceCamera);
80 public void testHandleCommandSwitchSurveillanceOn() {
81 when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
83 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
84 handler.handleCommand(cameraStatusChannelUID, OnOffType.ON);
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");
91 public void testHandleCommandSwitchSurveillanceOff() {
92 when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
94 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
95 handler.handleCommand(cameraStatusChannelUID, OnOffType.OFF);
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");
102 public void testHandleCommandSwitchSurveillanceUnknownCommand() {
103 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
104 handler.handleCommand(cameraStatusChannelUID, RefreshType.REFRESH);
106 verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed on a refresh
111 public void testHandleCommandSwitchSurveillanceWithoutVPN() {
112 handler.handleCommand(cameraStatusChannelUID, OnOffType.ON);
114 verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed when no VPN
119 public void testHandleCommandSwitchFloodlightOn() {
120 when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
122 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
123 handler.handleCommand(floodlightChannelUID, OnOffType.ON);
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");
131 public void testHandleCommandSwitchFloodlightOff() {
132 when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
134 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
135 handler.handleCommand(floodlightChannelUID, OnOffType.OFF);
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");
143 public void testHandleCommandSwitchFloodlightOffWithAutoModeOn() {
144 when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
146 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
147 presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
148 assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
150 handler.handleCommand(floodlightChannelUID, OnOffType.OFF);
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");
158 public void testHandleCommandSwitchFloodlightOnAddressChanged() {
159 when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
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");
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");
176 final String newDummyVPNURL = DUMMY_VPN_URL + "2";
177 final String newDummyLocalURL = DUMMY_LOCAL_URL + "2";
178 final Optional<String> newDummyPingResponse = createPingResponseContent(newDummyLocalURL);
180 when(requestExecutorMock.executeGETRequest(newDummyVPNURL + "/command/ping")).thenReturn(newDummyPingResponse);
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");
195 public void testHandleCommandSwitchFloodlightUnknownCommand() {
196 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
197 handler.handleCommand(floodlightChannelUID, RefreshType.REFRESH);
199 verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed on a refresh
204 public void testHandleCommandSwitchFloodlightAutoModeOn() {
205 when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
207 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
209 handler.handleCommand(floodlightAutoModeChannelUID, OnOffType.ON);
211 verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch
213 verify(requestExecutorMock).executeGETRequest(
214 DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22auto%22%7D");
218 public void testHandleCommandSwitchFloodlightAutoModeOff() {
219 when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
221 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
223 handler.handleCommand(floodlightAutoModeChannelUID, OnOffType.OFF);
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");
231 public void testHandleCommandSwitchFloodlightAutoModeUnknownCommand() {
232 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
233 handler.handleCommand(floodlightAutoModeChannelUID, RefreshType.REFRESH);
235 verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed on a refresh
240 * The request "fails" because there is no response content of the ping command.
243 public void testHandleCommandRequestFailed() {
244 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
245 handler.handleCommand(floodlightChannelUID, OnOffType.ON);
247 verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
251 public void testHandleCommandWithoutVPN() {
252 handler.handleCommand(floodlightChannelUID, OnOffType.ON);
254 verify(requestExecutorMock, never()).executeGETRequest(any()); // no executions because the VPN URL is still
259 public void testHandleCommandPingFailedNULLResponse() {
260 when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(Optional.of(""));
262 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
263 handler.handleCommand(floodlightChannelUID, OnOffType.ON);
265 verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
269 public void testHandleCommandPingFailedEmptyResponse() {
270 when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(Optional.of(""));
272 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
273 handler.handleCommand(floodlightChannelUID, OnOffType.ON);
275 verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
279 public void testHandleCommandPingFailedWrongResponse() {
280 when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping"))
281 .thenReturn(Optional.of("{ \"message\": \"error\" }"));
283 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
284 handler.handleCommand(floodlightChannelUID, OnOffType.ON);
286 verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
290 public void testHandleCommandModuleNULL() {
291 NAPresenceCameraHandler handlerWithoutModule = new NAPresenceCameraHandler(presenceCameraThing,
292 timeZoneProviderMock);
293 handlerWithoutModule.handleCommand(floodlightChannelUID, OnOffType.ON);
295 verify(requestExecutorMock, never()).executeGETRequest(any()); // no executions because the thing isn't
300 public void testGetNAThingPropertyCommonChannel() {
301 assertEquals(OnOffType.OFF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_CAMERA_STATUS));
305 public void testGetNAThingPropertyFloodlightOn() {
306 presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
307 assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightChannelUID.getId()));
311 public void testGetNAThingPropertyFloodlightOff() {
312 presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
313 assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
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()));
324 public void testGetNAThingPropertyFloodlightWithoutLightModeState() {
325 assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
329 public void testGetNAThingPropertyFloodlightModuleNULL() {
330 NAPresenceCameraHandler handlerWithoutModule = new NAPresenceCameraHandler(presenceCameraThing,
331 timeZoneProviderMock);
332 assertEquals(UnDefType.UNDEF, handlerWithoutModule.getNAThingProperty(floodlightChannelUID.getId()));
336 public void testGetNAThingPropertyFloodlightAutoModeFloodlightAuto() {
337 presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
338 assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
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()));
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()));
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()));
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()));
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()));
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()));
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()));
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()));
395 public void testGetNAThingPropertyFloodlightAutoModeModuleNULL() {
396 NAPresenceCameraHandler handlerWithoutModule = new NAPresenceCameraHandler(presenceCameraThing,
397 timeZoneProviderMock);
398 assertEquals(UnDefType.UNDEF, handlerWithoutModule.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
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());
410 public void testGetStreamURLLocal() {
411 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
412 presenceCamera.setIsLocal(true);
414 Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
415 assertTrue(streamURL.isPresent());
416 assertEquals(DUMMY_VPN_URL + "/vod/dummyVideoId/index_local.m3u8", streamURL.get());
420 public void testGetStreamURLNotLocal() {
421 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
422 presenceCamera.setIsLocal(false);
424 Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
425 assertTrue(streamURL.isPresent());
426 assertEquals(DUMMY_VPN_URL + "/vod/dummyVideoId/index.m3u8", streamURL.get());
430 public void testGetStreamURLWithoutVPN() {
431 Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
432 assertFalse(streamURL.isPresent());
436 public void testGetLivePictureURLState() {
437 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
439 State livePictureURLState = handler.getLivePictureURLState();
440 assertEquals(new StringType(DUMMY_VPN_URL + "/live/snapshot_720.jpg"), livePictureURLState);
444 public void testGetLivePictureURLStateWithoutVPN() {
445 State livePictureURLState = handler.getLivePictureURLState();
446 assertEquals(UnDefType.UNDEF, livePictureURLState);
450 public void testGetLiveStreamState() {
451 presenceCamera.setVpnUrl(DUMMY_VPN_URL);
453 State liveStreamState = handler.getLiveStreamState();
454 assertEquals(new StringType(DUMMY_VPN_URL + "/live/index.m3u8"), liveStreamState);
458 public void testGetLiveStreamStateWithoutVPN() {
459 State liveStreamState = handler.getLiveStreamState();
460 assertEquals(UnDefType.UNDEF, liveStreamState);
463 private static Optional<String> createPingResponseContent(final String localURL) {
464 return Optional.of("{\"local_url\":\"" + localURL + "\",\"product_name\":\"Welcome Netatmo\"}");
467 private interface RequestExecutor {
469 Optional<String> executeGETRequest(String url);
472 private class NAPresenceCameraHandlerAccessible extends NAPresenceCameraHandler {
474 private NAPresenceCameraHandlerAccessible(Thing thing, NAWelcomeCamera presenceCamera) {
475 super(thing, timeZoneProviderMock);
476 setModule(presenceCamera);
480 protected @NonNull Optional<@NonNull String> executeGETRequest(@NonNull String url) {
481 return requestExecutorMock.executeGETRequest(url);
485 protected @NonNull State getLivePictureURLState() {
486 return super.getLivePictureURLState();
490 protected @NonNull State getLiveStreamState() {
491 return super.getLiveStreamState();