Skip to content
This repository has been archived by the owner on Jul 2, 2020. It is now read-only.

Commit

Permalink
MQTT: Add MQTT receive tests for all channel types (eclipse-archived#…
Browse files Browse the repository at this point in the history
…6664)

The TypeParser commit a few days ago allowed to send command strings
like INCREASE/DECREASE. Unfortunately it broke a few channels.

* This commit adds tests for all channel types.
* The RGB color channel was broken (even before the TypeParser commit): Fixed.

Added features:
* Add location, datetime and image channel types including
  tests and readme sections.

Refactor:
* The internal.values.* classes are a lot slimmer. Value itself
  is not an interface but an abstract class now.
* Move the xml thing/channel configurations to ESH-INF/config.
* Rename two methods for clarity, so that value.getValue().getValue()
  is not a think anymore.

Signed-off-by: David Gräff <david.graeff@web.de>
  • Loading branch information
David Gräff authored and kaikreuzer committed Dec 12, 2018
1 parent bbbdbdb commit 804aaee
Show file tree
Hide file tree
Showing 32 changed files with 933 additions and 765 deletions.
Expand Up @@ -36,12 +36,12 @@
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homeassistant.ComponentSwitch;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents.ComponentDiscovered;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homeassistant.HaID;
import org.eclipse.smarthome.binding.mqtt.generic.internal.generic.ChannelStateUpdateListener;
import org.eclipse.smarthome.binding.mqtt.generic.internal.generic.MqttChannelTypeProvider;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homeassistant.HaID;
import org.eclipse.smarthome.binding.mqtt.generic.internal.handler.ThingChannelConstants;
import org.eclipse.smarthome.binding.mqtt.generic.internal.values.OnOffValue;
import org.eclipse.smarthome.core.library.types.OnOffType;
import org.eclipse.smarthome.core.types.State;
import org.eclipse.smarthome.core.types.UnDefType;
import org.eclipse.smarthome.io.transport.mqtt.MqttBrokerConnection;
import org.eclipse.smarthome.io.transport.mqtt.MqttConnectionObserver;
Expand Down Expand Up @@ -126,7 +126,7 @@ public void setUp() throws InterruptedException, ExecutionException, TimeoutExce
public void tearDown() throws InterruptedException, ExecutionException, TimeoutException {
if (connection != null) {
connection.removeConnectionObserver(failIfChange);
connection.stop().get(500, TimeUnit.MILLISECONDS);
connection.stop().get(1000, TimeUnit.MILLISECONDS);
}
}

Expand Down Expand Up @@ -190,9 +190,9 @@ public void parseHATree() throws InterruptedException, ExecutionException, Timeo
verify(channelTypeProvider, times(1)).setChannelType(any(), any());

// We expect a switch component with an OnOff channel with the initial value UNDEF:
OnOffValue value = (OnOffValue) haComponents.get(haID.getChannelGroupID()).channelTypes()
.get(ComponentSwitch.switchChannelID).channelState.getValue();
assertThat(value.getValue(), is(UnDefType.UNDEF));
State value = haComponents.get(haID.getChannelGroupID()).channelTypes()
.get(ComponentSwitch.switchChannelID).channelState.getCache().getChannelState();
assertThat(value, is(UnDefType.UNDEF));

haComponents.values().stream().map(e -> e.start(connection, scheduler, 100))
.reduce(CompletableFuture.completedFuture(null), (a, v) -> a.thenCompose(b -> v)).exceptionally(e -> {
Expand All @@ -204,9 +204,9 @@ public void parseHATree() throws InterruptedException, ExecutionException, Timeo
verify(channelStateUpdateListener, times(1)).updateChannelState(any(), any());

// Value should be ON now.
value = (OnOffValue) haComponents.get(haID.getChannelGroupID()).channelTypes()
.get(ComponentSwitch.switchChannelID).channelState.getValue();
assertThat(value.getValue(), is(OnOffType.ON));
value = haComponents.get(haID.getChannelGroupID()).channelTypes()
.get(ComponentSwitch.switchChannelID).channelState.getCache().getChannelState();
assertThat(value, is(OnOffType.ON));

}
}
Expand Up @@ -176,7 +176,7 @@ public void retrieveAllTopics() throws InterruptedException, ExecutionException,
CountDownLatch c = new CountDownLatch(registeredTopics);
connection.subscribe(deviceTopic + "/#", (topic, payload) -> c.countDown()).get(200, TimeUnit.MILLISECONDS);
assertTrue("Connection " + connection.getClientId() + " not retrieving all topics",
c.await(200, TimeUnit.MILLISECONDS));
c.await(1000, TimeUnit.MILLISECONDS));
}

@Test
Expand Down Expand Up @@ -217,7 +217,7 @@ public void retrieveAttributes() throws InterruptedException, ExecutionException
verify(channelState).processMessage(any(), any());
verify(callback).updateChannelState(any(), any());

assertThat(property.getChannelState().getValue().getValue(), is(new DecimalType(10)));
assertThat(property.getChannelState().getCache().getChannelState(), is(new DecimalType(10)));

property.stop().get();
assertThat(connection.hasSubscribers(), is(false));
Expand Down Expand Up @@ -307,17 +307,17 @@ public void parseHomieTree() throws InterruptedException, ExecutionException, Ti
// The device->node->property tree is ready. Now subscribe to property values.
device.startChannels(connection, scheduler, 50, handler).get();
assertThat(propertyBell.getChannelState().isStateful(), is(false));
assertThat(propertyBell.getChannelState().getValue().getValue(), is(UnDefType.UNDEF));
assertThat(property.getChannelState().getValue().getValue(), is(new DecimalType(10)));
assertThat(propertyBell.getChannelState().getCache().getChannelState(), is(UnDefType.UNDEF));
assertThat(property.getChannelState().getCache().getChannelState(), is(new DecimalType(10)));

property = node.properties.get("testRetain");
WaitForTopicValue watcher = new WaitForTopicValue(embeddedConnection, propertyTestTopic + "/set");
// Watch the topic. Publish a retain=false value to MQTT
property.getChannelState().setValue(OnOffType.OFF).get();
property.getChannelState().publishValue(OnOffType.OFF).get();
assertThat(watcher.waitForTopicValue(50), is("false"));

// Publish a retain=false value to MQTT.
property.getChannelState().setValue(OnOffType.ON).get();
property.getChannelState().publishValue(OnOffType.ON).get();
// This test is flaky if the MQTT broker does not get a time to "forget" this non-retained value
Thread.sleep(50);
// No value is expected to be retained on this MQTT topic
Expand Down
Expand Up @@ -12,12 +12,16 @@
*/
package org.eclipse.smarthome.binding.mqtt.generic.internal.generic;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.initMocks;

import java.math.BigDecimal;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
Expand All @@ -28,11 +32,18 @@

import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents.ComponentDiscovered;
import org.eclipse.smarthome.binding.mqtt.generic.internal.values.ColorValue;
import org.eclipse.smarthome.binding.mqtt.generic.internal.values.DateTimeValue;
import org.eclipse.smarthome.binding.mqtt.generic.internal.values.ImageValue;
import org.eclipse.smarthome.binding.mqtt.generic.internal.values.LocationValue;
import org.eclipse.smarthome.binding.mqtt.generic.internal.values.NumberValue;
import org.eclipse.smarthome.binding.mqtt.generic.internal.values.PercentageValue;
import org.eclipse.smarthome.binding.mqtt.generic.internal.values.TextValue;
import org.eclipse.smarthome.core.library.types.HSBType;
import org.eclipse.smarthome.core.library.types.RawType;
import org.eclipse.smarthome.core.library.types.StringType;
import org.eclipse.smarthome.core.thing.ChannelUID;
import org.eclipse.smarthome.io.transport.mqtt.MqttBrokerConnection;
import org.eclipse.smarthome.test.java.JavaOSGiTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
Expand All @@ -44,7 +55,7 @@
*
* @author David Graeff - Initial contribution
*/
public class ChannelStateTests extends JavaOSGiTest {
public class ChannelStateTests {
@Mock
MqttBrokerConnection connection;

Expand All @@ -62,6 +73,8 @@ public class ChannelStateTests extends JavaOSGiTest {

ScheduledExecutorService scheduler;

ChannelConfig config = ChannelConfigBuilder.create("state", "command").build();

@Before
public void setUp() {
initMocks(this);
Expand All @@ -84,69 +97,192 @@ public void tearDown() {

@Test
public void noInteractionTimeoutTest() throws InterruptedException, ExecutionException, TimeoutException {

ChannelState c = spy(new ChannelState(ChannelConfigBuilder.create("state", "command").build(), channelUID,
textValue, channelStateUpdateListener));
ChannelState c = spy(new ChannelState(config, channelUID, textValue, channelStateUpdateListener));
c.start(connection, scheduler, 50).get(100, TimeUnit.MILLISECONDS);
verify(connection).subscribe(eq("state"), eq(c));
c.stop().get();
verify(connection).unsubscribe(eq("state"), eq(c));
}

@Test
public void publishTest() throws InterruptedException, ExecutionException, TimeoutException {
ChannelState c = spy(new ChannelState(ChannelConfigBuilder.create("state", "command").build(), channelUID,
textValue, channelStateUpdateListener));
public void publishFormatTest() throws InterruptedException, ExecutionException, TimeoutException {
ChannelState c = spy(new ChannelState(config, channelUID, textValue, channelStateUpdateListener));

c.start(connection, scheduler, 0).get(50, TimeUnit.MILLISECONDS);
verify(connection).subscribe(eq("state"), eq(c));

c.setValue(new StringType("UPDATE")).get();
c.publishValue(new StringType("UPDATE")).get();
verify(connection).publish(eq("command"), argThat(p -> Arrays.equals(p, "UPDATE".getBytes())), anyInt(),
eq(false));

c.config.formatBeforePublish = "prefix%s";
c.setValue(new StringType("UPDATE")).get();
c.publishValue(new StringType("UPDATE")).get();
verify(connection).publish(eq("command"), argThat(p -> Arrays.equals(p, "prefixUPDATE".getBytes())), anyInt(),
eq(false));

c.config.formatBeforePublish = "%1$s-%1$s";
c.setValue(new StringType("UPDATE")).get();
c.publishValue(new StringType("UPDATE")).get();
verify(connection).publish(eq("command"), argThat(p -> Arrays.equals(p, "UPDATE-UPDATE".getBytes())), anyInt(),
eq(false));

c.config.formatBeforePublish = "%s";
c.config.retained = true;
c.setValue(new StringType("UPDATE")).get();
c.publishValue(new StringType("UPDATE")).get();
verify(connection).publish(eq("command"), any(), anyInt(), eq(true));

c.stop().get();
verify(connection).unsubscribe(eq("state"), eq(c));
}

@Test
public void receiveTest() throws InterruptedException, ExecutionException, TimeoutException {
ChannelState c = spy(new ChannelState(ChannelConfigBuilder.create("state", "command").build(), channelUID,
textValue, channelStateUpdateListener));
public void receiveWildcardTest() throws InterruptedException, ExecutionException, TimeoutException {
ChannelState c = spy(new ChannelState(ChannelConfigBuilder.create("state/+/topic", "command").build(),
channelUID, textValue, channelStateUpdateListener));

CompletableFuture<@Nullable Void> future = c.start(connection, scheduler, 100);
c.processMessage("state", "A TEST".getBytes());
c.processMessage("state/bla/topic", "A TEST".getBytes());
future.get(300, TimeUnit.MILLISECONDS);

assertThat(textValue.getValue().toString(), is("A TEST"));
assertThat(textValue.getChannelState().toString(), is("A TEST"));
verify(channelStateUpdateListener).updateChannelState(eq(channelUID), any());
}

@Test
public void receiveWildcardTest() throws InterruptedException, ExecutionException, TimeoutException {
ChannelState c = spy(new ChannelState(ChannelConfigBuilder.create("state/+/topic", "command").build(),
channelUID, textValue, channelStateUpdateListener));
public void receiveStringTest() throws InterruptedException, ExecutionException, TimeoutException {
ChannelState c = spy(new ChannelState(config, channelUID, textValue, channelStateUpdateListener));

CompletableFuture<@Nullable Void> future = c.start(connection, scheduler, 100);
c.processMessage("state/bla/topic", "A TEST".getBytes());
c.processMessage("state", "A TEST".getBytes());
future.get(300, TimeUnit.MILLISECONDS);

assertThat(textValue.getValue().toString(), is("A TEST"));
assertThat(textValue.getChannelState().toString(), is("A TEST"));
verify(channelStateUpdateListener).updateChannelState(eq(channelUID), any());
}

@Test
public void receiveDecimalTest() throws InterruptedException, ExecutionException, TimeoutException {
NumberValue value = new NumberValue(null, null, new BigDecimal(10));
ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
c.start(connection, mock(ScheduledExecutorService.class), 100);

c.processMessage("state", "15".getBytes());
assertThat(value.getChannelState().toString(), is("15"));

c.processMessage("state", "INCREASE".getBytes());
assertThat(value.getChannelState().toString(), is("25"));

c.processMessage("state", "DECREASE".getBytes());
assertThat(value.getChannelState().toString(), is("15"));

verify(channelStateUpdateListener, times(3)).updateChannelState(eq(channelUID), any());
}

@Test
public void receiveDecimalFractionalTest() throws InterruptedException, ExecutionException, TimeoutException {
NumberValue value = new NumberValue(null, null, new BigDecimal(10.5));
ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
c.start(connection, mock(ScheduledExecutorService.class), 100);

c.processMessage("state", "5.5".getBytes());
assertThat(value.getChannelState().toString(), is("5.5"));

c.processMessage("state", "INCREASE".getBytes());
assertThat(value.getChannelState().toString(), is("16.0"));
}

@Test
public void receivePercentageTest() throws InterruptedException, ExecutionException, TimeoutException {
PercentageValue value = new PercentageValue(new BigDecimal(-100), new BigDecimal(100), new BigDecimal(10));
ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
c.start(connection, mock(ScheduledExecutorService.class), 100);

c.processMessage("state", "-100".getBytes()); // 0%
assertThat(value.getChannelState().toString(), is("0"));

c.processMessage("state", "100".getBytes()); // 100%
assertThat(value.getChannelState().toString(), is("100"));

c.processMessage("state", "0".getBytes()); // 50%
assertThat(value.getChannelState().toString(), is("50"));

c.processMessage("state", "INCREASE".getBytes());
assertThat(value.getChannelState().toString(), is("60"));
}

@Test
public void receiveRGBColorTest() throws InterruptedException, ExecutionException, TimeoutException {
ColorValue value = new ColorValue(true, "FON", "FOFF");
ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
c.start(connection, mock(ScheduledExecutorService.class), 100);

c.processMessage("state", "ON".getBytes());
assertThat(value.getChannelState().toString(), is("0,0,10"));
assertThat(value.getMQTTpublishValue().toString(), is("25,25,25"));

c.processMessage("state", "FOFF".getBytes());
assertThat(value.getChannelState().toString(), is("0,0,0"));
assertThat(value.getMQTTpublishValue().toString(), is("0,0,0"));

HSBType t = HSBType.fromRGB(12, 18, 231);

c.processMessage("state", "12,18,231".getBytes());
assertThat(value.getChannelState(), is(t)); // HSB
// rgb -> hsv -> rgb is quite lossy
assertThat(value.getMQTTpublishValue().toString(), is("13,20,225"));
}

@Test
public void receiveHSBColorTest() throws InterruptedException, ExecutionException, TimeoutException {
ColorValue value = new ColorValue(false, "FON", "FOFF");
ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
c.start(connection, mock(ScheduledExecutorService.class), 100);

c.processMessage("state", "ON".getBytes()); // Minimum brightness is 10
assertThat(value.getChannelState().toString(), is("0,0,10"));
assertThat(value.getMQTTpublishValue().toString(), is("0,0,10"));

c.processMessage("state", "FOFF".getBytes());
assertThat(value.getChannelState().toString(), is("0,0,0"));
assertThat(value.getMQTTpublishValue().toString(), is("0,0,0"));

c.processMessage("state", "12,18,100".getBytes());
assertThat(value.getChannelState().toString(), is("12,18,100"));
assertThat(value.getMQTTpublishValue().toString(), is("12,18,100"));
}

@Test
public void receiveLocationTest() throws InterruptedException, ExecutionException, TimeoutException {
LocationValue value = new LocationValue();
ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
c.start(connection, mock(ScheduledExecutorService.class), 100);

c.processMessage("state", "46.833974, 7.108433".getBytes());
assertThat(value.getChannelState().toString(), is("46.833974,7.108433"));
assertThat(value.getMQTTpublishValue().toString(), is("46.833974,7.108433"));
}

@Test
public void receiveDateTimeTest() throws InterruptedException, ExecutionException, TimeoutException {
DateTimeValue value = new DateTimeValue();
ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
c.start(connection, mock(ScheduledExecutorService.class), 100);

ZonedDateTime zd = ZonedDateTime.now();
String datetime = zd.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
c.processMessage("state", datetime.getBytes());
assertThat(value.getChannelState().toString(), is(datetime + "+0100"));
assertThat(value.getMQTTpublishValue().toString(), is(datetime));
}

@Test
public void receiveImageTest() throws InterruptedException, ExecutionException, TimeoutException {
ImageValue value = new ImageValue();
ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
c.start(connection, mock(ScheduledExecutorService.class), 100);

byte payload[] = new byte[] { (byte) 0xFF, (byte) 0xD8, 0x01, 0x02, (byte) 0xFF, (byte) 0xD9 };
c.processMessage("state", payload);
assertThat(value.getChannelState(), is(instanceOf(RawType.class)));
assertThat(((RawType) value.getChannelState()).getMimeType(), is("image/jpeg"));
}
}
Expand Up @@ -126,6 +126,6 @@ public void processMessageWithJSONPath() throws Exception {
channelConfig.processMessage(channelConfig.getStateTopic(), payload);

verify(callback).stateUpdated(eq(textChannelUID), argThat(arg -> "23.2".equals(arg.toString())));
assertThat(channelConfig.getValue().getValue().toString(), is("23.2"));
assertThat(channelConfig.getCache().getChannelState().toString(), is("23.2"));
}
}

0 comments on commit 804aaee

Please sign in to comment.