]> git.basschouten.com Git - openhab-addons.git/commitdiff
[bosesoundtouch] Fix regression and add tests (#14097)
authorlsiepel <leosiepel@gmail.com>
Fri, 30 Dec 2022 12:58:27 +0000 (13:58 +0100)
committerGitHub <noreply@github.com>
Fri, 30 Dec 2022 12:58:27 +0000 (13:58 +0100)
* Fix regression and add tests

Signed-off-by: lsiepel <leosiepel@gmail.com>
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/CommandExecutor.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseHandler.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseProcessor.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/handler/BoseSoundTouchHandler.java
bundles/org.openhab.binding.bosesoundtouch/src/test/java/org/openhab/binding/bosesoundtouch/internal/SoundTouch20Tests.java [new file with mode: 0644]
bundles/org.openhab.binding.bosesoundtouch/src/test/java/org/openhab/binding/bosesoundtouch/internal/handler/InMemmoryContentStorage.java [new file with mode: 0644]

index 7a01af6069857d8cae62e659ba464c7fcbcb172c..65a1ad4d49b0ad9990d23065615d92a1a1f851b1 100644 (file)
@@ -155,7 +155,6 @@ public class CommandExecutor implements AvailableSources {
             contentItem.setPresetID(presetID);
 
             currentContentItem = contentItem;
-
         }
         updateOperatingValues();
     }
index 502680e58beabe8841e6f52bafc69c8c6f42e493..56b390fad43771fb2f0d714b74c8d5031cb830c8 100644 (file)
@@ -97,11 +97,6 @@ public class XMLResponseHandler extends DefaultHandler {
                                              // showing a
                                              // warning for unhandled states
 
-        XMLHandlerState localState = null;
-        if (stateMap != null) {
-            localState = stateMap.get(localName);
-        }
-
         switch (curState) {
             case INIT:
                 if ("updates".equals(localName)) {
@@ -112,10 +107,13 @@ public class XMLResponseHandler extends DefaultHandler {
                         state = XMLHandlerState.Unprocessed;
                     }
                 } else {
+                    XMLHandlerState localState = stateMap.get(localName);
                     if (localState == null) {
-                        logger.debug("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState,
+                        logger.warn("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState,
                                 localName);
                         state = XMLHandlerState.Unprocessed;
+                    } else {
+                        state = localState;
                     }
                 }
                 break;
@@ -196,9 +194,11 @@ public class XMLResponseHandler extends DefaultHandler {
                     state = XMLHandlerState.Presets;
                 } else if ("group".equals(localName)) {
                     this.masterDeviceId = new BoseSoundTouchConfiguration();
+                    state = stateMap.get(localName);
                 } else {
-                    if (localState == null) {
-                        logger.debug("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState,
+                    state = stateMap.get(localName);
+                    if (state == null) {
+                        logger.warn("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState,
                                 localName);
 
                         state = XMLHandlerState.Unprocessed;
@@ -366,16 +366,12 @@ public class XMLResponseHandler extends DefaultHandler {
             if (contentItem == null) {
                 contentItem = new ContentItem();
             }
-            String source = "";
-            String location = "";
-            String sourceAccount = "";
-            Boolean isPresetable = false;
 
             if (attributes != null) {
-                source = attributes.getValue("source");
-                sourceAccount = attributes.getValue("sourceAccount");
-                location = attributes.getValue("location");
-                isPresetable = Boolean.parseBoolean(attributes.getValue("isPresetable"));
+                String source = attributes.getValue("source");
+                String location = attributes.getValue("location");
+                String sourceAccount = attributes.getValue("sourceAccount");
+                Boolean isPresetable = Boolean.parseBoolean(attributes.getValue("isPresetable"));
 
                 if (source != null) {
                     contentItem.setSource(source);
index 533e538210ff214437bf526418dbb88b6fc68165..bd180faaf756dc7294cc61e26351b78d94b29280 100644 (file)
@@ -47,6 +47,7 @@ public class XMLResponseProcessor {
 
     public void handleMessage(String msg) throws SAXException, IOException, ParserConfigurationException {
         SAXParserFactory parserFactory = SAXParserFactory.newInstance();
+        parserFactory.setNamespaceAware(true);
         SAXParser parser = parserFactory.newSAXParser();
         XMLReader reader = parser.getXMLReader();
         reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
index c5a1e836010882e353b77422fd8fbefe58b5d24d..c69288c80b581e281ca272e88969b144d5dce8c7 100644 (file)
@@ -306,6 +306,14 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
         return commandExecutor;
     }
 
+    /**
+     * Sets the CommandExecutor of this handler
+     *
+     */
+    public void setCommandExecutor(@Nullable CommandExecutor commandExecutor) {
+        this.commandExecutor = commandExecutor;
+    }
+
     /**
      * Returns the Session this handler has opened
      *
diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/test/java/org/openhab/binding/bosesoundtouch/internal/SoundTouch20Tests.java b/bundles/org.openhab.binding.bosesoundtouch/src/test/java/org/openhab/binding/bosesoundtouch/internal/SoundTouch20Tests.java
new file mode 100644 (file)
index 0000000..f7a7d95
--- /dev/null
@@ -0,0 +1,136 @@
+/**
+ * Copyright (c) 2010-2022 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.bosesoundtouch.internal;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
+
+import java.text.MessageFormat;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
+import org.openhab.binding.bosesoundtouch.internal.handler.InMemmoryContentStorage;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.library.types.StringListType;
+import org.openhab.core.storage.Storage;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandlerCallback;
+import org.openhab.core.thing.binding.builder.ChannelBuilder;
+import org.openhab.core.thing.binding.builder.ThingBuilder;
+
+/**
+ *
+ * @author Leo Siepel - Initial contribution
+ *
+ */
+@ExtendWith(MockitoExtension.class)
+@NonNullByDefault
+public class SoundTouch20Tests {
+
+    private @Mock @NonNullByDefault({}) ThingHandlerCallback thingHandlerCallback;
+    private @NonNullByDefault({}) Thing soundTouchThing;
+    private @NonNullByDefault({}) BoseSoundTouchHandler thingHandler;
+    private @NonNullByDefault({}) XMLResponseProcessor processor;
+    private ThingUID thingUID = new ThingUID(BoseSoundTouchBindingConstants.BINDING_ID, "soundtouch20");
+    private ChannelUID volumeChannelUID = new ChannelUID(thingUID, BoseSoundTouchBindingConstants.CHANNEL_VOLUME);
+    private ChannelUID presetChannelUID = new ChannelUID(thingUID, BoseSoundTouchBindingConstants.CHANNEL_PRESET);
+    private Storage<@NonNull ContentItem> storage = new InMemmoryContentStorage();
+    private @Mock @NonNullByDefault({}) BoseStateDescriptionOptionProvider stateDescriptionProvider;
+
+    @BeforeEach
+    public void initialize() {
+        // arrange
+        Configuration config = new Configuration();
+        config.put(BoseSoundTouchConfiguration.MAC_ADDRESS, "B0D5CC1AAAA1");
+
+        soundTouchThing = ThingBuilder.create(BoseSoundTouchBindingConstants.BST_20_THING_TYPE_UID, thingUID)
+                .withConfiguration(config).withChannel(ChannelBuilder.create(volumeChannelUID, "Number").build())
+                .withChannel(ChannelBuilder.create(presetChannelUID, "Number").build()).build();
+
+        PresetContainer container = new PresetContainer(storage);
+        thingHandler = new BoseSoundTouchHandler(soundTouchThing, container, stateDescriptionProvider);
+        processor = new XMLResponseProcessor(thingHandler);
+    }
+
+    private void processIncomingMessage(String mesage) {
+        try {
+            processor.handleMessage(mesage);
+        } catch (Exception e) {
+            assert false : MessageFormat.format("handleMessage throws an exception: {0} Stacktrace: {1}",
+                    e.getMessage(), e.getStackTrace());
+        }
+    }
+
+    @Test
+    public void configurationPropertyUpdated() {
+        // arange
+        CommandExecutor executor = new CommandExecutor(thingHandler);
+        thingHandler.setCommandExecutor(executor);
+        String message = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><msg><header deviceID=\"B0D5CC1AAAA1\" url=\"info\" method=\"GET\"><request requestID=\"0\" msgType=\"RESPONSE\"><info type=\"new\" /></request></header><body><info deviceID=\"B0D5CC1AAAA1\"><name>livingroom</name><type>SoundTouch 20</type><margeAccountUUID>3504027</margeAccountUUID><components><component><componentCategory>SCM</componentCategory><softwareVersion>27.0.6.46330.5043500 epdbuild.trunk.hepdswbld04.2022-08-04T11:20:29</softwareVersion><serialNumber>U6148010803720048000100</serialNumber></component><component><componentCategory>PackagedProduct</componentCategory><serialNumber>069430P5227013812</serialNumber></component></components><margeURL>https://streaming.bose.com</margeURL><networkInfo type=\"SCM\"><macAddress>B0D5CC1AAAA1</macAddress><ipAddress>192.168.1.1</ipAddress></networkInfo><networkInfo type=\"SMSC\"><macAddress>5CF821E2FD76</macAddress><ipAddress>192.168.1.1</ipAddress></networkInfo><moduleType>sm2</moduleType><variant>spotty</variant><variantMode>normal</variantMode><countryCode>GB</countryCode><regionCode>GB</regionCode></info></body></msg>";
+
+        // act
+        processIncomingMessage(message);
+
+        // assert
+        assertEquals("27.0.6.46330.5043500",
+                soundTouchThing.getProperties().get(org.openhab.core.thing.Thing.PROPERTY_FIRMWARE_VERSION));
+    }
+
+    @Test
+    public void channelVolumeUpdated() {
+        // arrange
+        CommandExecutor executor = new CommandExecutor(thingHandler);
+
+        thingHandler.setCommandExecutor(executor);
+        Mockito.when(thingHandlerCallback.isChannelLinked((ChannelUID) notNull())).thenReturn(true);
+        thingHandler.setCallback(thingHandlerCallback);
+        String message = "<updates deviceID=\"B0D5CC1AAAA1\"><volumeUpdated><volume><actualvolume>27</actualvolume></volume></volumeUpdated></updates>";
+
+        // act
+        processIncomingMessage(message);
+
+        // assert
+        Mockito.verify(thingHandlerCallback).stateUpdated(eq(volumeChannelUID), eq(new PercentType("27")));
+    }
+
+    @Test
+    @Disabled
+    public void channelPresetUpdated() {
+        // arrange
+        CommandExecutor executor = new CommandExecutor(thingHandler);
+
+        thingHandler.setCommandExecutor(executor);
+        Mockito.when(thingHandlerCallback.isChannelLinked((ChannelUID) notNull())).thenReturn(true);
+        thingHandler.setCallback(thingHandlerCallback);
+        String message = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><presets><preset id=\"1\" createdOn=\"1502124154\" updatedOn=\"1644607971\"><ContentItem source=\"TUNEIN\" type=\"stationurl\" location=\"/v1/playback/station/s25077\" sourceAccount=\"\" isPresetable=\"true\"><itemName>Radio FM1</itemName><containerArt>http://cdn-profiles.tunein.com/s25077/images/logoq.jpg</containerArt></ContentItem></preset><preset id=\"2\" createdOn=\"1485893875\" updatedOn=\"1612895566\"><ContentItem source=\"STORED_MUSIC\" location=\"22$2955\" sourceAccount=\"00113254-f4eb-0011-ebf4-ebf454321100/0\" isPresetable=\"true\"><itemName>Medicine At Midnight</itemName><containerArt /></ContentItem></preset><preset id=\"3\" createdOn=\"1506167722\" updatedOn=\"1506167722\"><ContentItem source=\"STORED_MUSIC\" location=\"22$1421\" sourceAccount=\"00113254-f4eb-0011-ebf4-ebf454321100/0\" isPresetable=\"true\"><itemName>Concrete &amp; Gold</itemName><containerArt /></ContentItem></preset><preset id=\"4\" createdOn=\"1444146657\" updatedOn=\"1542740566\"><ContentItem source=\"TUNEIN\" type=\"stationurl\" location=\"/v1/playback/station/s24896\" sourceAccount=\"\" isPresetable=\"true\"><itemName>SWR3</itemName><containerArt>http://radiotime-logos.s3.amazonaws.com/s24896q.png</containerArt></ContentItem></preset><preset id=\"5\" createdOn=\"1468517184\" updatedOn=\"1542740566\"><ContentItem source=\"TUNEIN\" type=\"stationurl\" location=\"/v1/playback/station/s103302\" sourceAccount=\"\" isPresetable=\"true\"><itemName>SRF 3</itemName><containerArt>http://radiotime-logos.s3.amazonaws.com/s24862q.png</containerArt></ContentItem></preset><preset id=\"6\" createdOn=\"1481548081\" updatedOn=\"1524211387\"><ContentItem source=\"STORED_MUSIC\" location=\"22$882\" sourceAccount=\"00113254-f4eb-0011-ebf4-ebf454321100/0\" isPresetable=\"true\"><itemName>Sonic Highways</itemName><containerArt /></ContentItem></preset></presets>";
+
+        // act
+        processIncomingMessage(message);
+
+        // assert
+        // TODO: check if preset channels have changed
+        Mockito.verify(thingHandlerCallback).stateUpdated(eq(presetChannelUID), eq(new StringListType("27")));
+    }
+}
diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/test/java/org/openhab/binding/bosesoundtouch/internal/handler/InMemmoryContentStorage.java b/bundles/org.openhab.binding.bosesoundtouch/src/test/java/org/openhab/binding/bosesoundtouch/internal/handler/InMemmoryContentStorage.java
new file mode 100644 (file)
index 0000000..2b7a3c0
--- /dev/null
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2010-2022 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.bosesoundtouch.internal.handler;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.bosesoundtouch.internal.ContentItem;
+import org.openhab.core.storage.Storage;
+
+/**
+ * @author Leo Siepel - Initial contribution
+ */
+@NonNullByDefault
+public class InMemmoryContentStorage implements Storage<ContentItem> {
+    Map<String, @Nullable ContentItem> items = new TreeMap<>();
+
+    public InMemmoryContentStorage() {
+    }
+
+    @Override
+    public @Nullable ContentItem put(String key, @Nullable ContentItem value) {
+        return items.put(key, value);
+    }
+
+    @Override
+    public @Nullable ContentItem remove(String key) {
+        return items.remove(key);
+    }
+
+    @Override
+    public boolean containsKey(String key) {
+        return items.containsKey(key);
+    }
+
+    @Override
+    public @Nullable ContentItem get(String key) {
+        return items.get(key);
+    }
+
+    @Override
+    public Collection<@NonNull String> getKeys() {
+        return items.keySet();
+    }
+
+    @Override
+    public Collection<@Nullable ContentItem> getValues() {
+        return items.values();
+    }
+}