[mqtt] Revive disabled itests (#12431)

This fixes all the compilation/dependency issues in the MQTT itests so they can be reenabled again.
The tests now create and use their own Moquette instance instead of the removed embedded MQTT broker.
The moquette-broker JAR is also included in the test bundles as workaround for its missing OSGi bundle manifest headers.

Signed-off-by: Wouter Born <github@maindrain.net>
This commit is contained in:
Wouter Born
2022-04-26 20:24:11 +02:00
committed by GitHub
parent f310cf7ca0
commit 62c30d034f
18 changed files with 487 additions and 642 deletions

View File

@@ -3,92 +3,110 @@
Bundle-SymbolicName: ${project.artifactId}
Fragment-Host: org.openhab.binding.mqtt.homeassistant
Import-Package: \
com.bugsnag.*;resolution:=optional,\
com.librato.metrics.reporter.*;resolution:=optional,\
*
-includeresource: \
moquette-broker-[0-9.]*.jar;lib:=true
-runrequires: \
bnd.identity;id='org.openhab.binding.mqtt.homeassistant.tests',\
bnd.identity;id='org.openhab.core.binding.xml',\
bnd.identity;id='org.openhab.core.thing.xml',\
bnd.identity;id='org.openhab.io.mqttembeddedbroker'
bnd.identity;id='org.openhab.core.thing.xml'
# We would like to use the "volatile" storage only
-runblacklist: \
bnd.identity;id='org.openhab.core.storage.json'
-runvm: \
-runvm.mqtt: \
-Dio.netty.noUnsafe=true,\
-Dmqttembeddedbroker.port=${mqttembeddedbroker.port}
-Dmqttbroker.port=${mqttbroker.port}
#
# done
#
-runbundles: \
ch.qos.logback.core;version='[1.2.3,1.2.4)',\
com.google.gson;version='[2.8.2,2.8.3)',\
javax.measure.unit-api;version='[1.0.0,1.0.1)',\
org.apache.commons.lang;version='[2.6.0,2.6.1)',\
org.apache.felix.configadmin;version='[1.9.8,1.9.9)',\
org.apache.felix.http.servlet-api;version='[1.1.2,1.1.3)',\
org.apache.felix.scr;version='[2.1.10,2.1.11)',\
org.apache.servicemix.bundles.xstream;version='[1.4.7,1.4.8)',\
org.eclipse.equinox.event;version='[1.4.300,1.4.301)',\
org.objenesis;version='[2.6.0,2.6.1)',\
org.osgi.service.event;version='[1.4.0,1.4.1)',\
slf4j.api;version='[1.7.25,1.7.26)',\
com.h2database.mvstore;version='[1.4.199,1.4.200)',\
io.netty.buffer;version='[4.1.42,4.1.43)',\
io.netty.codec;version='[4.1.42,4.1.43)',\
io.netty.codec-mqtt;version='[4.1.42,4.1.43)',\
io.netty.common;version='[4.1.42,4.1.43)',\
io.netty.handler;version='[4.1.42,4.1.43)',\
io.netty.resolver;version='[4.1.42,4.1.43)',\
io.netty.transport;version='[4.1.42,4.1.43)',\
tec.uom.lib.uom-lib-common;version='[1.0.3,1.0.4)',\
tec.uom.se;version='[1.0.10,1.0.11)',\
ch.qos.logback.classic;version='[1.2.3,1.2.4)',\
biz.aQute.tester.junit-platform;version='[5.1.2,5.1.3)',\
com.google.dagger;version='[2.20.0,2.20.1)',\
com.hivemq.client.mqtt;version='[1.1.2,1.1.3)',\
io.netty.codec-http;version='[4.1.34,4.1.35)',\
io.netty.transport-native-epoll;version='[4.1.34,4.1.35)',\
io.netty.transport-native-unix-common;version='[4.1.34,4.1.35)',\
io.reactivex.rxjava2.rxjava;version='[2.2.5,2.2.6)',\
junit-jupiter-api;version='[5.6.2,5.6.3)',\
junit-jupiter-engine;version='[5.6.2,5.6.3)',\
junit-platform-commons;version='[1.6.2,1.6.3)',\
junit-platform-engine;version='[1.6.2,1.6.3)',\
junit-platform-launcher;version='[1.6.2,1.6.3)',\
net.bytebuddy.byte-buddy;version='[1.10.13,1.10.14)',\
net.bytebuddy.byte-buddy-agent;version='[1.10.13,1.10.14)',\
org.apache.aries.javax.jax.rs-api;version='[1.0.0,1.0.1)',\
org.apache.commons.codec;version='[1.10.0,1.10.1)',\
org.eclipse.jetty.http;version='[9.4.20,9.4.21)',\
org.eclipse.jetty.io;version='[9.4.20,9.4.21)',\
org.eclipse.jetty.security;version='[9.4.20,9.4.21)',\
org.eclipse.jetty.server;version='[9.4.20,9.4.21)',\
org.eclipse.jetty.servlet;version='[9.4.20,9.4.21)',\
org.eclipse.jetty.util;version='[9.4.20,9.4.21)',\
org.glassfish.hk2.external.javax.inject;version='[2.4.0,2.4.1)',\
org.hamcrest;version='[2.2.0,2.2.1)',\
org.jctools.core;version='[2.1.2,2.1.3)',\
org.mockito.mockito-core;version='[3.4.6,3.4.7)',\
org.openhab.binding.mqtt;version='[3.0.0,3.0.1)',\
org.openhab.binding.mqtt.generic;version='[3.0.0,3.0.1)',\
org.openhab.binding.mqtt.homeassistant;version='[3.0.0,3.0.1)',\
org.openhab.binding.mqtt.homeassistant.tests;version='[3.0.0,3.0.1)',\
org.openhab.core;version='[3.0.0,3.0.1)',\
org.openhab.core.binding.xml;version='[3.0.0,3.0.1)',\
org.openhab.core.config.core;version='[3.0.0,3.0.1)',\
org.openhab.core.config.discovery;version='[3.0.0,3.0.1)',\
org.openhab.core.config.xml;version='[3.0.0,3.0.1)',\
org.openhab.core.io.console;version='[3.0.0,3.0.1)',\
org.openhab.core.io.transport.mqtt;version='[3.0.0,3.0.1)',\
org.openhab.core.test;version='[3.0.0,3.0.1)',\
org.openhab.core.thing;version='[3.0.0,3.0.1)',\
org.openhab.core.thing.xml;version='[3.0.0,3.0.1)',\
org.openhab.core.transform;version='[3.0.0,3.0.1)',\
org.openhab.io.mqttembeddedbroker;version='[3.0.0,3.0.1)',\
org.opentest4j;version='[1.2.0,1.2.1)',\
org.reactivestreams.reactive-streams;version='[1.0.2,1.0.3)',\
jakarta.xml.bind-api;version='[2.3.3,2.3.4)',\
com.sun.xml.bind.jaxb-osgi;version='[2.3.3,2.3.4)',\
org.glassfish.hk2.osgi-resource-locator;version='[1.0.1,1.0.2)',\
org.apache.servicemix.specs.activation-api-1.2.1;version='[1.2.1,1.2.2)'
org.apache.servicemix.specs.activation-api-1.2.1;version='[1.2.1,1.2.2)',\
com.google.dagger;version='[2.27.0,2.27.1)',\
com.google.gson;version='[2.8.9,2.8.10)',\
com.hivemq.client.mqtt;version='[1.2.2,1.2.3)',\
io.netty.buffer;version='[4.1.72,4.1.73)',\
io.netty.codec;version='[4.1.72,4.1.73)',\
io.netty.codec-http;version='[4.1.59,4.1.60)',\
io.netty.codec-socks;version='[4.1.72,4.1.73)',\
io.netty.common;version='[4.1.72,4.1.73)',\
io.netty.handler;version='[4.1.72,4.1.73)',\
io.netty.handler-proxy;version='[4.1.72,4.1.73)',\
io.netty.resolver;version='[4.1.72,4.1.73)',\
io.netty.tcnative-classes;version='[2.0.46,2.0.47)',\
io.netty.transport;version='[4.1.72,4.1.73)',\
io.netty.transport-native-epoll;version='[4.1.59,4.1.60)',\
io.netty.transport-native-unix-common;version='[4.1.59,4.1.60)',\
io.reactivex.rxjava2.rxjava;version='[2.2.19,2.2.20)',\
jakarta.annotation-api;version='[2.0.0,2.0.1)',\
jakarta.inject.jakarta.inject-api;version='[2.0.0,2.0.1)',\
javax.measure.unit-api;version='[2.1.2,2.1.3)',\
junit-jupiter-api;version='[5.8.1,5.8.2)',\
junit-jupiter-engine;version='[5.8.1,5.8.2)',\
junit-platform-commons;version='[1.8.1,1.8.2)',\
junit-platform-engine;version='[1.8.1,1.8.2)',\
junit-platform-launcher;version='[1.8.1,1.8.2)',\
net.bytebuddy.byte-buddy;version='[1.12.1,1.12.2)',\
net.bytebuddy.byte-buddy-agent;version='[1.12.1,1.12.2)',\
org.apache.aries.javax.jax.rs-api;version='[1.0.1,1.0.2)',\
org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\
org.apache.felix.scr;version='[2.1.30,2.1.31)',\
org.eclipse.jetty.http;version='[9.4.43,9.4.44)',\
org.eclipse.jetty.io;version='[9.4.43,9.4.44)',\
org.eclipse.jetty.security;version='[9.4.43,9.4.44)',\
org.eclipse.jetty.server;version='[9.4.43,9.4.44)',\
org.eclipse.jetty.servlet;version='[9.4.43,9.4.44)',\
org.eclipse.jetty.util;version='[9.4.43,9.4.44)',\
org.eclipse.jetty.util.ajax;version='[9.4.43,9.4.44)',\
org.glassfish.hk2.osgi-resource-locator;version='[1.0.3,1.0.4)',\
org.jsr-305;version='[3.0.2,3.0.3)',\
org.mockito.junit-jupiter;version='[4.1.0,4.1.1)',\
org.mockito.mockito-core;version='[4.1.0,4.1.1)',\
org.objenesis;version='[3.2.0,3.2.1)',\
org.openhab.binding.mqtt;version='[3.3.0,3.3.1)',\
org.openhab.binding.mqtt.generic;version='[3.3.0,3.3.1)',\
org.openhab.binding.mqtt.homeassistant;version='[3.3.0,3.3.1)',\
org.openhab.binding.mqtt.homeassistant.tests;version='[3.3.0,3.3.1)',\
org.openhab.core;version='[3.3.0,3.3.1)',\
org.openhab.core.binding.xml;version='[3.3.0,3.3.1)',\
org.openhab.core.config.core;version='[3.3.0,3.3.1)',\
org.openhab.core.config.discovery;version='[3.3.0,3.3.1)',\
org.openhab.core.config.xml;version='[3.3.0,3.3.1)',\
org.openhab.core.io.console;version='[3.3.0,3.3.1)',\
org.openhab.core.io.transport.mqtt;version='[3.3.0,3.3.1)',\
org.openhab.core.test;version='[3.3.0,3.3.1)',\
org.openhab.core.thing;version='[3.3.0,3.3.1)',\
org.openhab.core.thing.xml;version='[3.3.0,3.3.1)',\
org.openhab.core.transform;version='[3.3.0,3.3.1)',\
org.ops4j.pax.logging.pax-logging-api;version='[2.0.14,2.0.15)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.util.function;version='[1.2.0,1.2.1)',\
org.osgi.util.promise;version='[1.2.0,1.2.1)',\
org.reactivestreams.reactive-streams;version='[1.0.3,1.0.4)',\
si-units;version='[2.1.0,2.1.1)',\
si.uom.si-quantity;version='[2.1.0,2.1.1)',\
tech.units.indriya;version='[2.1.2,2.1.3)',\
uom-lib-common;version='[2.1.0,2.1.1)',\
xstream;version='[1.4.19,1.4.20)',\
com.h2database.mvstore;version='[1.4.199,1.4.200)',\
com.zaxxer.HikariCP;version='[2.4.7,2.4.8)',\
io.dropwizard.metrics.core;version='[3.2.2,3.2.3)',\
io.netty.codec-mqtt;version='[4.1.72,4.1.73)',\
org.apache.commons.codec;version='[1.10.0,1.10.1)',\
biz.aQute.tester.junit-platform;version='[6.2.0,6.2.1)'

View File

@@ -7,13 +7,17 @@
<parent>
<groupId>org.openhab.addons.itests</groupId>
<artifactId>org.openhab.addons.reactor.itests</artifactId>
<version>3.1.0-SNAPSHOT</version>
<version>3.3.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.mqtt.homeassistant.tests</artifactId>
<name>openHAB Add-ons :: Integration Tests :: MQTT HomeAssistant Tests</name>
<properties>
<mqttbroker.port>1883</mqttbroker.port>
</properties>
<dependencies>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
@@ -31,52 +35,43 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.github.j-n-k</groupId>
<artifactId>moquette-broker</artifactId>
<version>0.13.0.OH2</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</exclusion>
</exclusions>
<groupId>com.h2database</groupId>
<artifactId>h2-mvstore</artifactId>
<version>1.4.199</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
<version>${netty.version}</version>
<groupId>io.moquette</groupId>
<artifactId>moquette-broker</artifactId>
<version>0.15</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2-mvstore</artifactId>
<version>1.4.199</version>
<groupId>io.netty</groupId>
<artifactId>netty-codec-mqtt</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-mqtt</artifactId>
<artifactId>netty-common</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler-proxy</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
@@ -86,7 +81,7 @@
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<artifactId>netty-transport</artifactId>
<version>${netty.version}</version>
</dependency>
</dependencies>
@@ -98,14 +93,14 @@
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>reserve-network-port</id>
<id>reserve-mqtt-broker-port</id>
<goals>
<goal>reserve-network-port</goal>
</goals>
<phase>process-resources</phase>
<configuration>
<portNames>
<portName>mqttembeddedbroker.port</portName>
<portName>mqttbroker.port</portName>
</portNames>
</configuration>
</execution>

View File

@@ -1,39 +0,0 @@
/**
* 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.mqtt;
/**
* MQTT embedded broker constants
*
* @author David Graeff - Initial contribution
*/
public class Constants {
/**
* The broker connection client ID. You can request the embedded broker connection via the MqttService:
*
* <pre>
* MqttBrokerConnection c = mqttService.getBrokerConnection(Constants.CLIENTID);
* </pre>
*/
public static final String CLIENTID = "embedded-mqtt-broker";
/**
* The broker persistent identifier used for identifying configurations.
*/
public static final String PID = "org.openhab.core.mqttembeddedbroker";
/**
* The configuration key used for configuring the embedded broker port.
*/
public static final String PORT = "port";
}

View File

@@ -1,122 +0,0 @@
/**
* 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.mqtt;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.core.io.transport.mqtt.MqttConnectionObserver;
import org.openhab.core.io.transport.mqtt.MqttConnectionState;
import org.openhab.core.io.transport.mqtt.MqttService;
import org.openhab.core.io.transport.mqtt.MqttServiceObserver;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
/**
* A full implementation test, that starts the embedded MQTT broker and publishes a homeassistant MQTT discovery device
* tree.
*
* @author David Graeff - Initial contribution
* @author Wouter Born - Support running MQTT itests in parallel by reconfiguring embedded broker port
*/
@NonNullByDefault
public class EmbeddedBrokerTools {
private static final int BROKER_PORT = Integer.getInteger("mqttembeddedbroker.port", 1883);
private final ConfigurationAdmin configurationAdmin;
private final MqttService mqttService;
public @Nullable MqttBrokerConnection embeddedConnection;
public EmbeddedBrokerTools(ConfigurationAdmin configurationAdmin, MqttService mqttService) {
this.configurationAdmin = configurationAdmin;
this.mqttService = mqttService;
}
/**
* Request the embedded broker connection from the {@link MqttService} and wait for a connection to be established.
*
* @throws InterruptedException
* @throws IOException
*/
public MqttBrokerConnection waitForConnection() throws InterruptedException, IOException {
reconfigurePort();
embeddedConnection = mqttService.getBrokerConnection(Constants.CLIENTID);
if (embeddedConnection == null) {
Semaphore semaphore = new Semaphore(1);
semaphore.acquire();
MqttServiceObserver observer = new MqttServiceObserver() {
@Override
public void brokerAdded(String brokerID, MqttBrokerConnection broker) {
if (brokerID.equals(Constants.CLIENTID)) {
embeddedConnection = broker;
semaphore.release();
}
}
@Override
public void brokerRemoved(String brokerID, MqttBrokerConnection broker) {
}
};
mqttService.addBrokersListener(observer);
assertTrue(semaphore.tryAcquire(5, TimeUnit.SECONDS), "Wait for embedded connection client failed");
}
MqttBrokerConnection embeddedConnection = this.embeddedConnection;
if (embeddedConnection == null) {
throw new IllegalStateException();
}
Semaphore semaphore = new Semaphore(1);
semaphore.acquire();
MqttConnectionObserver mqttConnectionObserver = (state, error) -> {
if (state == MqttConnectionState.CONNECTED) {
semaphore.release();
}
};
embeddedConnection.addConnectionObserver(mqttConnectionObserver);
if (embeddedConnection.connectionState() == MqttConnectionState.CONNECTED) {
semaphore.release();
}
assertTrue(semaphore.tryAcquire(5, TimeUnit.SECONDS), "Connection " + embeddedConnection.getClientId()
+ " failed. State: " + embeddedConnection.connectionState());
return embeddedConnection;
}
public void reconfigurePort() throws IOException {
Configuration configuration = configurationAdmin.getConfiguration(Constants.PID, null);
Dictionary<String, Object> properties = configuration.getProperties();
if (properties == null) {
properties = new Hashtable<>();
}
Integer currentPort = (Integer) properties.get(Constants.PORT);
if (currentPort == null || currentPort.intValue() != BROKER_PORT) {
properties.put(Constants.PORT, BROKER_PORT);
configuration.update(properties);
// Remove the connection to make sure the test waits for the new connection to become available
mqttService.removeBrokerConnection(Constants.CLIENTID);
}
}
}

View File

@@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mqtt;
package org.openhab.binding.mqtt.homeassistant;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@@ -37,11 +37,11 @@ import org.mockito.quality.Strictness;
import org.openhab.binding.mqtt.generic.AvailabilityTracker;
import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
import org.openhab.binding.mqtt.generic.TransformationServiceProvider;
import org.openhab.binding.mqtt.homeassistant.internal.ChannelConfigurationTypeAdapterFactory;
import org.openhab.binding.mqtt.homeassistant.internal.DiscoverComponents;
import org.openhab.binding.mqtt.homeassistant.internal.DiscoverComponents.ComponentDiscovered;
import org.openhab.binding.mqtt.homeassistant.internal.HaID;
import org.openhab.binding.mqtt.homeassistant.internal.HandlerConfiguration;
import org.openhab.binding.mqtt.homeassistant.internal.config.ChannelConfigurationTypeAdapterFactory;
import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.core.test.java.JavaOSGiTest;
@@ -83,7 +83,7 @@ public class DiscoverComponentsTest extends JavaOSGiTest {
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ChannelConfigurationTypeAdapterFactory()).create();
DiscoverComponents discover = spy(new DiscoverComponents(ThingChannelConstants.testHomeAssistantThing,
DiscoverComponents discover = spy(new DiscoverComponents(ThingChannelConstants.TEST_HOME_ASSISTANT_THING,
scheduler, channelStateUpdateListener, availabilityTracker, gson, transformationServiceProvider));
HandlerConfiguration config = new HandlerConfiguration("homeassistant",

View File

@@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mqtt;
package org.openhab.binding.mqtt.homeassistant;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -25,11 +25,9 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@@ -45,22 +43,19 @@ import org.openhab.binding.mqtt.generic.AvailabilityTracker;
import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
import org.openhab.binding.mqtt.generic.TransformationServiceProvider;
import org.openhab.binding.mqtt.homeassistant.internal.AbstractComponent;
import org.openhab.binding.mqtt.homeassistant.internal.ChannelConfigurationTypeAdapterFactory;
import org.openhab.binding.mqtt.homeassistant.internal.ComponentSwitch;
import org.openhab.binding.mqtt.homeassistant.internal.DiscoverComponents;
import org.openhab.binding.mqtt.homeassistant.internal.DiscoverComponents.ComponentDiscovered;
import org.openhab.binding.mqtt.homeassistant.internal.HaID;
import org.openhab.binding.mqtt.homeassistant.internal.component.AbstractComponent;
import org.openhab.binding.mqtt.homeassistant.internal.component.Switch;
import org.openhab.binding.mqtt.homeassistant.internal.config.ChannelConfigurationTypeAdapterFactory;
import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.core.io.transport.mqtt.MqttConnectionObserver;
import org.openhab.core.io.transport.mqtt.MqttConnectionState;
import org.openhab.core.io.transport.mqtt.MqttService;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.test.java.JavaOSGiTest;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.openhab.core.util.UIDUtils;
import org.osgi.service.cm.ConfigurationAdmin;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -74,11 +69,9 @@ import com.google.gson.GsonBuilder;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@NonNullByDefault
public class HomeAssistantMQTTImplementationTest extends JavaOSGiTest {
private @NonNullByDefault({}) ConfigurationAdmin configurationAdmin;
private @NonNullByDefault({}) MqttService mqttService;
private @NonNullByDefault({}) MqttBrokerConnection embeddedConnection;
private @NonNullByDefault({}) MqttBrokerConnection connection;
public class HomeAssistantMQTTImplementationTest extends MqttOSGiTest {
private @NonNullByDefault({}) MqttBrokerConnection haConnection;
private int registeredTopics = 100;
private @Nullable Throwable failure;
@@ -93,24 +86,17 @@ public class HomeAssistantMQTTImplementationTest extends JavaOSGiTest {
private final MqttConnectionObserver failIfChange = (state, error) -> assertThat(state,
is(MqttConnectionState.CONNECTED));
private final String testObjectTopic = "homeassistant/switch/node/"
+ ThingChannelConstants.testHomeAssistantThing.getId();
+ ThingChannelConstants.TEST_HOME_ASSISTANT_THING.getId();
@Override
@BeforeEach
public void beforeEach() throws Exception {
registerVolatileStorageService();
configurationAdmin = getService(ConfigurationAdmin.class);
mqttService = getService(MqttService.class);
super.beforeEach();
// Wait for the EmbeddedBrokerService internal connection to be connected
embeddedConnection = new EmbeddedBrokerTools(configurationAdmin, mqttService).waitForConnection();
connection = new MqttBrokerConnection(embeddedConnection.getHost(), embeddedConnection.getPort(),
embeddedConnection.isSecure(), "ha_mqtt");
connection.start().get(2, TimeUnit.SECONDS);
assertThat(connection.connectionState(), is(MqttConnectionState.CONNECTED));
haConnection = createBrokerConnection("ha_mqtt");
// If the connection state changes in between -> fail
connection.addConnectionObserver(failIfChange);
haConnection.addConnectionObserver(failIfChange);
// Create topic string and config for one example HA component (a Switch)
final String config = "{'name':'testname','state_topic':'" + testObjectTopic + "/state','command_topic':'"
@@ -118,8 +104,8 @@ public class HomeAssistantMQTTImplementationTest extends JavaOSGiTest {
// Publish component configurations and component states to MQTT
List<CompletableFuture<Boolean>> futures = new ArrayList<>();
futures.add(embeddedConnection.publish(testObjectTopic + "/config", config.getBytes(), 0, true));
futures.add(embeddedConnection.publish(testObjectTopic + "/state", "ON".getBytes(), 0, true));
futures.add(publish(testObjectTopic + "/config", config));
futures.add(publish(testObjectTopic + "/state", "ON"));
registeredTopics = futures.size();
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(2, TimeUnit.SECONDS);
@@ -129,41 +115,42 @@ public class HomeAssistantMQTTImplementationTest extends JavaOSGiTest {
doReturn(null).when(transformationServiceProvider).getTransformationService(any());
}
@Override
@AfterEach
public void afterEach() throws Exception {
if (connection != null) {
connection.removeConnectionObserver(failIfChange);
connection.stop().get(2, TimeUnit.SECONDS);
if (haConnection != null) {
haConnection.removeConnectionObserver(failIfChange);
haConnection.stop().get(5, TimeUnit.SECONDS);
}
super.afterEach();
}
@Test
public void reconnectTest() throws InterruptedException, ExecutionException, TimeoutException {
connection.removeConnectionObserver(failIfChange);
connection.stop().get(2, TimeUnit.SECONDS);
connection = new MqttBrokerConnection(embeddedConnection.getHost(), embeddedConnection.getPort(),
embeddedConnection.isSecure(), "ha_mqtt");
connection.start().get(2, TimeUnit.SECONDS);
public void reconnectTest() throws Exception {
haConnection.removeConnectionObserver(failIfChange);
haConnection.stop().get(5, TimeUnit.SECONDS);
haConnection = createBrokerConnection("ha_mqtt");
}
@Test
public void retrieveAllTopics() throws InterruptedException, ExecutionException, TimeoutException {
public void retrieveAllTopics() throws Exception {
CountDownLatch c = new CountDownLatch(registeredTopics);
connection.subscribe("homeassistant/+/+/" + ThingChannelConstants.testHomeAssistantThing.getId() + "/#",
(topic, payload) -> c.countDown()).get(2, TimeUnit.SECONDS);
haConnection.subscribe("homeassistant/+/+/" + ThingChannelConstants.TEST_HOME_ASSISTANT_THING.getId() + "/#",
(topic, payload) -> c.countDown()).get(5, TimeUnit.SECONDS);
assertTrue(c.await(2, TimeUnit.SECONDS),
"Connection " + connection.getClientId() + " not retrieving all topics");
"Connection " + haConnection.getClientId() + " not retrieving all topics");
}
@Test
public void parseHATree() throws InterruptedException, ExecutionException, TimeoutException {
public void parseHATree() throws Exception {
MqttChannelTypeProvider channelTypeProvider = mock(MqttChannelTypeProvider.class);
final Map<String, AbstractComponent<?>> haComponents = new HashMap<>();
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ChannelConfigurationTypeAdapterFactory()).create();
ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(4);
DiscoverComponents discover = spy(new DiscoverComponents(ThingChannelConstants.testHomeAssistantThing,
DiscoverComponents discover = spy(new DiscoverComponents(ThingChannelConstants.TEST_HOME_ASSISTANT_THING,
scheduler, channelStateUpdateListener, availabilityTracker, gson, transformationServiceProvider));
// The DiscoverComponents object calls ComponentDiscovered callbacks.
@@ -171,15 +158,15 @@ public class HomeAssistantMQTTImplementationTest extends JavaOSGiTest {
// and add the types to the channelTypeProvider, like in the real Thing handler.
final CountDownLatch latch = new CountDownLatch(1);
ComponentDiscovered cd = (haID, c) -> {
haComponents.put(c.uid().getId(), c);
haComponents.put(c.getGroupUID().getId(), c);
c.addChannelTypes(channelTypeProvider);
channelTypeProvider.setChannelGroupType(c.groupTypeUID(), c.type());
channelTypeProvider.setChannelGroupType(c.getGroupTypeUID(), c.getType());
latch.countDown();
};
// Start the discovery for 2000ms. Forced timeout after 4000ms.
HaID haID = new HaID(testObjectTopic + "/config");
CompletableFuture<Void> future = discover.startDiscovery(connection, 2000, Collections.singleton(haID), cd)
CompletableFuture<Void> future = discover.startDiscovery(haConnection, 2000, Collections.singleton(haID), cd)
.thenRun(() -> {
}).exceptionally(e -> {
failure = e;
@@ -187,7 +174,7 @@ public class HomeAssistantMQTTImplementationTest extends JavaOSGiTest {
});
assertTrue(latch.await(4, TimeUnit.SECONDS));
future.get(2, TimeUnit.SECONDS);
future.get(5, TimeUnit.SECONDS);
// No failure expected and one discovered result
assertNull(failure);
@@ -199,13 +186,13 @@ public class HomeAssistantMQTTImplementationTest extends JavaOSGiTest {
verify(channelTypeProvider, times(1)).setChannelType(any(), any());
String channelGroupId = UIDUtils
.encode("node_" + ThingChannelConstants.testHomeAssistantThing.getId() + "_switch");
.encode("node_" + ThingChannelConstants.TEST_HOME_ASSISTANT_THING.getId() + "_switch");
State value = haComponents.get(channelGroupId).channelTypes().get(ComponentSwitch.switchChannelID).getState()
.getCache().getChannelState();
State value = haComponents.get(channelGroupId).getChannel(Switch.SWITCH_CHANNEL_ID).getState().getCache()
.getChannelState();
assertThat(value, is(UnDefType.UNDEF));
haComponents.values().stream().map(e -> e.start(connection, scheduler, 100))
haComponents.values().stream().map(e -> e.start(haConnection, scheduler, 100))
.reduce(CompletableFuture.completedFuture(null), (a, v) -> a.thenCompose(b -> v)).exceptionally(e -> {
failure = e;
return null;
@@ -215,8 +202,8 @@ public class HomeAssistantMQTTImplementationTest extends JavaOSGiTest {
verify(channelStateUpdateListener, timeout(4000).times(1)).updateChannelState(any(), any());
// Value should be ON now.
value = haComponents.get(channelGroupId).channelTypes().get(ComponentSwitch.switchChannelID).getState()
.getCache().getChannelState();
value = haComponents.get(channelGroupId).getChannel(Switch.SWITCH_CHANNEL_ID).getState().getCache()
.getChannelState();
assertThat(value, is(OnOffType.ON));
}
}

View File

@@ -0,0 +1,87 @@
/**
* 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.mqtt.homeassistant;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.core.io.transport.mqtt.MqttConnectionState;
import org.openhab.core.test.java.JavaOSGiTest;
import io.moquette.BrokerConstants;
import io.moquette.broker.Server;
/**
* Creates a Moquette MQTT broker instance and a {@link MqttBrokerConnection} for testing MQTT bindings.
*
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public class MqttOSGiTest extends JavaOSGiTest {
private static final String BROKER_ID = "test-broker";
private static final int BROKER_PORT = Integer.getInteger("mqttbroker.port", 1883);
protected @NonNullByDefault({}) MqttBrokerConnection brokerConnection;
private Server moquetteServer = new Server();
@BeforeEach
public void beforeEach() throws Exception {
registerVolatileStorageService();
moquetteServer = new Server();
moquetteServer.startServer(brokerProperties());
brokerConnection = createBrokerConnection(BROKER_ID);
}
@AfterEach
public void afterEach() throws Exception {
brokerConnection.stop().get(5, TimeUnit.SECONDS);
moquetteServer.stopServer();
}
private Properties brokerProperties() {
Properties properties = new Properties();
properties.put(BrokerConstants.HOST_PROPERTY_NAME, BrokerConstants.HOST);
properties.put(BrokerConstants.PORT_PROPERTY_NAME, String.valueOf(BROKER_PORT));
properties.put(BrokerConstants.SSL_PORT_PROPERTY_NAME, BrokerConstants.DISABLED_PORT_BIND);
properties.put(BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME, BrokerConstants.DISABLED_PORT_BIND);
properties.put(BrokerConstants.WSS_PORT_PROPERTY_NAME, BrokerConstants.DISABLED_PORT_BIND);
return properties;
}
protected MqttBrokerConnection createBrokerConnection(String clientId) throws Exception {
MqttBrokerConnection connection = new MqttBrokerConnection(BrokerConstants.HOST, BROKER_PORT, false, clientId);
connection.setQos(1);
connection.start().get(5, TimeUnit.SECONDS);
waitForAssert(() -> assertThat(connection.connectionState(), is(MqttConnectionState.CONNECTED)));
return connection;
}
protected CompletableFuture<Boolean> publish(String topic, String message) {
return brokerConnection.publish(topic, message.getBytes(StandardCharsets.UTF_8), 1, true);
}
}

View File

@@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mqtt;
package org.openhab.binding.mqtt.homeassistant;
import static org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants.HOMEASSISTANT_MQTT_THING;
@@ -25,5 +25,5 @@ import org.openhab.core.thing.ThingUID;
@NonNullByDefault
public class ThingChannelConstants {
// Common ThingUID and ChannelUIDs
public static final ThingUID testHomeAssistantThing = new ThingUID(HOMEASSISTANT_MQTT_THING, "device234");
public static final ThingUID TEST_HOME_ASSISTANT_THING = new ThingUID(HOMEASSISTANT_MQTT_THING, "device234");
}