]> git.basschouten.com Git - openhab-addons.git/commitdiff
[boschshc] Add unit tests (#14426)
authorDavid Pace <dev@davidpace.de>
Mon, 20 Feb 2023 07:56:29 +0000 (08:56 +0100)
committerGitHub <noreply@github.com>
Mon, 20 Feb 2023 07:56:29 +0000 (08:56 +0100)
Signed-off-by: David Pace <dev@davidpace.de>
34 files changed:
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/AbstractBatteryPoweredDeviceHandlerTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/AbstractBoschSHCDeviceHandlerTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/AbstractBoschSHCHandlerTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/AbstractPowerSwitchHandlerTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/AbstractSHCHandlerTest.java [deleted file]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/AbstractSmokeDetectorHandlerTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandlerFactoryTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClientTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeConfigurationTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandlerTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/JsonRpcRequestTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPollingTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceServiceDataTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/LongPollResultTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/camera/CameraHandlerTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/climatecontrol/ClimateControlHandlerTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/intrusion/IntrusionDetectionHandlerTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/motiondetector/MotionDetectorHandlerTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandlerTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/smartbulb/SmartBulbHandlerTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/thermostat/ThermostatHandlerTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/twinguard/TwinguardHandlerTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/wallthermostat/WallThermostatHandlerTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/windowcontact/WindowContactHandlerTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/exceptions/LongPollingFailedExceptionTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/exceptions/PairingFailedExceptionTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/batterylevel/BatteryLevelTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceStateTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/JsonRestExceptionResponseTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/intrusion/IntrusionDetectionControlStateServiceTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/intrusion/IntrusionDetectionSystemStateServiceTest.java

index 52786a248253a6fb0fb90a8db945c38aa349102c..f97100d3ea82e22b3b3896c6d388bdda712992df 100644 (file)
@@ -28,7 +28,7 @@ import org.openhab.core.thing.ThingTypeUID;
 @NonNullByDefault
 public class BoschSHCBindingConstants {
 
-    private static final String BINDING_ID = "boschshc";
+    public static final String BINDING_ID = "boschshc";
 
     // List of all Thing Type UIDs
     public static final ThingTypeUID THING_TYPE_SHC = new ThingTypeUID(BINDING_ID, "shc");
index d8dab396c25c4a918ed2c32c6ab222d186d21e72..5856316b45c0bd46671c0965052bff9c582f28f9 100644 (file)
@@ -46,6 +46,7 @@ import org.openhab.core.thing.ThingStatusDetail;
 import org.openhab.core.thing.binding.BaseBridgeHandler;
 import org.openhab.core.thing.binding.ThingHandler;
 import org.openhab.core.types.Command;
+import org.osgi.framework.Bundle;
 import org.osgi.framework.FrameworkUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -95,8 +96,10 @@ public class BridgeHandler extends BaseBridgeHandler {
 
     @Override
     public void initialize() {
-        logger.debug("Initialize {} Version {}", FrameworkUtil.getBundle(getClass()).getSymbolicName(),
-                FrameworkUtil.getBundle(getClass()).getVersion());
+        Bundle bundle = FrameworkUtil.getBundle(getClass());
+        if (bundle != null) {
+            logger.debug("Initialize {} Version {}", bundle.getSymbolicName(), bundle.getVersion());
+        }
 
         // Read configuration
         BridgeConfiguration config = getConfigAs(BridgeConfiguration.class);
@@ -190,8 +193,10 @@ public class BridgeHandler extends BaseBridgeHandler {
      * to check if access if possible
      * pairs this Bosch SHC Bridge with the SHC if necessary
      * and starts the first log poll.
+     * <p>
+     * This method is package-protected to enable unit testing.
      */
-    private void initialAccess(BoschHttpClient httpClient) {
+    /* package */ void initialAccess(BoschHttpClient httpClient) {
         logger.debug("Initializing Bosch SHC Bridge: {} - HTTP client is: {}", this, httpClient);
 
         try {
@@ -482,7 +487,7 @@ public class BridgeHandler extends BaseBridgeHandler {
                                     deviceId, errorResponse.statusCode, errorResponse.errorCode));
                 }
             } else {
-                return new BoschSHCException(String.format("Request for info for device %s failed with status code %d",
+                return new BoschSHCException(String.format("Request for info of device %s failed with status code %d",
                         deviceId, statusCode));
             }
         });
index 1a243671c56dcaa6a1625d351b92696d70100cdc..6e85cefdf6bcd8e0ddacfff14aefe1d52c16b2ca 100644 (file)
  */
 package org.openhab.binding.boschshc.internal.devices;
 
-import static org.mockito.Mockito.verify;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.RefreshType;
 import org.openhab.core.types.UnDefType;
 
 import com.google.gson.JsonElement;
@@ -35,8 +43,20 @@ import com.google.gson.JsonParser;
 public abstract class AbstractBatteryPoweredDeviceHandlerTest<T extends AbstractBatteryPoweredDeviceHandler>
         extends AbstractBoschSHCDeviceHandlerTest<T> {
 
+    @BeforeEach
+    @Override
+    public void beforeEach() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        super.beforeEach();
+
+        DeviceServiceData deviceServiceData = new DeviceServiceData();
+        deviceServiceData.path = "/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel";
+        deviceServiceData.id = "BatteryLevel";
+        deviceServiceData.deviceId = "hdm:ZigBee:000d6f0004b93361";
+        lenient().when(bridgeHandler.getServiceData(anyString(), anyString())).thenReturn(deviceServiceData);
+    }
+
     @Test
-    public void testProcessUpdate_BatteryLevel_LowBattery() {
+    public void testProcessUpdateBatteryLevelLowBattery() {
         JsonElement deviceServiceData = JsonParser.parseString("{ \n" + "    \"@type\":\"DeviceServiceData\",\n"
                 + "    \"path\":\"/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel\",\n"
                 + "    \"id\":\"BatteryLevel\",\n" + "    \"deviceId\":\"hdm:ZigBee:000d6f0004b93361\",\n"
@@ -44,15 +64,13 @@ public abstract class AbstractBatteryPoweredDeviceHandlerTest<T extends Abstract
                 + "            \"type\":\"LOW_BATTERY\",\n" + "            \"category\":\"WARNING\"\n" + "          }\n"
                 + "        ]\n" + "    }\n" + "}");
         getFixture().processUpdate("BatteryLevel", deviceServiceData);
-        verify(getCallback()).stateUpdated(
-                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_BATTERY_LEVEL),
+        verify(getCallback()).stateUpdated(getChannelUID(BoschSHCBindingConstants.CHANNEL_BATTERY_LEVEL),
                 new DecimalType(10));
-        verify(getCallback()).stateUpdated(
-                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_LOW_BATTERY), OnOffType.ON);
+        verify(getCallback()).stateUpdated(getChannelUID(BoschSHCBindingConstants.CHANNEL_LOW_BATTERY), OnOffType.ON);
     }
 
     @Test
-    public void testProcessUpdate_BatteryLevel_CriticalLow() {
+    public void testProcessUpdateBatteryLevelCriticalLow() {
         JsonElement deviceServiceData = JsonParser.parseString("{ \n" + "    \"@type\":\"DeviceServiceData\",\n"
                 + "    \"path\":\"/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel\",\n"
                 + "    \"id\":\"BatteryLevel\",\n" + "    \"deviceId\":\"hdm:ZigBee:000d6f0004b93361\",\n"
@@ -60,15 +78,13 @@ public abstract class AbstractBatteryPoweredDeviceHandlerTest<T extends Abstract
                 + "            \"type\":\"CRITICAL_LOW\",\n" + "            \"category\":\"WARNING\"\n"
                 + "          }\n" + "        ]\n" + "    }\n" + "}");
         getFixture().processUpdate("BatteryLevel", deviceServiceData);
-        verify(getCallback()).stateUpdated(
-                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_BATTERY_LEVEL),
+        verify(getCallback()).stateUpdated(getChannelUID(BoschSHCBindingConstants.CHANNEL_BATTERY_LEVEL),
                 new DecimalType(1));
-        verify(getCallback()).stateUpdated(
-                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_LOW_BATTERY), OnOffType.ON);
+        verify(getCallback()).stateUpdated(getChannelUID(BoschSHCBindingConstants.CHANNEL_LOW_BATTERY), OnOffType.ON);
     }
 
     @Test
-    public void testProcessUpdate_BatteryLevel_CriticallyLowBattery() {
+    public void testProcessUpdateBatteryLevelCriticallyLowBattery() {
         JsonElement deviceServiceData = JsonParser.parseString("{ \n" + "    \"@type\":\"DeviceServiceData\",\n"
                 + "    \"path\":\"/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel\",\n"
                 + "    \"id\":\"BatteryLevel\",\n" + "    \"deviceId\":\"hdm:ZigBee:000d6f0004b93361\",\n"
@@ -76,15 +92,13 @@ public abstract class AbstractBatteryPoweredDeviceHandlerTest<T extends Abstract
                 + "            \"type\":\"CRITICALLY_LOW_BATTERY\",\n" + "            \"category\":\"WARNING\"\n"
                 + "          }\n" + "        ]\n" + "    }\n" + "}");
         getFixture().processUpdate("BatteryLevel", deviceServiceData);
-        verify(getCallback()).stateUpdated(
-                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_BATTERY_LEVEL),
+        verify(getCallback()).stateUpdated(getChannelUID(BoschSHCBindingConstants.CHANNEL_BATTERY_LEVEL),
                 new DecimalType(1));
-        verify(getCallback()).stateUpdated(
-                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_LOW_BATTERY), OnOffType.ON);
+        verify(getCallback()).stateUpdated(getChannelUID(BoschSHCBindingConstants.CHANNEL_LOW_BATTERY), OnOffType.ON);
     }
 
     @Test
-    public void testProcessUpdate_BatteryLevel_OK() {
+    public void testProcessUpdateBatteryLevelOK() {
         JsonElement deviceServiceData = JsonParser.parseString("{ \n" + "    \"@type\":\"DeviceServiceData\",\n"
                 + "    \"path\":\"/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel\",\n"
                 + "    \"id\":\"BatteryLevel\",\n" + "    \"deviceId\":\"hdm:ZigBee:000d6f0004b93361\" }");
@@ -97,7 +111,7 @@ public abstract class AbstractBatteryPoweredDeviceHandlerTest<T extends Abstract
     }
 
     @Test
-    public void testProcessUpdate_BatteryLevel_NotAvailable() {
+    public void testProcessUpdateBatteryLevelNotAvailable() {
         JsonElement deviceServiceData = JsonParser.parseString("{ \n" + "    \"@type\":\"DeviceServiceData\",\n"
                 + "    \"path\":\"/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel\",\n"
                 + "    \"id\":\"BatteryLevel\",\n" + "    \"deviceId\":\"hdm:ZigBee:000d6f0004b93361\",\n"
@@ -105,9 +119,21 @@ public abstract class AbstractBatteryPoweredDeviceHandlerTest<T extends Abstract
                 + "            \"type\":\"NOT_AVAILABLE\",\n" + "            \"category\":\"WARNING\"\n"
                 + "          }\n" + "        ]\n" + "    }\n" + "}");
         getFixture().processUpdate("BatteryLevel", deviceServiceData);
-        verify(getCallback()).stateUpdated(
-                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_BATTERY_LEVEL), UnDefType.UNDEF);
-        verify(getCallback()).stateUpdated(
-                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_LOW_BATTERY), OnOffType.OFF);
+        verify(getCallback()).stateUpdated(getChannelUID(BoschSHCBindingConstants.CHANNEL_BATTERY_LEVEL),
+                UnDefType.UNDEF);
+        verify(getCallback()).stateUpdated(getChannelUID(BoschSHCBindingConstants.CHANNEL_LOW_BATTERY), OnOffType.OFF);
+    }
+
+    @Test
+    public void testHandleCommandRefreshBatteryLevelChannel() {
+        getFixture().handleCommand(getChannelUID(BoschSHCBindingConstants.CHANNEL_BATTERY_LEVEL), RefreshType.REFRESH);
+        verify(getCallback()).stateUpdated(getChannelUID(BoschSHCBindingConstants.CHANNEL_BATTERY_LEVEL),
+                new DecimalType(100));
+    }
+
+    @Test
+    public void testHandleCommandRefreshLowBatteryChannel() {
+        getFixture().handleCommand(getChannelUID(BoschSHCBindingConstants.CHANNEL_LOW_BATTERY), RefreshType.REFRESH);
+        verify(getCallback()).stateUpdated(getChannelUID(BoschSHCBindingConstants.CHANNEL_LOW_BATTERY), OnOffType.OFF);
     }
 }
index 5f104b8219f715768c48c0e154c9e0681199fd96..ba11a32b005b9ba7f62bb7c2f3a5533a28355ee7 100644 (file)
@@ -12,6 +12,7 @@
  */
 package org.openhab.binding.boschshc.internal.devices;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.core.config.core.Configuration;
 
 /**
@@ -21,8 +22,9 @@ import org.openhab.core.config.core.Configuration;
  *
  * @param <T> type of the device handler to be tested
  */
+@NonNullByDefault
 public abstract class AbstractBoschSHCDeviceHandlerTest<T extends BoschSHCDeviceHandler>
-        extends AbstractSHCHandlerTest<T> {
+        extends AbstractBoschSHCHandlerTest<T> {
 
     @Override
     protected Configuration getConfiguration() {
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/AbstractBoschSHCHandlerTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/AbstractBoschSHCHandlerTest.java
new file mode 100644 (file)
index 0000000..a3ccf28
--- /dev/null
@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.devices;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandlerCallback;
+
+/**
+ * Abstract unit test implementation for all types of handlers.
+ *
+ * @author David Pace - Initial contribution
+ *
+ * @param <T> type of the handler to be tested
+ */
+@NonNullByDefault
+@ExtendWith(MockitoExtension.class)
+public abstract class AbstractBoschSHCHandlerTest<T extends BoschSHCHandler> {
+
+    private T fixture;
+
+    private @Mock @NonNullByDefault({}) Thing thing;
+
+    private @Mock @NonNullByDefault({}) Bridge bridge;
+
+    protected @Mock @NonNullByDefault({}) BridgeHandler bridgeHandler;
+
+    private @Mock @NonNullByDefault({}) ThingHandlerCallback callback;
+
+    protected AbstractBoschSHCHandlerTest() {
+        this.fixture = createFixture();
+    }
+
+    @BeforeEach
+    void beforeEach() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        fixture = createFixture();
+        lenient().when(thing.getUID()).thenReturn(getThingUID());
+        when(thing.getBridgeUID()).thenReturn(new ThingUID("boschshc", "shc", "myBridgeUID"));
+        when(callback.getBridge(any())).thenReturn(bridge);
+        fixture.setCallback(callback);
+        when(bridge.getHandler()).thenReturn(bridgeHandler);
+        lenient().when(thing.getConfiguration()).thenReturn(getConfiguration());
+
+        fixture.initialize();
+    }
+
+    protected abstract T createFixture();
+
+    protected T getFixture() {
+        return fixture;
+    }
+
+    protected ThingUID getThingUID() {
+        return new ThingUID(getThingTypeUID(), "abcdef");
+    }
+
+    protected abstract ThingTypeUID getThingTypeUID();
+
+    protected ChannelUID getChannelUID(String channelID) {
+        return new ChannelUID(getThingUID(), channelID);
+    }
+
+    protected Configuration getConfiguration() {
+        return new Configuration();
+    }
+
+    protected Thing getThing() {
+        return thing;
+    }
+
+    protected BridgeHandler getBridgeHandler() {
+        return bridgeHandler;
+    }
+
+    protected ThingHandlerCallback getCallback() {
+        return callback;
+    }
+
+    @Test
+    public void testInitialize() {
+        ThingStatusInfo expectedStatusInfo = new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
+        verify(callback).statusUpdated(same(thing), eq(expectedStatusInfo));
+    }
+}
index 1dc632bc6cc481ef262d1f12124b721e2d89caa7..6ec8f4b40dce7604669b100da2a9822d51443038 100644 (file)
@@ -13,7 +13,7 @@
 package org.openhab.binding.boschshc.internal.devices;
 
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
 
 import java.util.concurrent.ExecutionException;
@@ -22,16 +22,19 @@ import java.util.concurrent.TimeoutException;
 import javax.measure.quantity.Energy;
 import javax.measure.quantity.Power;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.binding.boschshc.internal.services.powermeter.dto.PowerMeterServiceState;
 import org.openhab.binding.boschshc.internal.services.powerswitch.PowerSwitchState;
 import org.openhab.binding.boschshc.internal.services.powerswitch.dto.PowerSwitchServiceState;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.ThingUID;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.types.RefreshType;
 
 import com.google.gson.JsonElement;
 import com.google.gson.JsonParser;
@@ -43,30 +46,42 @@ import com.google.gson.JsonParser;
  *
  * @param <T> type of the handler to be tested
  */
+@NonNullByDefault
 public abstract class AbstractPowerSwitchHandlerTest<T extends AbstractPowerSwitchHandler>
         extends AbstractBoschSHCDeviceHandlerTest<T> {
 
-    @Captor
-    private ArgumentCaptor<PowerSwitchServiceState> serviceStateCaptor;
+    private @Captor @NonNullByDefault({}) ArgumentCaptor<PowerSwitchServiceState> serviceStateCaptor;
 
-    @Captor
-    private ArgumentCaptor<QuantityType<Power>> powerCaptor;
+    private @Captor @NonNullByDefault({}) ArgumentCaptor<QuantityType<Power>> powerCaptor;
 
-    @Captor
-    private ArgumentCaptor<QuantityType<Energy>> energyCaptor;
+    private @Captor @NonNullByDefault({}) ArgumentCaptor<QuantityType<Energy>> energyCaptor;
+
+    @BeforeEach
+    @Override
+    public void beforeEach() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        super.beforeEach();
+
+        PowerSwitchServiceState powerSwitchServiceState = new PowerSwitchServiceState();
+        powerSwitchServiceState.switchState = PowerSwitchState.ON;
+        lenient().when(bridgeHandler.getState(anyString(), eq("PowerSwitch"), same(PowerSwitchServiceState.class)))
+                .thenReturn(powerSwitchServiceState);
+
+        PowerMeterServiceState powerMeterServiceState = new PowerMeterServiceState();
+        powerMeterServiceState.powerConsumption = 12.34d;
+        powerMeterServiceState.energyConsumption = 56.78d;
+        lenient().when(bridgeHandler.getState(anyString(), eq("PowerMeter"), same(PowerMeterServiceState.class)))
+                .thenReturn(powerMeterServiceState);
+    }
 
     @Test
-    public void testHandleCommand()
+    public void testHandleCommandPowerSwitchChannel()
             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
-
-        getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_POWER_SWITCH),
-                OnOffType.ON);
+        getFixture().handleCommand(getChannelUID(BoschSHCBindingConstants.CHANNEL_POWER_SWITCH), OnOffType.ON);
         verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("PowerSwitch"), serviceStateCaptor.capture());
         PowerSwitchServiceState state = serviceStateCaptor.getValue();
         assertSame(PowerSwitchState.ON, state.switchState);
 
-        getFixture().handleCommand(new ChannelUID(new ThingUID(getThingTypeUID(), "abcdef"),
-                BoschSHCBindingConstants.CHANNEL_POWER_SWITCH), OnOffType.OFF);
+        getFixture().handleCommand(getChannelUID(BoschSHCBindingConstants.CHANNEL_POWER_SWITCH), OnOffType.OFF);
         verify(getBridgeHandler(), times(2)).putState(eq(getDeviceID()), eq("PowerSwitch"),
                 serviceStateCaptor.capture());
         state = serviceStateCaptor.getValue();
@@ -74,36 +89,54 @@ public abstract class AbstractPowerSwitchHandlerTest<T extends AbstractPowerSwit
     }
 
     @Test
-    public void testUpdateChannel_PowerSwitchState() {
+    public void testUpdateChannelPowerSwitchState() {
         JsonElement jsonObject = JsonParser
                 .parseString("{\n" + "  \"@type\": \"powerSwitchState\",\n" + "  \"switchState\": \"ON\"\n" + "}");
         getFixture().processUpdate("PowerSwitch", jsonObject);
-        verify(getCallback()).stateUpdated(
-                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_POWER_SWITCH), OnOffType.ON);
+        verify(getCallback()).stateUpdated(getChannelUID(BoschSHCBindingConstants.CHANNEL_POWER_SWITCH), OnOffType.ON);
 
         jsonObject = JsonParser
                 .parseString("{\n" + "  \"@type\": \"powerSwitchState\",\n" + "  \"switchState\": \"OFF\"\n" + "}");
         getFixture().processUpdate("PowerSwitch", jsonObject);
-        verify(getCallback()).stateUpdated(
-                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_POWER_SWITCH), OnOffType.OFF);
+        verify(getCallback()).stateUpdated(getChannelUID(BoschSHCBindingConstants.CHANNEL_POWER_SWITCH), OnOffType.OFF);
     }
 
     @Test
-    public void testUpdateChannel_PowerMeterServiceState() {
+    public void testUpdateChannelPowerMeterServiceState() {
         JsonElement jsonObject = JsonParser.parseString("{\n" + "  \"@type\": \"powerMeterState\",\n"
                 + "  \"powerConsumption\": \"23\",\n" + "  \"energyConsumption\": 42\n" + "}");
         getFixture().processUpdate("PowerMeter", jsonObject);
 
-        verify(getCallback()).stateUpdated(
-                eq(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_POWER_CONSUMPTION)),
+        verify(getCallback()).stateUpdated(eq(getChannelUID(BoschSHCBindingConstants.CHANNEL_POWER_CONSUMPTION)),
                 powerCaptor.capture());
         QuantityType<Power> powerValue = powerCaptor.getValue();
         assertEquals(23, powerValue.intValue());
 
-        verify(getCallback()).stateUpdated(
-                eq(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ENERGY_CONSUMPTION)),
+        verify(getCallback()).stateUpdated(eq(getChannelUID(BoschSHCBindingConstants.CHANNEL_ENERGY_CONSUMPTION)),
                 energyCaptor.capture());
         QuantityType<Energy> energyValue = energyCaptor.getValue();
         assertEquals(42, energyValue.intValue());
     }
+
+    @Test
+    public void testHandleCommandRefreshPowerSwitchChannel() {
+        getFixture().handleCommand(getChannelUID(BoschSHCBindingConstants.CHANNEL_POWER_SWITCH), RefreshType.REFRESH);
+        verify(getCallback()).stateUpdated(getChannelUID(BoschSHCBindingConstants.CHANNEL_POWER_SWITCH), OnOffType.ON);
+    }
+
+    @Test
+    public void testHandleCommandRefreshPowerConsumptionChannel() {
+        getFixture().handleCommand(getChannelUID(BoschSHCBindingConstants.CHANNEL_POWER_CONSUMPTION),
+                RefreshType.REFRESH);
+        verify(getCallback()).stateUpdated(getChannelUID(BoschSHCBindingConstants.CHANNEL_POWER_CONSUMPTION),
+                new QuantityType<Power>(12.34d, Units.WATT));
+    }
+
+    @Test
+    public void testHandleCommandRefreshEnergyConsumptionChannel() {
+        getFixture().handleCommand(getChannelUID(BoschSHCBindingConstants.CHANNEL_ENERGY_CONSUMPTION),
+                RefreshType.REFRESH);
+        verify(getCallback()).stateUpdated(getChannelUID(BoschSHCBindingConstants.CHANNEL_ENERGY_CONSUMPTION),
+                new QuantityType<Energy>(56.78d, Units.WATT_HOUR));
+    }
 }
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/AbstractSHCHandlerTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/AbstractSHCHandlerTest.java
deleted file mode 100644 (file)
index 18fe7ff..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.boschshc.internal.devices;
-
-import static org.mockito.ArgumentMatchers.*;
-import static org.mockito.Mockito.*;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.ThingStatusInfo;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.ThingUID;
-import org.openhab.core.thing.binding.ThingHandlerCallback;
-
-/**
- * Abstract unit test implementation for all types of handlers.
- *
- * @author David Pace - Initial contribution
- *
- * @param <T> type of the handler to be tested
- */
-@ExtendWith(MockitoExtension.class)
-public abstract class AbstractSHCHandlerTest<T extends BoschSHCHandler> {
-
-    private T fixture;
-
-    @Mock
-    private Thing thing;
-
-    @Mock
-    private Bridge bridge;
-
-    @Mock
-    private BridgeHandler bridgeHandler;
-
-    @Mock
-    private ThingHandlerCallback callback;
-
-    @BeforeEach
-    public void beforeEach() {
-        fixture = createFixture();
-        lenient().when(thing.getUID()).thenReturn(getThingUID());
-        when(thing.getBridgeUID()).thenReturn(new ThingUID("boschshc", "shc", "myBridgeUID"));
-        when(callback.getBridge(any())).thenReturn(bridge);
-        fixture.setCallback(callback);
-        when(bridge.getHandler()).thenReturn(bridgeHandler);
-        when(thing.getConfiguration()).thenReturn(getConfiguration());
-
-        fixture.initialize();
-    }
-
-    protected abstract T createFixture();
-
-    protected T getFixture() {
-        return fixture;
-    }
-
-    protected ThingUID getThingUID() {
-        return new ThingUID(getThingTypeUID(), "abcdef");
-    }
-
-    protected abstract ThingTypeUID getThingTypeUID();
-
-    protected Configuration getConfiguration() {
-        return new Configuration();
-    }
-
-    protected Thing getThing() {
-        return thing;
-    }
-
-    public BridgeHandler getBridgeHandler() {
-        return bridgeHandler;
-    }
-
-    public ThingHandlerCallback getCallback() {
-        return callback;
-    }
-
-    @Test
-    public void testInitialize() {
-        ThingStatusInfo expectedStatusInfo = new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
-        verify(callback).statusUpdated(same(thing), eq(expectedStatusInfo));
-    }
-}
index a04452bdf4b159e709d8a89ac157c031469000b4..76898ffaeeb2f459b480ec10fa571e566d286bd3 100644 (file)
@@ -14,8 +14,7 @@ package org.openhab.binding.boschshc.internal.devices;
 
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.*;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -28,9 +27,14 @@ import org.openhab.binding.boschshc.internal.devices.smokedetector.SmokeDetector
 import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
 import org.openhab.binding.boschshc.internal.services.smokedetectorcheck.SmokeDetectorCheckState;
 import org.openhab.binding.boschshc.internal.services.smokedetectorcheck.dto.SmokeDetectorCheckServiceState;
+import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.PlayPauseType;
 import org.openhab.core.library.types.StringType;
 import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
 
 import com.google.gson.JsonElement;
 import com.google.gson.JsonParser;
@@ -51,7 +55,6 @@ public abstract class AbstractSmokeDetectorHandlerTest<T extends AbstractSmokeDe
     @Test
     public void testHandleCommand()
             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
-
         // valid commands with valid thing & channel
         getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SMOKE_CHECK),
                 new StringType(SmokeDetectorCheckState.SMOKE_TEST_REQUESTED.toString()));
@@ -83,9 +86,8 @@ public abstract class AbstractSmokeDetectorHandlerTest<T extends AbstractSmokeDe
     }
 
     @Test
-    public void testHandleCommand_PlayPauseType()
+    public void testHandleCommandPlayPauseType()
             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
-
         getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SMOKE_CHECK),
                 PlayPauseType.PLAY);
         verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("SmokeDetectorCheck"),
@@ -95,7 +97,20 @@ public abstract class AbstractSmokeDetectorHandlerTest<T extends AbstractSmokeDe
     }
 
     @Test
-    public void testUpdateChannel_SmokeDetectorCheckServiceState_none() {
+    public void testHandleCommandUnknownCommand()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SMOKE_CHECK),
+                OnOffType.ON);
+        ThingStatusInfo expectedThingStatusInfo = ThingStatusInfoBuilder
+                .create(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR)
+                .withDescription(
+                        "Error when service SmokeDetectorCheck should handle command org.openhab.core.library.types.OnOffType: SmokeDetectorCheck: Can not handle command org.openhab.core.library.types.OnOffType")
+                .build();
+        verify(getCallback()).statusUpdated(getThing(), expectedThingStatusInfo);
+    }
+
+    @Test
+    public void testUpdateChannelSmokeDetectorCheckServiceStateNone() {
         JsonElement jsonObject = JsonParser.parseString("{\"@type\":\"smokeDetectorCheckState\",\"value\":NONE}");
         getFixture().processUpdate("SmokeDetectorCheck", jsonObject);
         verify(getCallback()).stateUpdated(
@@ -104,7 +119,7 @@ public abstract class AbstractSmokeDetectorHandlerTest<T extends AbstractSmokeDe
     }
 
     @Test
-    public void testUpdateChannel_SmokeDetectorCheckServiceState_Requests() {
+    public void testUpdateChannelSmokeDetectorCheckServiceStateRequests() {
         JsonElement jsonObject = JsonParser
                 .parseString("{\"@type\":\"smokeDetectorCheckState\",\"value\":SMOKE_TEST_REQUESTED}");
         getFixture().processUpdate("SmokeDetectorCheck", jsonObject);
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandlerFactoryTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandlerFactoryTest.java
new file mode 100644 (file)
index 0000000..91c097c
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.devices;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.boschshc.internal.devices.plug.PlugHandler;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * Unit tests for {@link BoschSHCHandlerFactory}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class BoschSHCHandlerFactoryTest {
+
+    private @NonNullByDefault({}) BoschSHCHandlerFactory fixture;
+
+    @BeforeEach
+    public void setUp() throws Exception {
+        fixture = new BoschSHCHandlerFactory();
+    }
+
+    @Test
+    public void testSupportsThingType() {
+        assertTrue(fixture.supportsThingType(BoschSHCBindingConstants.THING_TYPE_SHC));
+        assertTrue(fixture.supportsThingType(BoschSHCBindingConstants.THING_TYPE_INWALL_SWITCH));
+        assertTrue(fixture.supportsThingType(BoschSHCBindingConstants.THING_TYPE_TWINGUARD));
+        assertTrue(fixture.supportsThingType(BoschSHCBindingConstants.THING_TYPE_WINDOW_CONTACT));
+        assertTrue(fixture.supportsThingType(BoschSHCBindingConstants.THING_TYPE_MOTION_DETECTOR));
+        assertTrue(fixture.supportsThingType(BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL));
+        assertTrue(fixture.supportsThingType(BoschSHCBindingConstants.THING_TYPE_THERMOSTAT));
+        assertTrue(fixture.supportsThingType(BoschSHCBindingConstants.THING_TYPE_CLIMATE_CONTROL));
+        assertTrue(fixture.supportsThingType(BoschSHCBindingConstants.THING_TYPE_WALL_THERMOSTAT));
+        assertTrue(fixture.supportsThingType(BoschSHCBindingConstants.THING_TYPE_CAMERA_360));
+        assertTrue(fixture.supportsThingType(BoschSHCBindingConstants.THING_TYPE_CAMERA_EYES));
+        assertTrue(fixture.supportsThingType(BoschSHCBindingConstants.THING_TYPE_INTRUSION_DETECTION_SYSTEM));
+        assertTrue(fixture.supportsThingType(BoschSHCBindingConstants.THING_TYPE_SMART_PLUG_COMPACT));
+        assertTrue(fixture.supportsThingType(BoschSHCBindingConstants.THING_TYPE_SMART_BULB));
+        assertTrue(fixture.supportsThingType(BoschSHCBindingConstants.THING_TYPE_SMOKE_DETECTOR));
+
+        assertFalse(fixture.supportsThingType(new ThingTypeUID(BoschSHCBindingConstants.BINDING_ID, "foo")));
+    }
+
+    @Test
+    public void testCreateHandler() {
+        Thing thing = mock(Thing.class);
+        when(thing.getThingTypeUID()).thenReturn(BoschSHCBindingConstants.THING_TYPE_SMART_PLUG_COMPACT);
+        assertTrue(fixture.createHandler(thing) instanceof PlugHandler);
+    }
+}
index 0c6b8518808ac8cb8e5c3448d477c4bc49fb61cd..5809c1d4376474db8bfe9bc6f49e269ae92b82d5 100644 (file)
 package org.openhab.binding.boschshc.internal.devices.bridge;
 
 import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.lang.reflect.Field;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.api.ContentResponse;
 import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.http.HttpMethod;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.platform.commons.support.HierarchyTraversalMode;
+import org.junit.platform.commons.support.ReflectionSupport;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
 import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException;
+import org.openhab.binding.boschshc.internal.services.binaryswitch.dto.BinarySwitchServiceState;
+import org.slf4j.Logger;
 
 /**
  * Tests cases for {@link BoschHttpClient}.
@@ -33,8 +47,7 @@ import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException;
 @NonNullByDefault
 class BoschHttpClientTest {
 
-    @Nullable
-    private BoschHttpClient httpClient;
+    private @NonNullByDefault({}) BoschHttpClient httpClient;
 
     @BeforeAll
     static void beforeAll() {
@@ -91,6 +104,53 @@ class BoschHttpClientTest {
         assertFalse(httpClient.isOnline());
     }
 
+    @Test
+    void isOnlineErrorResponse() throws InterruptedException, IllegalArgumentException, IllegalAccessException,
+            TimeoutException, ExecutionException {
+        BoschHttpClient mockedHttpClient = mock(BoschHttpClient.class);
+        when(mockedHttpClient.isOnline()).thenCallRealMethod();
+        when(mockedHttpClient.getPublicInformationUrl()).thenCallRealMethod();
+
+        // mock a logger using reflection to avoid NPEs during logger calls
+        Logger mockedLogger = mock(Logger.class);
+        List<Field> fields = ReflectionSupport.findFields(BoschHttpClient.class,
+                f -> f.getName().equalsIgnoreCase("logger"), HierarchyTraversalMode.TOP_DOWN);
+        Field field = fields.iterator().next();
+        field.setAccessible(true);
+        field.set(mockedHttpClient, mockedLogger);
+
+        Request request = mock(Request.class);
+        when(mockedHttpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
+        ContentResponse response = mock(ContentResponse.class);
+        when(request.send()).thenReturn(response);
+        when(response.getStatus()).thenReturn(500);
+        assertFalse(mockedHttpClient.isOnline());
+    }
+
+    @Test
+    void isOnlineMockedResponse() throws InterruptedException, TimeoutException, ExecutionException,
+            IllegalArgumentException, IllegalAccessException {
+        BoschHttpClient mockedHttpClient = mock(BoschHttpClient.class);
+        when(mockedHttpClient.isOnline()).thenCallRealMethod();
+        when(mockedHttpClient.getPublicInformationUrl()).thenCallRealMethod();
+
+        // mock a logger using reflection to avoid NPEs during logger calls
+        Logger mockedLogger = mock(Logger.class);
+        List<Field> fields = ReflectionSupport.findFields(BoschHttpClient.class,
+                f -> f.getName().equalsIgnoreCase("logger"), HierarchyTraversalMode.TOP_DOWN);
+        Field field = fields.iterator().next();
+        field.setAccessible(true);
+        field.set(mockedHttpClient, mockedLogger);
+
+        Request request = mock(Request.class);
+        when(mockedHttpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
+        ContentResponse response = mock(ContentResponse.class);
+        when(request.send()).thenReturn(response);
+        when(response.getStatus()).thenReturn(200);
+        when(response.getContentAsString()).thenReturn("response");
+        assertTrue(mockedHttpClient.isOnline());
+    }
+
     @Test
     void doPairing() throws InterruptedException {
         assertFalse(httpClient.doPairing());
@@ -104,16 +164,103 @@ class BoschHttpClientTest {
 
     @Test
     void createRequestWithObject() {
-        Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET, "someData");
+        BinarySwitchServiceState binarySwitchState = new BinarySwitchServiceState();
+        binarySwitchState.on = true;
+        Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET, binarySwitchState);
         assertNotNull(request);
+        assertEquals("{\"on\":true,\"stateType\":\"binarySwitchState\",\"@type\":\"binarySwitchState\"}",
+                StandardCharsets.UTF_8.decode(request.getContent().iterator().next()).toString());
     }
 
     @Test
-    void sendRequest() {
-        Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET);
-        // Null pointer exception is expected, because localhost will not answer request
-        assertThrows(NullPointerException.class, () -> {
-            httpClient.sendRequest(request, SubscribeResult.class, SubscribeResult::isValid, null);
-        });
+    void sendRequest() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        Request request = mock(Request.class);
+        ContentResponse response = mock(ContentResponse.class);
+        when(request.send()).thenReturn(response);
+        when(response.getStatus()).thenReturn(200);
+        when(response.getContentAsString()).thenReturn("{\"jsonrpc\": \"2.0\", \"result\": \"test result\"}");
+
+        SubscribeResult subscribeResult = httpClient.sendRequest(request, SubscribeResult.class,
+                SubscribeResult::isValid, null);
+        assertEquals("2.0", subscribeResult.getJsonrpc());
+        assertEquals("test result", subscribeResult.getResult());
+    }
+
+    @Test
+    void sendRequestResponseError()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        Request request = mock(Request.class);
+        ContentResponse response = mock(ContentResponse.class);
+        when(request.send()).thenReturn(response);
+        when(response.getStatus()).thenReturn(500);
+        ExecutionException e = assertThrows(ExecutionException.class,
+                () -> httpClient.sendRequest(request, SubscribeResult.class, SubscribeResult::isValid, null));
+        assertEquals("Request failed with status code 500", e.getMessage());
+    }
+
+    @Test
+    void sendRequestResponseErrorWithErrorHandler()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        Request request = mock(Request.class);
+        ContentResponse response = mock(ContentResponse.class);
+        when(request.send()).thenReturn(response);
+        when(response.getStatus()).thenReturn(500);
+        when(response.getContentAsString()).thenReturn(
+                "{\"@type\": \"JsonRestExceptionResponseEntity\", \"errorCode\": \"500\", \"statusCode\": \"500\"}");
+
+        BoschSHCException e = assertThrows(BoschSHCException.class, () -> httpClient.sendRequest(request, Device.class,
+                Device::isValid, (Integer statusCode, String content) -> {
+                    return new BoschSHCException("test exception");
+                }));
+        assertEquals("test exception", e.getMessage());
+    }
+
+    @Test
+    void sendRequestEmptyResponse()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        Request request = mock(Request.class);
+        ContentResponse response = mock(ContentResponse.class);
+        when(request.send()).thenReturn(response);
+        when(response.getStatus()).thenReturn(200);
+        ExecutionException e = assertThrows(ExecutionException.class,
+                () -> httpClient.sendRequest(request, SubscribeResult.class, SubscribeResult::isValid, null));
+        assertEquals(
+                "Received no content in response, expected type org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult",
+                e.getMessage());
+    }
+
+    @Test
+    void sendRequestInvalidResponse()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        Request request = mock(Request.class);
+        ContentResponse response = mock(ContentResponse.class);
+        when(request.send()).thenReturn(response);
+        when(response.getStatus()).thenReturn(200);
+        when(response.getContentAsString()).thenReturn(
+                "{\"@type\": \"JsonRestExceptionResponseEntity\", \"errorCode\": \"500\", \"statusCode\": \"500\"}");
+        ExecutionException e = assertThrows(ExecutionException.class,
+                () -> httpClient.sendRequest(request, SubscribeResult.class, sr -> {
+                    return false;
+                }, null));
+        String actualMessage = e.getMessage();
+        assertTrue(actualMessage.contains(
+                "Received invalid content for type org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult:"));
+    }
+
+    @Test
+    void sendRequestInvalidSyntaxInResponse()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        Request request = mock(Request.class);
+        ContentResponse response = mock(ContentResponse.class);
+        when(request.send()).thenReturn(response);
+        when(response.getStatus()).thenReturn(200);
+        when(response.getContentAsString()).thenReturn("{\"@type\": \"JsonRestExceptionResponseEntity}");
+        ExecutionException e = assertThrows(ExecutionException.class,
+                () -> httpClient.sendRequest(request, SubscribeResult.class, sr -> {
+                    return false;
+                }, null));
+        assertEquals(
+                "Received invalid content in response, expected type org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult: com.google.gson.stream.MalformedJsonException: Unterminated string at line 1 column 44 path $.@type",
+                e.getMessage());
     }
 }
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeConfigurationTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeConfigurationTest.java
new file mode 100644 (file)
index 0000000..4a6a8d3
--- /dev/null
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.devices.bridge;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link BridgeConfiguration}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class BridgeConfigurationTest {
+
+    @Test
+    void testConstructor() {
+        BridgeConfiguration fixture = new BridgeConfiguration();
+        assertEquals("", fixture.ipAddress);
+        assertEquals("", fixture.password);
+    }
+}
index 3c61340c1fa64ef8af8603563d775cbdd6791ca2..1f77e17416b2bce5e9e7cf6477f898fdaf8d6414 100644 (file)
  */
 package org.openhab.binding.boschshc.internal.devices.bridge;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
 
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
+import java.util.function.BiFunction;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.api.ContentResponse;
 import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.http.HttpMethod;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceTest;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.Faults;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.binding.boschshc.internal.services.binaryswitch.dto.BinarySwitchServiceState;
 import org.openhab.binding.boschshc.internal.services.intrusion.actions.arm.dto.ArmActionRequest;
+import org.openhab.binding.boschshc.internal.services.intrusion.dto.AlarmState;
+import org.openhab.binding.boschshc.internal.services.intrusion.dto.ArmingState;
+import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDetectionSystemState;
+import org.openhab.binding.boschshc.internal.services.shuttercontact.ShutterContactState;
+import org.openhab.binding.boschshc.internal.services.shuttercontact.dto.ShutterContactServiceState;
+import org.openhab.core.config.core.Configuration;
 import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.ThingHandlerCallback;
+import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
 
 /**
  * Unit tests for the {@link BridgeHandler}.
- * 
+ *
  * @author David Pace - Initial contribution
  *
  */
 @NonNullByDefault
 class BridgeHandlerTest {
 
-    @Nullable
-    private BridgeHandler fixture;
+    private @NonNullByDefault({}) BridgeHandler fixture;
+
+    private @NonNullByDefault({}) BoschHttpClient httpClient;
 
-    @Nullable
-    private BoschHttpClient httpClient;
+    private @NonNullByDefault({}) ThingHandlerCallback thingHandlerCallback;
+
+    @BeforeAll
+    static void beforeAll() throws IOException {
+        Path mavenTargetFolder = Paths.get("target");
+        assertTrue(Files.exists(mavenTargetFolder), "Maven target folder does not exist.");
+        System.setProperty("openhab.userdata", mavenTargetFolder.toFile().getAbsolutePath());
+        Path etc = mavenTargetFolder.resolve("etc");
+        if (!Files.exists(etc)) {
+            Files.createDirectory(etc);
+        }
+    }
 
     @BeforeEach
-    void beforeEach() {
+    void beforeEach() throws Exception {
         Bridge bridge = mock(Bridge.class);
         fixture = new BridgeHandler(bridge);
+
+        thingHandlerCallback = mock(ThingHandlerCallback.class);
+        fixture.setCallback(thingHandlerCallback);
+
+        Configuration bridgeConfiguration = new Configuration();
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("ipAddress", "localhost");
+        properties.put("password", "test");
+        bridgeConfiguration.setProperties(properties);
+
+        Thing thing = mock(Bridge.class);
+        when(thing.getConfiguration()).thenReturn(bridgeConfiguration);
+        // this calls initialize() as well
+        fixture.thingUpdated(thing);
+
+        // shut down the real HTTP client
+        if (fixture.httpClient != null) {
+            fixture.httpClient.stop();
+        }
+
+        // use a mocked HTTP client
         httpClient = mock(BoschHttpClient.class);
         fixture.httpClient = httpClient;
     }
@@ -69,4 +125,265 @@ class BridgeHandlerTest {
         verify(httpClient).createRequest(eq(url), same(HttpMethod.POST), same(request));
         verify(mockRequest).send();
     }
+
+    @Test
+    void initialAccessHttpClientOffline() {
+        fixture.initialAccess(httpClient);
+    }
+
+    @Test
+    void initialAccessHttpClientOnline() throws InterruptedException {
+        when(httpClient.isOnline()).thenReturn(true);
+        fixture.initialAccess(httpClient);
+    }
+
+    @Test
+    void initialAccessAccessPossible()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        when(httpClient.isOnline()).thenReturn(true);
+        when(httpClient.isAccessPossible()).thenReturn(true);
+        when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
+        when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
+
+        // mock a request and response to obtain rooms
+        Request roomsRequest = mock(Request.class);
+        ContentResponse roomsResponse = mock(ContentResponse.class);
+        when(roomsResponse.getStatus()).thenReturn(200);
+        when(roomsResponse.getContentAsString()).thenReturn(
+                "[{\"@type\":\"room\",\"id\":\"hz_1\",\"iconId\":\"icon_room_bedroom\",\"name\":\"Bedroom\"}]");
+        when(roomsRequest.send()).thenReturn(roomsResponse);
+        when(httpClient.createRequest(contains("/rooms"), same(HttpMethod.GET))).thenReturn(roomsRequest);
+
+        // mock a request and response to obtain devices
+        Request devicesRequest = mock(Request.class);
+        ContentResponse devicesResponse = mock(ContentResponse.class);
+        when(devicesResponse.getStatus()).thenReturn(200);
+        when(devicesResponse.getContentAsString()).thenReturn("[{\"@type\":\"device\",\r\n"
+                + " \"rootDeviceId\":\"64-da-a0-02-14-9b\",\r\n"
+                + " \"id\":\"hdm:HomeMaticIP:3014F711A00004953859F31B\",\r\n"
+                + " \"deviceServiceIds\":[\"PowerMeter\",\"PowerSwitch\",\"PowerSwitchProgram\",\"Routing\"],\r\n"
+                + " \"manufacturer\":\"BOSCH\",\r\n" + " \"roomId\":\"hz_3\",\r\n" + " \"deviceModel\":\"PSM\",\r\n"
+                + " \"serial\":\"3014F711A00004953859F31B\",\r\n" + " \"profile\":\"GENERIC\",\r\n"
+                + " \"name\":\"Coffee Machine\",\r\n" + " \"status\":\"AVAILABLE\",\r\n" + " \"childDeviceIds\":[]\r\n"
+                + " }]");
+        when(devicesRequest.send()).thenReturn(devicesResponse);
+        when(httpClient.createRequest(contains("/devices"), same(HttpMethod.GET))).thenReturn(devicesRequest);
+
+        SubscribeResult subscribeResult = new SubscribeResult();
+        when(httpClient.sendRequest(any(), same(SubscribeResult.class), any(), any())).thenReturn(subscribeResult);
+
+        Request longPollRequest = mock(Request.class);
+        when(httpClient.createRequest(anyString(), same(HttpMethod.POST),
+                argThat((JsonRpcRequest r) -> r.method.equals("RE/longPoll")))).thenReturn(longPollRequest);
+
+        fixture.initialAccess(httpClient);
+        verify(thingHandlerCallback).statusUpdated(any(),
+                eq(ThingStatusInfoBuilder.create(ThingStatus.ONLINE, ThingStatusDetail.NONE).build()));
+    }
+
+    @Test
+    void getState() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
+        when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
+        Request request = mock(Request.class);
+        when(request.header(anyString(), anyString())).thenReturn(request);
+        ContentResponse response = mock(ContentResponse.class);
+        when(response.getStatus()).thenReturn(200);
+        when(response.getContentAsString()).thenReturn("{\r\n" + "     \"@type\": \"systemState\",\r\n"
+                + "     \"systemAvailability\": {\r\n" + "         \"@type\": \"systemAvailabilityState\",\r\n"
+                + "         \"available\": true,\r\n" + "         \"deleted\": false\r\n" + "     },\r\n"
+                + "     \"armingState\": {\r\n" + "         \"@type\": \"armingState\",\r\n"
+                + "         \"state\": \"SYSTEM_DISARMED\",\r\n" + "         \"deleted\": false\r\n" + "     },\r\n"
+                + "     \"alarmState\": {\r\n" + "         \"@type\": \"alarmState\",\r\n"
+                + "         \"value\": \"ALARM_OFF\",\r\n" + "         \"incidents\": [],\r\n"
+                + "         \"deleted\": false\r\n" + "     },\r\n" + "     \"activeConfigurationProfile\": {\r\n"
+                + "         \"@type\": \"activeConfigurationProfile\",\r\n" + "         \"deleted\": false\r\n"
+                + "     },\r\n" + "     \"securityGapState\": {\r\n" + "         \"@type\": \"securityGapState\",\r\n"
+                + "         \"securityGaps\": [],\r\n" + "         \"deleted\": false\r\n" + "     },\r\n"
+                + "     \"deleted\": false\r\n" + " }");
+        when(request.send()).thenReturn(response);
+        when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
+
+        IntrusionDetectionSystemState state = fixture.getState("intrusion/states/system",
+                IntrusionDetectionSystemState.class);
+        assertNotNull(state);
+        assertTrue(state.systemAvailability.available);
+        assertSame(AlarmState.ALARM_OFF, state.alarmState.value);
+        assertSame(ArmingState.SYSTEM_DISARMED, state.armingState.state);
+    }
+
+    @Test
+    void getDeviceState() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
+        when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
+        when(httpClient.getServiceStateUrl(anyString(), anyString())).thenCallRealMethod();
+
+        Request request = mock(Request.class);
+        when(request.header(anyString(), anyString())).thenReturn(request);
+        ContentResponse response = mock(ContentResponse.class);
+        when(response.getStatus()).thenReturn(200);
+        when(response.getContentAsString())
+                .thenReturn("{\n" + "   \"@type\": \"shutterContactState\",\n" + "   \"value\": \"OPEN\"\n" + " }");
+        when(request.send()).thenReturn(response);
+        when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
+
+        ShutterContactServiceState state = fixture.getState("hdm:HomeMaticIP:3014D711A000009D545DEB39D",
+                "ShutterContact", ShutterContactServiceState.class);
+        assertNotNull(state);
+        assertSame(ShutterContactState.OPEN, state.value);
+    }
+
+    @Test
+    void getDeviceInfo() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
+        when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
+
+        Request request = mock(Request.class);
+        when(request.header(anyString(), anyString())).thenReturn(request);
+        ContentResponse response = mock(ContentResponse.class);
+        when(response.getStatus()).thenReturn(200);
+        when(request.send()).thenReturn(response);
+        when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
+        when(httpClient.sendRequest(same(request), same(Device.class), any(), any()))
+                .thenReturn(DeviceTest.createTestDevice());
+
+        String deviceId = "hdm:HomeMaticIP:3014F711A00004953859F31B";
+        Device device = fixture.getDeviceInfo(deviceId);
+        assertEquals(deviceId, device.id);
+    }
+
+    @Test
+    void getDeviceInfoErrorCases()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
+        when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
+
+        Request request = mock(Request.class);
+        when(request.header(anyString(), anyString())).thenReturn(request);
+        ContentResponse response = mock(ContentResponse.class);
+        when(response.getStatus()).thenReturn(200);
+        when(request.send()).thenReturn(response);
+        when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
+
+        @SuppressWarnings("unchecked")
+        ArgumentCaptor<BiFunction<Integer, String, BoschSHCException>> errorResponseHandlerCaptor = ArgumentCaptor
+                .forClass(BiFunction.class);
+
+        when(httpClient.sendRequest(same(request), same(Device.class), any(), errorResponseHandlerCaptor.capture()))
+                .thenReturn(DeviceTest.createTestDevice());
+
+        String deviceId = "hdm:HomeMaticIP:3014F711A00004953859F31B";
+        fixture.getDeviceInfo(deviceId);
+
+        BiFunction<Integer, String, BoschSHCException> errorResponseHandler = errorResponseHandlerCaptor.getValue();
+        Exception e = errorResponseHandler.apply(500,
+                "{\"@type\":\"JsonRestExceptionResponseEntity\",\"errorCode\": \"testErrorCode\",\"statusCode\": 500}");
+        assertEquals(
+                "Request for info of device hdm:HomeMaticIP:3014F711A00004953859F31B failed with status code 500 and error code testErrorCode",
+                e.getMessage());
+
+        e = errorResponseHandler.apply(404,
+                "{\"@type\":\"JsonRestExceptionResponseEntity\",\"errorCode\": \"ENTITY_NOT_FOUND\",\"statusCode\": 404}");
+        assertNotNull(e);
+
+        e = errorResponseHandler.apply(500, "");
+        assertEquals("Request for info of device hdm:HomeMaticIP:3014F711A00004953859F31B failed with status code 500",
+                e.getMessage());
+    }
+
+    @Test
+    void getServiceData() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
+        when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
+        when(httpClient.getServiceUrl(anyString(), anyString())).thenCallRealMethod();
+
+        Request request = mock(Request.class);
+        when(request.header(anyString(), anyString())).thenReturn(request);
+        ContentResponse response = mock(ContentResponse.class);
+        when(response.getStatus()).thenReturn(200);
+        when(response.getContentAsString()).thenReturn("{ \n" + "    \"@type\":\"DeviceServiceData\",\n"
+                + "    \"path\":\"/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel\",\n"
+                + "    \"id\":\"BatteryLevel\",\n" + "    \"deviceId\":\"hdm:ZigBee:000d6f0004b93361\",\n"
+                + "    \"faults\":{ \n" + "        \"entries\":[\n" + "          {\n"
+                + "            \"type\":\"LOW_BATTERY\",\n" + "            \"category\":\"WARNING\"\n" + "          }\n"
+                + "        ]\n" + "    }\n" + "}");
+        when(request.send()).thenReturn(response);
+        when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
+
+        DeviceServiceData serviceData = fixture.getServiceData("hdm:ZigBee:000d6f0004b93361", "BatteryLevel");
+        assertNotNull(serviceData);
+        Faults faults = serviceData.faults;
+        assertNotNull(faults);
+        assertEquals("LOW_BATTERY", faults.entries.get(0).type);
+    }
+
+    @Test
+    void getServiceDataError() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
+        when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
+        when(httpClient.getServiceUrl(anyString(), anyString())).thenCallRealMethod();
+
+        Request request = mock(Request.class);
+        when(request.header(anyString(), anyString())).thenReturn(request);
+        ContentResponse response = mock(ContentResponse.class);
+        when(response.getStatus()).thenReturn(500);
+        when(response.getContentAsString()).thenReturn(
+                "{\"@type\":\"JsonRestExceptionResponseEntity\",\"errorCode\": \"testErrorCode\",\"statusCode\": 500}");
+        when(request.send()).thenReturn(response);
+        when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
+        when(httpClient.sendRequest(same(request), same(Device.class), any(), any()))
+                .thenReturn(DeviceTest.createTestDevice());
+
+        BoschSHCException e = assertThrows(BoschSHCException.class,
+                () -> fixture.getServiceData("hdm:ZigBee:000d6f0004b93361", "BatteryLevel"));
+        assertEquals(
+                "State request with URL https://null:8444/smarthome/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel failed with status code 500 and error code testErrorCode",
+                e.getMessage());
+    }
+
+    @Test
+    void getServiceDataErrorNoRestExceptionResponse()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
+        when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
+        when(httpClient.getServiceUrl(anyString(), anyString())).thenCallRealMethod();
+
+        Request request = mock(Request.class);
+        when(request.header(anyString(), anyString())).thenReturn(request);
+        ContentResponse response = mock(ContentResponse.class);
+        when(response.getStatus()).thenReturn(500);
+        when(response.getContentAsString()).thenReturn("");
+        when(request.send()).thenReturn(response);
+        when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
+
+        BoschSHCException e = assertThrows(BoschSHCException.class,
+                () -> fixture.getServiceData("hdm:ZigBee:000d6f0004b93361", "BatteryLevel"));
+        assertEquals(
+                "State request with URL https://null:8444/smarthome/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel failed with status code 500",
+                e.getMessage());
+    }
+
+    @Test
+    void putState() throws InterruptedException, TimeoutException, ExecutionException {
+        when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
+        when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
+        when(httpClient.getServiceStateUrl(anyString(), anyString())).thenCallRealMethod();
+
+        Request request = mock(Request.class);
+        when(request.header(anyString(), anyString())).thenReturn(request);
+        ContentResponse response = mock(ContentResponse.class);
+
+        when(httpClient.createRequest(anyString(), same(HttpMethod.PUT), any(BinarySwitchServiceState.class)))
+                .thenReturn(request);
+        when(request.send()).thenReturn(response);
+
+        BinarySwitchServiceState binarySwitchState = new BinarySwitchServiceState();
+        binarySwitchState.on = true;
+        fixture.putState("hdm:ZigBee:f0d1b80000f2a3e9", "BinarySwitch", binarySwitchState);
+    }
+
+    @AfterEach
+    void afterEach() throws Exception {
+        fixture.dispose();
+    }
 }
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/JsonRpcRequestTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/JsonRpcRequestTest.java
new file mode 100644 (file)
index 0000000..7d3b43b
--- /dev/null
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.devices.bridge;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link JsonRpcRequest}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class JsonRpcRequestTest {
+
+    private @NonNullByDefault({}) JsonRpcRequest fixture;
+
+    @BeforeEach
+    protected void setUp() throws Exception {
+        fixture = new JsonRpcRequest("2.0", "RE/longPoll", new String[] { "subscriptionId", "20" });
+    }
+
+    @Test
+    public void testConstructor() {
+        assertEquals("2.0", fixture.getJsonrpc());
+        assertEquals("RE/longPoll", fixture.getMethod());
+        assertArrayEquals(new String[] { "subscriptionId", "20" }, fixture.getParams());
+    }
+
+    @Test
+    public void testNoArgConstructor() {
+        fixture = new JsonRpcRequest();
+        assertEquals("", fixture.getJsonrpc());
+        assertEquals("", fixture.getMethod());
+        assertArrayEquals(new String[0], fixture.getParams());
+    }
+
+    @Test
+    public void testSetJsonrpc() {
+        fixture.setJsonrpc("test");
+        assertEquals("test", fixture.getJsonrpc());
+    }
+
+    @Test
+    public void testSetMethod() {
+        fixture.setMethod("RE/subscribe");
+        assertEquals("RE/subscribe", fixture.getMethod());
+    }
+
+    @Test
+    public void testSetParams() {
+        fixture.setParams(new String[] { "com/bosch/sh/remote/*", null });
+        assertArrayEquals(new String[] { "com/bosch/sh/remote/*", null }, fixture.getParams());
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPollingTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPollingTest.java
new file mode 100644 (file)
index 0000000..524d23f
--- /dev/null
@@ -0,0 +1,327 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.devices.bridge;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Response.CompleteListener;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.http.HttpMethod;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException;
+
+import com.google.gson.JsonObject;
+
+/**
+ * Unit tests for {@link LongPolling}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+@ExtendWith(MockitoExtension.class)
+public class LongPollingTest {
+
+    /**
+     * A dummy implementation of {@link ScheduledFuture}.
+     * <p>
+     * This is required because we can not return <code>null</code> in the executor service test implementation (see
+     * below).
+     *
+     * @author David Pace - Initial contribution
+     *
+     * @param <T> The result type returned by this Future
+     */
+    private static class NullScheduledFuture<T> implements ScheduledFuture<T> {
+
+        @Override
+        public long getDelay(@Nullable TimeUnit unit) {
+            return 0;
+        }
+
+        @Override
+        public int compareTo(@Nullable Delayed o) {
+            return 0;
+        }
+
+        @Override
+        public boolean cancel(boolean mayInterruptIfRunning) {
+            return false;
+        }
+
+        @Override
+        public boolean isCancelled() {
+            return false;
+        }
+
+        @Override
+        public boolean isDone() {
+            return false;
+        }
+
+        @Override
+        public T get() throws InterruptedException, ExecutionException {
+            return null;
+        }
+
+        @Override
+        public T get(long timeout, @Nullable TimeUnit unit)
+                throws InterruptedException, ExecutionException, TimeoutException {
+            return null;
+        }
+    }
+
+    /**
+     * Executor service implementation that runs all runnables in the same thread in order to enable deterministic
+     * testing.
+     *
+     * @author David Pace - Initial contribution
+     *
+     */
+    private static class SameThreadExecutorService extends AbstractExecutorService implements ScheduledExecutorService {
+
+        private volatile boolean terminated;
+
+        @Override
+        public void shutdown() {
+            terminated = true;
+        }
+
+        @NonNullByDefault({})
+        @Override
+        public List<Runnable> shutdownNow() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public boolean isShutdown() {
+            return terminated;
+        }
+
+        @Override
+        public boolean isTerminated() {
+            return terminated;
+        }
+
+        @Override
+        public boolean awaitTermination(long timeout, @Nullable TimeUnit unit) throws InterruptedException {
+            shutdown();
+            return terminated;
+        }
+
+        @Override
+        public void execute(@Nullable Runnable command) {
+            if (command != null) {
+                // execute in the same thread in unit tests
+                command.run();
+            }
+        }
+
+        @Override
+        public ScheduledFuture<?> schedule(@Nullable Runnable command, long delay, @Nullable TimeUnit unit) {
+            // not used in this tests
+            return new NullScheduledFuture<Object>();
+        }
+
+        @Override
+        public <V> ScheduledFuture<V> schedule(@Nullable Callable<V> callable, long delay, @Nullable TimeUnit unit) {
+            return new NullScheduledFuture<V>();
+        }
+
+        @Override
+        public ScheduledFuture<?> scheduleAtFixedRate(@Nullable Runnable command, long initialDelay, long period,
+                @Nullable TimeUnit unit) {
+            if (command != null) {
+                command.run();
+            }
+            return new NullScheduledFuture<Object>();
+        }
+
+        @Override
+        public ScheduledFuture<?> scheduleWithFixedDelay(@Nullable Runnable command, long initialDelay, long delay,
+                @Nullable TimeUnit unit) {
+            if (command != null) {
+                command.run();
+            }
+            return new NullScheduledFuture<Object>();
+        }
+    }
+
+    private @NonNullByDefault({}) LongPolling fixture;
+
+    private @NonNullByDefault({}) BoschHttpClient httpClient;
+
+    private @Mock @NonNullByDefault({}) Consumer<@NonNull LongPollResult> longPollHandler;
+
+    private @Mock @NonNullByDefault({}) Consumer<@NonNull Throwable> failureHandler;
+
+    @BeforeEach
+    void beforeEach() {
+        fixture = new LongPolling(new SameThreadExecutorService(), longPollHandler, failureHandler);
+        httpClient = mock(BoschHttpClient.class);
+    }
+
+    @Test
+    void start() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        // when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
+        when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
+
+        Request subscribeRequest = mock(Request.class);
+        when(httpClient.createRequest(anyString(), same(HttpMethod.POST),
+                argThat((JsonRpcRequest r) -> r.method.equals("RE/subscribe")))).thenReturn(subscribeRequest);
+        SubscribeResult subscribeResult = new SubscribeResult();
+        when(httpClient.sendRequest(any(), same(SubscribeResult.class), any(), any())).thenReturn(subscribeResult);
+
+        Request longPollRequest = mock(Request.class);
+        when(httpClient.createRequest(anyString(), same(HttpMethod.POST),
+                argThat((JsonRpcRequest r) -> r.method.equals("RE/longPoll")))).thenReturn(longPollRequest);
+
+        fixture.start(httpClient);
+
+        ArgumentCaptor<CompleteListener> completeListener = ArgumentCaptor.forClass(CompleteListener.class);
+        verify(longPollRequest).send(completeListener.capture());
+
+        BufferingResponseListener bufferingResponseListener = (BufferingResponseListener) completeListener.getValue();
+
+        String longPollResultJSON = "{\"result\":[{\"path\":\"/devices/hdm:HomeMaticIP:3014F711A0001916D859A8A9/services/PowerSwitch\",\"@type\":\"DeviceServiceData\",\"id\":\"PowerSwitch\",\"state\":{\"@type\":\"powerSwitchState\",\"switchState\":\"ON\"},\"deviceId\":\"hdm:HomeMaticIP:3014F711A0001916D859A8A9\"}],\"jsonrpc\":\"2.0\"}\n";
+        Response response = mock(Response.class);
+        bufferingResponseListener.onContent(response,
+                ByteBuffer.wrap(longPollResultJSON.getBytes(StandardCharsets.UTF_8)));
+
+        Result result = mock(Result.class);
+        bufferingResponseListener.onComplete(result);
+
+        ArgumentCaptor<LongPollResult> longPollResultCaptor = ArgumentCaptor.forClass(LongPollResult.class);
+        verify(longPollHandler).accept(longPollResultCaptor.capture());
+        LongPollResult longPollResult = longPollResultCaptor.getValue();
+        assertEquals(1, longPollResult.result.size());
+        DeviceServiceData longPollResultItem = longPollResult.result.get(0);
+        assertEquals("hdm:HomeMaticIP:3014F711A0001916D859A8A9", longPollResultItem.deviceId);
+        assertEquals("/devices/hdm:HomeMaticIP:3014F711A0001916D859A8A9/services/PowerSwitch", longPollResultItem.path);
+        assertEquals("PowerSwitch", longPollResultItem.id);
+        JsonObject stateObject = (JsonObject) longPollResultItem.state;
+        assertNotNull(stateObject);
+        assertEquals("ON", stateObject.get("switchState").getAsString());
+    }
+
+    @Test
+    void startSubscriptionFailure()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        when(httpClient.sendRequest(any(), same(SubscribeResult.class), any(), any()))
+                .thenThrow(new ExecutionException("Subscription failed.", null));
+
+        LongPollingFailedException e = assertThrows(LongPollingFailedException.class, () -> fixture.start(httpClient));
+        assertTrue(e.getMessage().contains("Subscription failed."));
+    }
+
+    @Test
+    void startLongPollFailure() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
+
+        Request request = mock(Request.class);
+        when(httpClient.createRequest(anyString(), same(HttpMethod.POST), any(JsonRpcRequest.class)))
+                .thenReturn(request);
+        SubscribeResult subscribeResult = new SubscribeResult();
+        when(httpClient.sendRequest(any(), same(SubscribeResult.class), any(), any())).thenReturn(subscribeResult);
+
+        Request longPollRequest = mock(Request.class);
+        when(httpClient.createRequest(anyString(), same(HttpMethod.POST),
+                argThat((JsonRpcRequest r) -> r.method.equals("RE/longPoll")))).thenReturn(longPollRequest);
+
+        fixture.start(httpClient);
+
+        ArgumentCaptor<CompleteListener> completeListener = ArgumentCaptor.forClass(CompleteListener.class);
+        verify(longPollRequest).send(completeListener.capture());
+
+        BufferingResponseListener bufferingResponseListener = (BufferingResponseListener) completeListener.getValue();
+
+        Result result = mock(Result.class);
+        ExecutionException exception = new ExecutionException("test exception", null);
+        when(result.getFailure()).thenReturn(exception);
+        bufferingResponseListener.onComplete(result);
+
+        ArgumentCaptor<Throwable> throwableCaptor = ArgumentCaptor.forClass(Throwable.class);
+        verify(failureHandler).accept(throwableCaptor.capture());
+        Throwable t = throwableCaptor.getValue();
+        assertEquals("Unexpected exception during long polling request", t.getMessage());
+        assertSame(exception, t.getCause());
+    }
+
+    @Test
+    void startSubscriptionInvalid()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
+
+        Request subscribeRequest = mock(Request.class);
+        when(httpClient.createRequest(anyString(), same(HttpMethod.POST),
+                argThat((JsonRpcRequest r) -> r.method.equals("RE/subscribe")))).thenReturn(subscribeRequest);
+        SubscribeResult subscribeResult = new SubscribeResult();
+        when(httpClient.sendRequest(any(), same(SubscribeResult.class), any(), any())).thenReturn(subscribeResult);
+
+        Request longPollRequest = mock(Request.class);
+        when(httpClient.createRequest(anyString(), same(HttpMethod.POST),
+                argThat((JsonRpcRequest r) -> r.method.equals("RE/longPoll")))).thenReturn(longPollRequest);
+
+        fixture.start(httpClient);
+
+        ArgumentCaptor<CompleteListener> completeListener = ArgumentCaptor.forClass(CompleteListener.class);
+        verify(longPollRequest).send(completeListener.capture());
+
+        BufferingResponseListener bufferingResponseListener = (BufferingResponseListener) completeListener.getValue();
+
+        String longPollResultJSON = "{\"jsonrpc\":\"2.0\",\"error\": {\"code\":-32001,\"message\":\"No subscription with id: e8fei62b0-0\"}}\n";
+        Response response = mock(Response.class);
+        bufferingResponseListener.onContent(response,
+                ByteBuffer.wrap(longPollResultJSON.getBytes(StandardCharsets.UTF_8)));
+
+        Result result = mock(Result.class);
+        bufferingResponseListener.onComplete(result);
+    }
+
+    @AfterEach
+    void afterEach() {
+        fixture.stop();
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceServiceDataTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceServiceDataTest.java
new file mode 100644 (file)
index 0000000..cfe6536
--- /dev/null
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.devices.bridge.dto;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link DeviceServiceData}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+public class DeviceServiceDataTest {
+
+    private DeviceServiceData fixture;
+
+    @BeforeEach
+    void beforeEach() {
+        fixture = new DeviceServiceData();
+        fixture.deviceId = "64-da-a0-02-14-9b";
+    }
+
+    @Test
+    public void testToString() {
+        assertEquals("64-da-a0-02-14-9b state: DeviceServiceData", fixture.toString());
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceTest.java
new file mode 100644 (file)
index 0000000..9ee3ac9
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.devices.bridge.dto;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link Device}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class DeviceTest {
+
+    public static Device createTestDevice() {
+        Device device = new Device();
+        device.type = "device";
+        device.rootDeviceId = "64-da-a0-02-14-9b";
+        device.id = "hdm:HomeMaticIP:3014F711A00004953859F31B";
+        device.deviceServiceIds = Collections
+                .unmodifiableList(List.of("PowerMeter", "PowerSwitch", "PowerSwitchProgram", "Routing"));
+        device.manufacturer = "BOSCH";
+        device.roomId = "hz_3";
+        device.deviceModel = "PSM";
+        device.serial = "3014F711A00004953859F31B";
+        device.profile = "GENERIC";
+        device.name = "Coffee Machine";
+        device.status = "AVAILABLE";
+        return device;
+    }
+
+    private @NonNullByDefault({}) Device fixture;
+
+    @BeforeEach
+    void beforeEach() {
+        fixture = createTestDevice();
+    }
+
+    @Test
+    void testIsValid() {
+        assertTrue(Device.isValid(fixture));
+    }
+
+    @Test
+    public void testToString() {
+        assertEquals(
+                "Type device; RootDeviceId: 64-da-a0-02-14-9b; Id: hdm:HomeMaticIP:3014F711A00004953859F31B; Device Service Ids: PowerMeter, PowerSwitch, PowerSwitchProgram, Routing; Manufacturer: BOSCH; Room Id: hz_3; Device Model: PSM; Serial: 3014F711A00004953859F31B; Profile: GENERIC; Name: Coffee Machine; Status: AVAILABLE; Child Device Ids: null ",
+                fixture.toString());
+    }
+}
index 0cb373d3655f32459f0e70ea7bc70381479ea476..fb8a3e3c248172d5eb42fd788116c6bf37863113 100644 (file)
@@ -12,8 +12,7 @@
  */
 package org.openhab.binding.boschshc.internal.devices.bridge.dto;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.*;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.Test;
@@ -30,13 +29,11 @@ public class LongPollResultTest {
     private final Gson gson = new Gson();
 
     @Test
-    public void noResultsForErrorResult() {
+    void noResultsForErrorResult() {
         LongPollResult longPollResult = gson.fromJson(
                 "{\"jsonrpc\":\"2.0\", \"error\": { \"code\":-32001, \"message\":\"No subscription with id: e8fei62b0-0\" } }",
                 LongPollResult.class);
-        assertNotEquals(null, longPollResult);
-        if (longPollResult != null) {
-            assertEquals(null, longPollResult.result);
-        }
+        assertNotNull(longPollResult);
+        assertEquals(null, longPollResult.result);
     }
 }
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/camera/CameraHandlerTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/camera/CameraHandlerTest.java
new file mode 100644 (file)
index 0000000..b2b7919
--- /dev/null
@@ -0,0 +1,134 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.devices.camera;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.openhab.binding.boschshc.internal.devices.AbstractBoschSHCDeviceHandlerTest;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.binding.boschshc.internal.services.cameranotification.CameraNotificationState;
+import org.openhab.binding.boschshc.internal.services.cameranotification.dto.CameraNotificationServiceState;
+import org.openhab.binding.boschshc.internal.services.privacymode.PrivacyModeState;
+import org.openhab.binding.boschshc.internal.services.privacymode.dto.PrivacyModeServiceState;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingTypeUID;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+
+/**
+ * Unit tests for {@link CameraHandler}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class CameraHandlerTest extends AbstractBoschSHCDeviceHandlerTest<CameraHandler> {
+
+    private @Captor @NonNullByDefault({}) ArgumentCaptor<PrivacyModeServiceState> privacyModeServiceStateCaptor;
+
+    private @Captor @NonNullByDefault({}) ArgumentCaptor<CameraNotificationServiceState> cameraNotificationServiceStateCaptor;
+
+    @Override
+    protected CameraHandler createFixture() {
+        return new CameraHandler(getThing());
+    }
+
+    @Override
+    protected ThingTypeUID getThingTypeUID() {
+        return BoschSHCBindingConstants.THING_TYPE_CAMERA_360;
+    }
+
+    @Override
+    protected String getDeviceID() {
+        return "8e28ce2d-e7bf-3e3d-8e3a-a78de61b493e";
+    }
+
+    @Test
+    public void testHandleCommandPrivacyMode()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PRIVACY_MODE),
+                OnOffType.ON);
+        verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("PrivacyMode"),
+                privacyModeServiceStateCaptor.capture());
+        PrivacyModeServiceState state = privacyModeServiceStateCaptor.getValue();
+        assertSame(PrivacyModeState.ENABLED, state.value);
+
+        getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PRIVACY_MODE),
+                OnOffType.OFF);
+        verify(getBridgeHandler(), times(2)).putState(eq(getDeviceID()), eq("PrivacyMode"),
+                privacyModeServiceStateCaptor.capture());
+        state = privacyModeServiceStateCaptor.getValue();
+        assertSame(PrivacyModeState.DISABLED, state.value);
+    }
+
+    @Test
+    public void testHandleCommandCameraNotification()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        getFixture().handleCommand(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_CAMERA_NOTIFICATION),
+                OnOffType.ON);
+        verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("CameraNotification"),
+                cameraNotificationServiceStateCaptor.capture());
+        CameraNotificationServiceState state = cameraNotificationServiceStateCaptor.getValue();
+        assertSame(CameraNotificationState.ENABLED, state.value);
+
+        getFixture().handleCommand(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_CAMERA_NOTIFICATION),
+                OnOffType.OFF);
+        verify(getBridgeHandler(), times(2)).putState(eq(getDeviceID()), eq("CameraNotification"),
+                cameraNotificationServiceStateCaptor.capture());
+        state = cameraNotificationServiceStateCaptor.getValue();
+        assertSame(CameraNotificationState.DISABLED, state.value);
+    }
+
+    @Test
+    public void testUpdateChannelsPrivacyModeState() {
+        JsonElement jsonObject = JsonParser.parseString("{\"@type\":\"privacyModeState\",\"value\":\"ENABLED\"}");
+        getFixture().processUpdate("PrivacyMode", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PRIVACY_MODE), OnOffType.ON);
+
+        jsonObject = JsonParser.parseString("{\"@type\":\"privacyModeState\",\"value\":\"DISABLED\"}");
+        getFixture().processUpdate("PrivacyMode", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PRIVACY_MODE), OnOffType.OFF);
+    }
+
+    @Test
+    public void testUpdateChannelsCameraNotificationState() {
+        JsonElement jsonObject = JsonParser
+                .parseString("{\"@type\":\"cameraNotificationState\",\"value\":\"ENABLED\"}");
+        getFixture().processUpdate("CameraNotification", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_CAMERA_NOTIFICATION),
+                OnOffType.ON);
+
+        jsonObject = JsonParser.parseString("{\"@type\":\"cameraNotificationState\",\"value\":\"DISABLED\"}");
+        getFixture().processUpdate("CameraNotification", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_CAMERA_NOTIFICATION),
+                OnOffType.OFF);
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/climatecontrol/ClimateControlHandlerTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/climatecontrol/ClimateControlHandlerTest.java
new file mode 100644 (file)
index 0000000..86dc312
--- /dev/null
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.devices.climatecontrol;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import javax.measure.quantity.Temperature;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.openhab.binding.boschshc.internal.devices.AbstractBoschSHCDeviceHandlerTest;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.binding.boschshc.internal.services.roomclimatecontrol.dto.RoomClimateControlServiceState;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingTypeUID;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+
+/**
+ * Unit tests for {@link ClimateControlHandler}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ClimateControlHandlerTest extends AbstractBoschSHCDeviceHandlerTest<ClimateControlHandler> {
+
+    private @Captor @NonNullByDefault({}) ArgumentCaptor<RoomClimateControlServiceState> roomClimateControlServiceStateCaptor;
+
+    @Override
+    protected String getDeviceID() {
+        return "hdm:ZigBee:abcd6fc012ad25b1";
+    }
+
+    @Override
+    protected ClimateControlHandler createFixture() {
+        return new ClimateControlHandler(getThing());
+    }
+
+    @Override
+    protected ThingTypeUID getThingTypeUID() {
+        return BoschSHCBindingConstants.THING_TYPE_CLIMATE_CONTROL;
+    }
+
+    @Test
+    public void testHandleCommandRoomClimateControlService()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        QuantityType<Temperature> temperature = new QuantityType<>(21.5, SIUnits.CELSIUS);
+        getFixture().handleCommand(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SETPOINT_TEMPERATURE),
+                temperature);
+        verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("RoomClimateControl"),
+                roomClimateControlServiceStateCaptor.capture());
+        RoomClimateControlServiceState state = roomClimateControlServiceStateCaptor.getValue();
+        assertEquals(temperature, state.getSetpointTemperatureState());
+    }
+
+    @Test
+    public void testUpdateChannelsTemperatureLevelService() {
+        JsonElement jsonObject = JsonParser.parseString(
+                "{\n" + "   \"@type\": \"temperatureLevelState\",\n" + "   \"temperature\": 21.5\n" + " }");
+        getFixture().processUpdate("TemperatureLevel", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_TEMPERATURE),
+                new QuantityType<Temperature>(21.5, SIUnits.CELSIUS));
+    }
+
+    @Test
+    public void testUpdateChannelsRoomClimateControlService() {
+        JsonElement jsonObject = JsonParser.parseString(
+                "{\n" + "   \"@type\": \"climateControlState\",\n" + "   \"setpointTemperature\": 21.5\n" + " }");
+        getFixture().processUpdate("RoomClimateControl", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SETPOINT_TEMPERATURE),
+                new QuantityType<Temperature>(21.5, SIUnits.CELSIUS));
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/intrusion/IntrusionDetectionHandlerTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/intrusion/IntrusionDetectionHandlerTest.java
new file mode 100644 (file)
index 0000000..caf80c4
--- /dev/null
@@ -0,0 +1,139 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.devices.intrusion;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.openhab.binding.boschshc.internal.devices.AbstractBoschSHCHandlerTest;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.binding.boschshc.internal.services.intrusion.actions.arm.dto.ArmActionRequest;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingTypeUID;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+
+/**
+ * Unit test for {@link IntrusionDetectionHandler}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class IntrusionDetectionHandlerTest extends AbstractBoschSHCHandlerTest<IntrusionDetectionHandler> {
+
+    private @Captor @NonNullByDefault({}) ArgumentCaptor<ArmActionRequest> armActionRequestCaptor;
+
+    @Override
+    protected IntrusionDetectionHandler createFixture() {
+        return new IntrusionDetectionHandler(getThing());
+    }
+
+    @Override
+    protected ThingTypeUID getThingTypeUID() {
+        return BoschSHCBindingConstants.THING_TYPE_INTRUSION_DETECTION_SYSTEM;
+    }
+
+    @Test
+    public void testHandleCommandArmAction() throws InterruptedException, TimeoutException, ExecutionException {
+        getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ARM_ACTION),
+                new StringType("0"));
+        verify(getBridgeHandler()).postAction(eq("intrusion/actions/arm"), armActionRequestCaptor.capture());
+        ArmActionRequest armRequest = armActionRequestCaptor.getValue();
+        assertEquals("0", armRequest.profileId);
+    }
+
+    @Test
+    public void testHandleCommandDisarmAction() throws InterruptedException, TimeoutException, ExecutionException {
+        getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_DISARM_ACTION),
+                OnOffType.ON);
+        verify(getBridgeHandler()).postAction("intrusion/actions/disarm");
+    }
+
+    @Test
+    public void testHandleCommandMuteAction() throws InterruptedException, TimeoutException, ExecutionException {
+        getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_MUTE_ACTION),
+                OnOffType.ON);
+        verify(getBridgeHandler()).postAction("intrusion/actions/mute");
+    }
+
+    @Test
+    public void testUpdateChannelsIntrusionDetectionSystemState() {
+        JsonElement jsonObject = JsonParser.parseString("{\n" + "     \"@type\": \"systemState\",\n"
+                + "     \"systemAvailability\": {\n" + "         \"@type\": \"systemAvailabilityState\",\n"
+                + "         \"available\": true,\n" + "         \"deleted\": false\n" + "     },\n"
+                + "     \"armingState\": {\n" + "         \"@type\": \"armingState\",\n"
+                + "         \"state\": \"SYSTEM_DISARMED\",\n" + "         \"deleted\": false\n" + "     },\n"
+                + "     \"alarmState\": {\n" + "         \"@type\": \"alarmState\",\n"
+                + "         \"value\": \"ALARM_OFF\",\n" + "         \"incidents\": [],\n"
+                + "         \"deleted\": false\n" + "     },\n" + "     \"activeConfigurationProfile\": {\n"
+                + "         \"@type\": \"activeConfigurationProfile\",\n" + "         \"deleted\": false\n"
+                + "     },\n" + "     \"securityGapState\": {\n" + "         \"@type\": \"securityGapState\",\n"
+                + "         \"securityGaps\": [],\n" + "         \"deleted\": false\n" + "     },\n"
+                + "     \"deleted\": false\n" + " }\n");
+        getFixture().processUpdate(BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION, jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SYSTEM_AVAILABILITY),
+                OnOffType.ON);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ARMING_STATE),
+                new StringType("SYSTEM_DISARMED"));
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ALARM_STATE),
+                new StringType("ALARM_OFF"));
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ACTIVE_CONFIGURATION_PROFILE),
+                new StringType(null));
+    }
+
+    @Test
+    public void testUpdateChannelsIntrusionDetectionControlState() {
+        JsonElement jsonObject = JsonParser.parseString("{\n" + "   \"@type\": \"intrusionDetectionControlState\",\n"
+                + "   \"activeProfile\": \"0\",\n" + "   \"alarmActivationDelayTime\": 30,\n" + "   \"actuators\": [\n"
+                + "     {\n" + "       \"readonly\": false,\n" + "       \"active\": true,\n"
+                + "       \"id\": \"intrusion:video\"\n" + "     },\n" + "     {\n" + "       \"readonly\": false,\n"
+                + "       \"active\": false,\n" + "       \"id\": \"intrusion:siren\"\n" + "     }\n" + "   ],\n"
+                + "   \"remainingTimeUntilArmed\": 29559,\n" + "   \"armActivationDelayTime\": 30,\n"
+                + "   \"triggers\": [\n" + "     {\n" + "       \"readonly\": false,\n" + "       \"active\": true,\n"
+                + "       \"id\": \"hdm:ZigBee:000d6f0012f02378\"\n" + "     }\n" + "   ],\n"
+                + "   \"value\": \"SYSTEM_ARMING\"\n" + " }");
+        getFixture().processUpdate("IntrusionDetectionControl", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ARMING_STATE),
+                new StringType("SYSTEM_ARMING"));
+    }
+
+    @Test
+    public void testUpdateChannelsSurveillanceAlarmState() {
+        JsonElement jsonObject = JsonParser.parseString("{\n" + "   \"@type\": \"surveillanceAlarmState\",\n"
+                + "   \"incidents\": [\n" + "     {\n" + "       \"triggerName\": \"Motion Detector\",\n"
+                + "       \"locationId\": \"hz_5\",\n" + "       \"location\": \"Living Room\",\n"
+                + "       \"id\": \"hdm:ZigBee:000d6f0012f02342\",\n" + "       \"time\": 1652615755336,\n"
+                + "       \"type\": \"INTRUSION\"\n" + "     }\n" + "   ],\n" + "   \"value\": \"ALARM_ON\"\n" + " }");
+        getFixture().processUpdate("SurveillanceAlarm", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ALARM_STATE),
+                new StringType("ALARM_ON"));
+    }
+}
index f36f78805271aa33efd3eb2002830953754ee69a..418218426bf7fa4c2ebe5dc798600d231776c682 100644 (file)
  */
 package org.openhab.binding.boschshc.internal.devices.motiondetector;
 
+import static org.mockito.Mockito.verify;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
 import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandlerTest;
 import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.ThingTypeUID;
 
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+
 /**
  * Unit Tests for {@link MotionDetectorHandler}.
  *
@@ -40,4 +48,14 @@ public class MotionDetectorHandlerTest extends AbstractBatteryPoweredDeviceHandl
     protected ThingTypeUID getThingTypeUID() {
         return BoschSHCBindingConstants.THING_TYPE_MOTION_DETECTOR;
     }
+
+    @Test
+    public void testUpdateChannelsLatestMotionService() {
+        JsonElement jsonObject = JsonParser.parseString("{\n" + "   \"@type\": \"latestMotionState\",\n"
+                + "   \"latestMotionDetected\": \"2020-04-03T19:02:19.054Z\"\n" + " }");
+        getFixture().processUpdate("LatestMotion", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_LATEST_MOTION),
+                new DateTimeType("2020-04-03T19:02:19.054Z"));
+    }
 }
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandlerTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandlerTest.java
new file mode 100644 (file)
index 0000000..e3a9a5a
--- /dev/null
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.devices.shuttercontrol;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.openhab.binding.boschshc.internal.devices.AbstractBoschSHCDeviceHandlerTest;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.binding.boschshc.internal.services.shuttercontrol.OperationState;
+import org.openhab.binding.boschshc.internal.services.shuttercontrol.dto.ShutterControlServiceState;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.library.types.StopMoveType;
+import org.openhab.core.library.types.UpDownType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingTypeUID;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+
+/**
+ * Unit tests for {@link ShutterControlHandler}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ShutterControlHandlerTest extends AbstractBoschSHCDeviceHandlerTest<ShutterControlHandler> {
+
+    private @Captor @NonNullByDefault({}) ArgumentCaptor<ShutterControlServiceState> shutterControlServiceStateCaptor;
+
+    @Override
+    protected String getDeviceID() {
+        return "hdm:ZigBee:abcd6fc012ad25b1";
+    }
+
+    @Override
+    protected ShutterControlHandler createFixture() {
+        return new ShutterControlHandler(getThing());
+    }
+
+    @Override
+    protected ThingTypeUID getThingTypeUID() {
+        return BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL;
+    }
+
+    @Test
+    public void testHandleCommandUpDownType()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_LEVEL),
+                UpDownType.UP);
+        verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("ShutterControl"),
+                shutterControlServiceStateCaptor.capture());
+        ShutterControlServiceState state = shutterControlServiceStateCaptor.getValue();
+        assertEquals(1d, state.level);
+
+        getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_LEVEL),
+                UpDownType.DOWN);
+        verify(getBridgeHandler(), times(2)).putState(eq(getDeviceID()), eq("ShutterControl"),
+                shutterControlServiceStateCaptor.capture());
+        state = shutterControlServiceStateCaptor.getValue();
+        assertEquals(0d, state.level);
+    }
+
+    @Test
+    public void testHandleCommandStopMoveType()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_LEVEL),
+                StopMoveType.STOP);
+        verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("ShutterControl"),
+                shutterControlServiceStateCaptor.capture());
+        ShutterControlServiceState state = shutterControlServiceStateCaptor.getValue();
+        assertEquals(OperationState.STOPPED, state.operationState);
+    }
+
+    @Test
+    public void testHandleCommandPercentType()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_LEVEL),
+                new PercentType(42));
+        verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("ShutterControl"),
+                shutterControlServiceStateCaptor.capture());
+        ShutterControlServiceState state = shutterControlServiceStateCaptor.getValue();
+        assertEquals(0.58d, state.level);
+    }
+
+    @Test
+    public void testUpdateChannelsShutterControlService() {
+        JsonElement jsonObject = JsonParser
+                .parseString("{\n" + "   \"@type\": \"shutterControlState\",\n" + "   \"level\": 0.58\n" + " }");
+        getFixture().processUpdate("ShutterControl", jsonObject);
+        verify(getCallback()).stateUpdated(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_LEVEL),
+                new PercentType(42));
+    }
+}
index 8662d5da058c72679dd0a8cf1c47b4aed400becd..9f3fd50580001c9a4ee7c8f44f4f965e51325b39 100644 (file)
@@ -19,6 +19,7 @@ import static org.mockito.Mockito.*;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
@@ -44,16 +45,14 @@ import com.google.gson.JsonParser;
  * @author David Pace - Initial contribution
  *
  */
+@NonNullByDefault
 public class SmartBulbHandlerTest extends AbstractBoschSHCDeviceHandlerTest<SmartBulbHandler> {
 
-    @Captor
-    private ArgumentCaptor<BinarySwitchServiceState> binarySwitchServiceStateCaptor;
+    private @Captor @NonNullByDefault({}) ArgumentCaptor<BinarySwitchServiceState> binarySwitchServiceStateCaptor;
 
-    @Captor
-    private ArgumentCaptor<MultiLevelSwitchServiceState> multiLevelSwitchServiceStateCaptor;
+    private @Captor @NonNullByDefault({}) ArgumentCaptor<MultiLevelSwitchServiceState> multiLevelSwitchServiceStateCaptor;
 
-    @Captor
-    private ArgumentCaptor<HSBColorActuatorServiceState> hsbColorActuatorServiceStateCaptor;
+    private @Captor @NonNullByDefault({}) ArgumentCaptor<HSBColorActuatorServiceState> hsbColorActuatorServiceStateCaptor;
 
     @Override
     protected SmartBulbHandler createFixture() {
@@ -71,9 +70,8 @@ public class SmartBulbHandlerTest extends AbstractBoschSHCDeviceHandlerTest<Smar
     }
 
     @Test
-    public void testHandleCommand_BinarySwitch()
+    public void testHandleCommandBinarySwitch()
             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
-
         getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_POWER_SWITCH),
                 OnOffType.ON);
         verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("BinarySwitch"),
@@ -90,9 +88,8 @@ public class SmartBulbHandlerTest extends AbstractBoschSHCDeviceHandlerTest<Smar
     }
 
     @Test
-    public void testHandleCommand_MultiLevelSwitch()
+    public void testHandleCommandMultiLevelSwitch()
             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
-
         getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_BRIGHTNESS),
                 new PercentType(42));
         verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("MultiLevelSwitch"),
@@ -102,9 +99,8 @@ public class SmartBulbHandlerTest extends AbstractBoschSHCDeviceHandlerTest<Smar
     }
 
     @Test
-    public void testHandleCommand_HSBColorActuator()
+    public void testHandleCommandHSBColorActuator()
             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
-
         getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_COLOR),
                 HSBType.BLUE);
         verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("HSBColorActuator"),
@@ -114,7 +110,7 @@ public class SmartBulbHandlerTest extends AbstractBoschSHCDeviceHandlerTest<Smar
     }
 
     @Test
-    public void testUpdateChannel_BinarySwitchState() {
+    public void testUpdateChannelBinarySwitchState() {
         JsonElement jsonObject = JsonParser.parseString("{\"@type\":\"binarySwitchState\",\"on\":true}");
         getFixture().processUpdate("BinarySwitch", jsonObject);
         verify(getCallback()).stateUpdated(
@@ -127,7 +123,7 @@ public class SmartBulbHandlerTest extends AbstractBoschSHCDeviceHandlerTest<Smar
     }
 
     @Test
-    public void testUpdateChannel_MultiLevelSwitchState() {
+    public void testUpdateChannelMultiLevelSwitchState() {
         JsonElement jsonObject = JsonParser.parseString("{\"@type\":\"multiLevelSwitchState\",\"level\":16}");
         getFixture().processUpdate("MultiLevelSwitch", jsonObject);
         verify(getCallback()).stateUpdated(
@@ -135,7 +131,7 @@ public class SmartBulbHandlerTest extends AbstractBoschSHCDeviceHandlerTest<Smar
     }
 
     @Test
-    public void testUpdateChannel_HSBColorActuatorState() {
+    public void testUpdateChannelHSBColorActuatorState() {
         JsonElement jsonObject = JsonParser.parseString("{\"colorTemperatureRange\": {\n" + "        \"minCt\": 153,\n"
                 + "        \"maxCt\": 526\n" + "    },\n" + "    \"@type\": \"colorState\",\n"
                 + "    \"gamut\": \"LEDVANCE_GAMUT_A\",\n" + "    \"rgb\": -12427}");
index 0e650cde0096d10f9428f424231454d9f0bb821e..e493c57cc7890c0177ac677731ff9c53b4489856 100644 (file)
  */
 package org.openhab.binding.boschshc.internal.devices.thermostat;
 
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import javax.measure.quantity.Temperature;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandlerTest;
 import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.binding.boschshc.internal.services.childlock.dto.ChildLockServiceState;
+import org.openhab.binding.boschshc.internal.services.childlock.dto.ChildLockState;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
 import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
 
 /**
  * Unit Tests for {@link ThermostatHandler}.
@@ -26,6 +53,8 @@ import org.openhab.core.thing.ThingTypeUID;
 @NonNullByDefault
 public class ThermostatHandlerTest extends AbstractBatteryPoweredDeviceHandlerTest<ThermostatHandler> {
 
+    private @Captor @NonNullByDefault({}) ArgumentCaptor<ChildLockServiceState> childLockServiceStateCaptor;
+
     @Override
     protected ThermostatHandler createFixture() {
         return new ThermostatHandler(getThing());
@@ -40,4 +69,55 @@ public class ThermostatHandlerTest extends AbstractBatteryPoweredDeviceHandlerTe
     protected ThingTypeUID getThingTypeUID() {
         return BoschSHCBindingConstants.THING_TYPE_THERMOSTAT;
     }
+
+    @Test
+    public void testHandleCommand()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_CHILD_LOCK),
+                OnOffType.ON);
+        verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("Thermostat"), childLockServiceStateCaptor.capture());
+        ChildLockServiceState state = childLockServiceStateCaptor.getValue();
+        assertSame(ChildLockState.ON, state.childLock);
+    }
+
+    @Test
+    public void testHandleCommandUnknownCommand() {
+        getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_CHILD_LOCK),
+                new DecimalType(42));
+        ThingStatusInfo expectedThingStatusInfo = ThingStatusInfoBuilder
+                .create(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR)
+                .withDescription(
+                        "Error when service Thermostat should handle command org.openhab.core.library.types.DecimalType: Thermostat: Can not handle command org.openhab.core.library.types.DecimalType")
+                .build();
+        verify(getCallback()).statusUpdated(getThing(), expectedThingStatusInfo);
+    }
+
+    @Test
+    public void testUpdateChannelsTemperatureLevelService() {
+        JsonElement jsonObject = JsonParser.parseString(
+                "{\n" + "   \"@type\": \"temperatureLevelState\",\n" + "   \"temperature\": 21.5\n" + " }");
+        getFixture().processUpdate("TemperatureLevel", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_TEMPERATURE),
+                new QuantityType<Temperature>(21.5, SIUnits.CELSIUS));
+    }
+
+    @Test
+    public void testUpdateChannelsValveTappetService() {
+        JsonElement jsonObject = JsonParser
+                .parseString("{\n" + "   \"@type\": \"valveTappetState\",\n" + "   \"position\": 42\n" + " }");
+        getFixture().processUpdate("ValveTappet", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_VALVE_TAPPET_POSITION),
+                new DecimalType(42));
+    }
+
+    @Test
+    public void testUpdateChannelsChildLockService() {
+        JsonElement jsonObject = JsonParser
+                .parseString("{\n" + "   \"@type\": \"childLockState\",\n" + "   \"childLock\": \"ON\"\n" + " }");
+        getFixture().processUpdate("Thermostat", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_CHILD_LOCK), OnOffType.ON);
+    }
 }
index 58dd99efc1c870ccb68fe082d4941f0dd1a49ebb..2ef534dc6d56454f01f374e42bca005a861466df 100644 (file)
  */
 package org.openhab.binding.boschshc.internal.devices.twinguard;
 
+import static org.mockito.Mockito.verify;
+
+import javax.measure.quantity.Dimensionless;
+import javax.measure.quantity.Temperature;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
 import org.openhab.binding.boschshc.internal.devices.AbstractSmokeDetectorHandlerTest;
 import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.ThingTypeUID;
 
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+
 /**
  * Unit Tests for {@link TwinguardHandler}.
  *
@@ -40,4 +54,43 @@ public class TwinguardHandlerTest extends AbstractSmokeDetectorHandlerTest<Twing
     protected ThingTypeUID getThingTypeUID() {
         return BoschSHCBindingConstants.THING_TYPE_TWINGUARD;
     }
+
+    @Test
+    public void testUpdateChannelsAirQualityLevelService() {
+        JsonElement jsonObject = JsonParser.parseString(
+                "{\"temperatureRating\":\"GOOD\",\"humidityRating\":\"MEDIUM\",\"purity\":620,\"@type\":\"airQualityLevelState\",\n"
+                        + "     \"purityRating\":\"GOOD\",\"temperature\":23.77,\"description\":\"LITTLE_DRY\",\"humidity\":32.69,\"combinedRating\":\"MEDIUM\"}");
+        getFixture().processUpdate("AirQualityLevel", jsonObject);
+
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_TEMPERATURE),
+                new QuantityType<Temperature>(23.77, SIUnits.CELSIUS));
+
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_TEMPERATURE_RATING),
+                new StringType("GOOD"));
+
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_HUMIDITY),
+                new QuantityType<Dimensionless>(32.69, Units.PERCENT));
+
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_HUMIDITY_RATING),
+                new StringType("MEDIUM"));
+
+        verify(getCallback()).stateUpdated(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PURITY),
+                new QuantityType<Dimensionless>(620, Units.PARTS_PER_MILLION));
+
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PURITY_RATING),
+                new StringType("GOOD"));
+
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_AIR_DESCRIPTION),
+                new StringType("LITTLE_DRY"));
+
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_COMBINED_RATING),
+                new StringType("MEDIUM"));
+    }
 }
index 96b1ec31a91448158a70bab37914af792e358d62..9f755f696e13c5e4e62e0c903fcbc6d4582fc256 100644 (file)
  */
 package org.openhab.binding.boschshc.internal.devices.wallthermostat;
 
+import static org.mockito.Mockito.verify;
+
+import javax.measure.quantity.Dimensionless;
+import javax.measure.quantity.Temperature;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
 import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandlerTest;
 import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.ThingTypeUID;
 
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+
 /**
  * Unit Tests for {@link WallThermostatHandler}.
  *
@@ -40,4 +53,24 @@ public class WallThermostatHandlerTest extends AbstractBatteryPoweredDeviceHandl
     protected ThingTypeUID getThingTypeUID() {
         return BoschSHCBindingConstants.THING_TYPE_WALL_THERMOSTAT;
     }
+
+    @Test
+    public void testUpdateChannelsTemperatureLevelService() {
+        JsonElement jsonObject = JsonParser.parseString(
+                "{\n" + "   \"@type\": \"temperatureLevelState\",\n" + "   \"temperature\": 21.5\n" + " }");
+        getFixture().processUpdate("TemperatureLevel", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_TEMPERATURE),
+                new QuantityType<Temperature>(21.5, SIUnits.CELSIUS));
+    }
+
+    @Test
+    public void testUpdateChannelsHumidityLevelService() {
+        JsonElement jsonObject = JsonParser
+                .parseString("{\n" + "   \"@type\": \"humidityLevelState\",\n" + "   \"humidity\": 42.5\n" + " }");
+        getFixture().processUpdate("HumidityLevel", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_HUMIDITY),
+                new QuantityType<Dimensionless>(42.5, Units.PERCENT));
+    }
 }
index 510ec0d3efccfad8487b32b1b5fe6fdfc6fdbb9a..9c1b3920a1068070ebf841d6f0519cad5cda8a19 100644 (file)
  */
 package org.openhab.binding.boschshc.internal.devices.windowcontact;
 
+import static org.mockito.Mockito.verify;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
 import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandlerTest;
 import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.ThingTypeUID;
 
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+
 /**
  * Unit Tests for {@link WindowContactHandler}.
  *
@@ -40,4 +48,19 @@ public class WindowContactHandlerTest extends AbstractBatteryPoweredDeviceHandle
     protected ThingTypeUID getThingTypeUID() {
         return BoschSHCBindingConstants.THING_TYPE_WINDOW_CONTACT;
     }
+
+    @Test
+    public void testUpdateChannelsShutterContactService() {
+        JsonElement jsonObject = JsonParser
+                .parseString("{\n" + "   \"@type\": \"shutterContactState\",\n" + "   \"value\": \"OPEN\"\n" + " }");
+        getFixture().processUpdate("ShutterContact", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_CONTACT), OpenClosedType.OPEN);
+
+        jsonObject = JsonParser
+                .parseString("{\n" + "   \"@type\": \"shutterContactState\",\n" + "   \"value\": \"CLOSED\"\n" + " }");
+        getFixture().processUpdate("ShutterContact", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_CONTACT), OpenClosedType.CLOSED);
+    }
 }
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/exceptions/LongPollingFailedExceptionTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/exceptions/LongPollingFailedExceptionTest.java
new file mode 100644 (file)
index 0000000..7eb58b7
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.exceptions;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link LongPollingFailedException}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class LongPollingFailedExceptionTest {
+
+    @Test
+    public void testConstructor() {
+        RuntimeException testException = new RuntimeException("test exception");
+        LongPollingFailedException longPollingFailedException = new LongPollingFailedException("message",
+                testException);
+        assertEquals("message", longPollingFailedException.getMessage());
+        assertSame(testException, longPollingFailedException.getCause());
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/exceptions/PairingFailedExceptionTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/exceptions/PairingFailedExceptionTest.java
new file mode 100644 (file)
index 0000000..b9bdfae
--- /dev/null
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.exceptions;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link PairingFailedException}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class PairingFailedExceptionTest {
+
+    @Test
+    public void testConstructor() {
+        PairingFailedException fixture = new PairingFailedException();
+        assertNotNull(fixture);
+        assertNull(fixture.getMessage());
+        assertNull(fixture.getCause());
+    }
+
+    @Test
+    public void testConstructorWithMessage() {
+        PairingFailedException fixture = new PairingFailedException("message");
+        assertNotNull(fixture);
+        assertEquals("message", fixture.getMessage());
+        assertNull(fixture.getCause());
+    }
+
+    @Test
+    public void testConstructorWithMessageAndCause() {
+        RuntimeException testException = new RuntimeException("test exception");
+        PairingFailedException fixture = new PairingFailedException("message", testException);
+        assertNotNull(fixture);
+        assertEquals("message", fixture.getMessage());
+        assertSame(testException, fixture.getCause());
+    }
+}
index d8ac8049da807b0208c12f6b938a7ee00ae1cec9..04b3c328cd0087a6ea102ac653443882f44607ee 100644 (file)
@@ -16,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.*;
 
 import java.util.ArrayList;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.Test;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Fault;
@@ -30,6 +31,7 @@ import org.openhab.core.types.UnDefType;
  * @author David Pace - Initial contribution
  *
  */
+@NonNullByDefault
 class BatteryLevelTest {
 
     @Test
index 05a3ae157a67637a7408aa5854a374965fb048ee..dd780413596e0c048e4a634bab1806b0c5e4356f 100644 (file)
@@ -12,8 +12,7 @@
  */
 package org.openhab.binding.boschshc.internal.services.dto;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.*;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.Test;
@@ -53,14 +52,14 @@ public class BoschSHCServiceStateTest {
     private final Gson gson = new Gson();
 
     @Test
-    public void fromJson_nullStateForDifferentType() {
+    public void fromJsonNullStateForDifferentType() {
         var state = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"differentState\"}", JsonObject.class),
                 TestState.class);
         assertEquals(null, state);
     }
 
     @Test
-    public void fromJson_stateObjectForValidJson() {
+    public void fromJsonStateObjectForValidJson() {
         var state = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState\"}", JsonObject.class),
                 TestState.class);
         assertNotEquals(null, state);
@@ -70,7 +69,7 @@ public class BoschSHCServiceStateTest {
      * This checks for a bug we had where the expected type stayed the same for different state classes
      */
     @Test
-    public void fromJson_stateObjectForValidJsonAfterOtherState() {
+    public void fromJsonStateObjectForValidJsonAfterOtherState() {
         BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState\"}", JsonObject.class), TestState.class);
         var state2 = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState2\"}", JsonObject.class),
                 TestState2.class);
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/JsonRestExceptionResponseTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/JsonRestExceptionResponseTest.java
new file mode 100644 (file)
index 0000000..86e485a
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.services.dto;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link JsonRestExceptionResponse}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class JsonRestExceptionResponseTest {
+
+    private @NonNullByDefault({}) JsonRestExceptionResponse fixture;
+
+    @BeforeEach
+    public void setUp() throws Exception {
+        fixture = new JsonRestExceptionResponse();
+    }
+
+    @Test
+    public void testIsValid() {
+        assertFalse(JsonRestExceptionResponse.isValid(null));
+        assertTrue(JsonRestExceptionResponse.isValid(fixture));
+        fixture.errorCode = null;
+        assertFalse(JsonRestExceptionResponse.isValid(fixture));
+        fixture.statusCode = null;
+        assertFalse(JsonRestExceptionResponse.isValid(fixture));
+        fixture.errorCode = "";
+        assertFalse(JsonRestExceptionResponse.isValid(fixture));
+    }
+}
index 390d3ecca422b9c0554faa64dad3e4ee223ca2ec..3f4fe0c8334e79d956b08aecbfac11aece8a9a3e 100644 (file)
  */
 package org.openhab.binding.boschshc.internal.services.intrusion;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -38,23 +36,21 @@ import com.google.gson.JsonParser;
 
 /**
  * Unit tests for {@link IntrusionDetectionControlStateService}.
- * 
+ *
  * @author David Pace - Initial contribution
  *
  */
+@NonNullByDefault
 @ExtendWith(MockitoExtension.class)
 class IntrusionDetectionControlStateServiceTest {
 
-    private IntrusionDetectionControlStateService fixture;
+    private @NonNullByDefault({}) IntrusionDetectionControlStateService fixture;
 
-    @Mock
-    private BridgeHandler bridgeHandler;
+    private @Mock @NonNullByDefault({}) BridgeHandler bridgeHandler;
 
-    @Mock
-    private Consumer<IntrusionDetectionControlState> consumer;
+    private @Mock @NonNullByDefault({}) Consumer<IntrusionDetectionControlState> consumer;
 
-    @Mock
-    private IntrusionDetectionControlState testState;
+    private @Mock @NonNullByDefault({}) IntrusionDetectionControlState testState;
 
     @BeforeEach
     void beforeEach() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
index 5ac919c8581dbf5f2849d5f462aa5f5acd77ab05..5372efa5c7c46cb71dcc696f5ab15cd714fdb3d1 100644 (file)
 package org.openhab.binding.boschshc.internal.services.intrusion;
 
 import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -34,23 +33,21 @@ import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDet
 
 /**
  * Unit tests for {@link IntrusionDetectionSystemStateService}.
- * 
+ *
  * @author David Pace - Initial contribution
  *
  */
+@NonNullByDefault
 @ExtendWith(MockitoExtension.class)
 class IntrusionDetectionSystemStateServiceTest {
 
-    private IntrusionDetectionSystemStateService fixture;
+    private @NonNullByDefault({}) IntrusionDetectionSystemStateService fixture;
 
-    @Mock
-    private BridgeHandler bridgeHandler;
+    private @Mock @NonNullByDefault({}) BridgeHandler bridgeHandler;
 
-    @Mock
-    private Consumer<IntrusionDetectionSystemState> consumer;
+    private @Mock @NonNullByDefault({}) Consumer<IntrusionDetectionSystemState> consumer;
 
-    @Mock
-    private IntrusionDetectionSystemState testState;
+    private @Mock @NonNullByDefault({}) IntrusionDetectionSystemState testState;
 
     @BeforeEach
     void beforeEach() {