Cover MQTT API with black box tests
This commit is contained in:
parent
67b8c2f4bf
commit
fb098e1bae
@ -15,4 +15,4 @@ As result, in REPOSITORY column, next images should be present:
|
||||
|
||||
- Run the integration tests in the [msa/integration-tests](../integration-tests) directory:
|
||||
|
||||
mvn clean install -Dintegrationtests.skip=false
|
||||
mvn clean install -DintegrationTests.skip=false
|
||||
@ -34,9 +34,10 @@
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<main.dir>${basedir}/../..</main.dir>
|
||||
<integrationtests.skip>true</integrationtests.skip>
|
||||
<integrationTests.skip>true</integrationTests.skip>
|
||||
<testcontainers.version>1.9.1</testcontainers.version>
|
||||
<java-websocket.version>1.3.9</java-websocket.version>
|
||||
<httpclient.version>4.5.6</httpclient.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@ -50,6 +51,11 @@
|
||||
<artifactId>Java-WebSocket</artifactId>
|
||||
<version>${java-websocket.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${httpclient.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.takari.junit</groupId>
|
||||
<artifactId>takari-cpsuite</artifactId>
|
||||
@ -89,7 +95,7 @@
|
||||
<includes>
|
||||
<include>**/*TestSuite.java</include>
|
||||
</includes>
|
||||
<skipTests>${integrationtests.skip}</skipTests>
|
||||
<skipTests>${integrationTests.skip}</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
@ -21,55 +21,68 @@ import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.http.config.Registry;
|
||||
import org.apache.http.config.RegistryBuilder;
|
||||
import org.apache.http.conn.socket.ConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.TrustStrategy;
|
||||
import org.apache.http.conn.ssl.X509HostnameVerifier;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.apache.http.ssl.SSLContextBuilder;
|
||||
import org.apache.http.ssl.SSLContexts;
|
||||
import org.junit.*;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.thingsboard.client.tools.RestClient;
|
||||
import org.thingsboard.server.common.data.Device;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.id.DeviceId;
|
||||
import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
public abstract class AbstractContainerTest {
|
||||
protected static String httpUrl;
|
||||
protected static String wsUrl;
|
||||
protected static final String HTTPS_URL = "https://localhost";
|
||||
protected static final String WSS_URL = "wss://localhost";
|
||||
protected static RestClient restClient;
|
||||
protected ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@BeforeClass
|
||||
public static void before() {
|
||||
httpUrl = "http://localhost:" + ContainerTestSuite.composeContainer.getServicePort("tb-web-ui1", ContainerTestSuite.EXPOSED_PORT);
|
||||
wsUrl = "ws://localhost:" + ContainerTestSuite.composeContainer.getServicePort("tb-web-ui1", ContainerTestSuite.EXPOSED_PORT);
|
||||
restClient = new RestClient(httpUrl);
|
||||
public static void before() throws Exception {
|
||||
restClient = new RestClient(HTTPS_URL);
|
||||
restClient.getRestTemplate().setRequestFactory(getRequestFactoryForSelfSignedCert());
|
||||
}
|
||||
|
||||
protected Device createDevice(String name) {
|
||||
return restClient.createDevice(name + RandomStringUtils.randomAlphanumeric(7), "DEFAULT");
|
||||
}
|
||||
|
||||
protected WsClient subscribeToTelemetryWebSocket(DeviceId deviceId) throws URISyntaxException, InterruptedException {
|
||||
WsClient mWs = new WsClient(new URI(wsUrl + "/api/ws/plugins/telemetry?token=" + restClient.getToken()));
|
||||
mWs.connectBlocking(1, TimeUnit.SECONDS);
|
||||
protected WsClient subscribeToWebSocket(DeviceId deviceId, String scope, CmdsType property) throws Exception {
|
||||
WsClient wsClient = new WsClient(new URI(WSS_URL + "/api/ws/plugins/telemetry?token=" + restClient.getToken()));
|
||||
SSLContextBuilder builder = SSLContexts.custom();
|
||||
builder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true);
|
||||
wsClient.setSocket(builder.build().getSocketFactory().createSocket());
|
||||
wsClient.connectBlocking();
|
||||
|
||||
JsonObject tsSubCmd = new JsonObject();
|
||||
tsSubCmd.addProperty("entityType", EntityType.DEVICE.name());
|
||||
tsSubCmd.addProperty("entityId", deviceId.toString());
|
||||
tsSubCmd.addProperty("scope", "LATEST_TELEMETRY");
|
||||
tsSubCmd.addProperty("cmdId", new Random().nextInt(100));
|
||||
tsSubCmd.addProperty("unsubscribe", false);
|
||||
JsonArray wsTsSubCmds = new JsonArray();
|
||||
wsTsSubCmds.add(tsSubCmd);
|
||||
JsonObject cmdsObject = new JsonObject();
|
||||
cmdsObject.addProperty("entityType", EntityType.DEVICE.name());
|
||||
cmdsObject.addProperty("entityId", deviceId.toString());
|
||||
cmdsObject.addProperty("scope", scope);
|
||||
cmdsObject.addProperty("cmdId", new Random().nextInt(100));
|
||||
|
||||
JsonArray cmd = new JsonArray();
|
||||
cmd.add(cmdsObject);
|
||||
JsonObject wsRequest = new JsonObject();
|
||||
wsRequest.add("tsSubCmds", wsTsSubCmds);
|
||||
wsRequest.add("historyCmds", new JsonArray());
|
||||
wsRequest.add("attrSubCmds", new JsonArray());
|
||||
mWs.send(wsRequest.toString());
|
||||
return mWs;
|
||||
wsRequest.add(property.toString(), cmd);
|
||||
wsClient.send(wsRequest.toString());
|
||||
return wsClient;
|
||||
}
|
||||
|
||||
protected Map<String, Long> getExpectedLatestValues(long ts) {
|
||||
@ -109,4 +122,54 @@ public abstract class AbstractContainerTest {
|
||||
return values;
|
||||
}
|
||||
|
||||
protected enum CmdsType {
|
||||
TS_SUB_CMDS("tsSubCmds"),
|
||||
HISTORY_CMDS("historyCmds"),
|
||||
ATTR_SUB_CMDS("attrSubCmds");
|
||||
|
||||
private final String text;
|
||||
|
||||
CmdsType(final String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpComponentsClientHttpRequestFactory getRequestFactoryForSelfSignedCert() throws Exception {
|
||||
SSLContextBuilder builder = SSLContexts.custom();
|
||||
builder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true);
|
||||
SSLContext sslContext = builder.build();
|
||||
SSLConnectionSocketFactory sslSelfSigned = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {
|
||||
@Override
|
||||
public void verify(String host, SSLSocket ssl) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(String host, X509Certificate cert) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(String host, String[] cns, String[] subjectAlts) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String s, SSLSession sslSession) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
|
||||
.<ConnectionSocketFactory>create()
|
||||
.register("https", sslSelfSigned)
|
||||
.build();
|
||||
|
||||
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
|
||||
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
|
||||
return new HttpComponentsClientHttpRequestFactory(httpClient);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -26,12 +26,11 @@ import java.io.File;
|
||||
@RunWith(ClasspathSuite.class)
|
||||
@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*"})
|
||||
public class ContainerTestSuite {
|
||||
static final int EXPOSED_PORT = 8080;
|
||||
|
||||
@ClassRule
|
||||
public static DockerComposeContainer composeContainer = new DockerComposeContainer(new File("./../docker/docker-compose.yml"))
|
||||
.withPull(false)
|
||||
.withLocalCompose(true)
|
||||
.withTailChildContainers(true)
|
||||
.withExposedService("tb-web-ui1", EXPOSED_PORT, Wait.forHttp("/login"));
|
||||
.withExposedService("tb-web-ui1", 8080, Wait.forHttp("/login"));
|
||||
}
|
||||
|
||||
@ -19,16 +19,12 @@ import org.java_websocket.client.WebSocketClient;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
public class WsClient extends WebSocketClient {
|
||||
private final BlockingQueue<String> events;
|
||||
private String message;
|
||||
|
||||
public WsClient(URI serverUri) {
|
||||
super(serverUri);
|
||||
events = new ArrayBlockingQueue<>(100);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -37,13 +33,11 @@ public class WsClient extends WebSocketClient {
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
events.add(message);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(int code, String reason, boolean remote) {
|
||||
events.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -54,4 +48,4 @@ public class WsClient extends WebSocketClient {
|
||||
public String getLastMessage() {
|
||||
return this.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.thingsboard.server.msa.connectivity;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -22,7 +23,7 @@ import org.thingsboard.server.common.data.Device;
|
||||
import org.thingsboard.server.common.data.security.DeviceCredentials;
|
||||
import org.thingsboard.server.msa.AbstractContainerTest;
|
||||
import org.thingsboard.server.msa.WsClient;
|
||||
import org.thingsboard.server.msa.WsTelemetryResponse;
|
||||
import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -35,23 +36,24 @@ public class HttpClientTest extends AbstractContainerTest {
|
||||
Device device = createDevice("http_");
|
||||
DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId());
|
||||
|
||||
WsClient mWs = subscribeToTelemetryWebSocket(device.getId());
|
||||
WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS);
|
||||
ResponseEntity deviceTelemetryResponse = restClient.getRestTemplate()
|
||||
.postForEntity(httpUrl + "/api/v1/{credentialsId}/telemetry",
|
||||
.postForEntity(HTTPS_URL + "/api/v1/{credentialsId}/telemetry",
|
||||
mapper.readTree(createPayload().toString()),
|
||||
ResponseEntity.class,
|
||||
deviceCredentials.getCredentialsId());
|
||||
Assert.assertTrue(deviceTelemetryResponse.getStatusCode().is2xxSuccessful());
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
WsTelemetryResponse actualLatestTelemetry = mapper.readValue(mWs.getLastMessage(), WsTelemetryResponse.class);
|
||||
WsTelemetryResponse actualLatestTelemetry = mapper.readValue(wsClient.getLastMessage(), WsTelemetryResponse.class);
|
||||
|
||||
Assert.assertEquals(getExpectedLatestValues(123456789L).keySet(), actualLatestTelemetry.getLatestValues().keySet());
|
||||
Assert.assertEquals(Sets.newHashSet("booleanKey", "stringKey", "doubleKey", "longKey"),
|
||||
actualLatestTelemetry.getLatestValues().keySet());
|
||||
|
||||
Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", Boolean.TRUE.toString()));
|
||||
Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", "value1"));
|
||||
Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0)));
|
||||
Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73)));
|
||||
|
||||
restClient.getRestTemplate().delete(httpUrl + "/api/device/" + device.getId());
|
||||
restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,20 +15,39 @@
|
||||
*/
|
||||
package org.thingsboard.server.msa.connectivity;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.junit.*;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.thingsboard.mqtt.MqttClient;
|
||||
import org.thingsboard.mqtt.MqttClientConfig;
|
||||
import org.thingsboard.mqtt.MqttHandler;
|
||||
import org.thingsboard.server.common.data.Device;
|
||||
import org.thingsboard.server.common.data.id.RuleChainId;
|
||||
import org.thingsboard.server.common.data.page.TextPageData;
|
||||
import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
|
||||
import org.thingsboard.server.common.data.rule.RuleChain;
|
||||
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
|
||||
import org.thingsboard.server.common.data.rule.RuleNode;
|
||||
import org.thingsboard.server.common.data.security.DeviceCredentials;
|
||||
import org.thingsboard.server.msa.AbstractContainerTest;
|
||||
import org.thingsboard.server.msa.WsClient;
|
||||
import org.thingsboard.server.msa.WsTelemetryResponse;
|
||||
import org.thingsboard.server.msa.mapper.AttributesResponse;
|
||||
import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class MqttClientTest extends AbstractContainerTest {
|
||||
@ -39,20 +58,22 @@ public class MqttClientTest extends AbstractContainerTest {
|
||||
Device device = createDevice("mqtt_");
|
||||
DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId());
|
||||
|
||||
WsClient mWs = subscribeToTelemetryWebSocket(device.getId());
|
||||
MqttClient mqttClient = getMqttClient(deviceCredentials);
|
||||
WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS);
|
||||
MqttClient mqttClient = getMqttClient(deviceCredentials, null);
|
||||
mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload().toString().getBytes()));
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
WsTelemetryResponse actualLatestTelemetry = mapper.readValue(mWs.getLastMessage(), WsTelemetryResponse.class);
|
||||
WsTelemetryResponse actualLatestTelemetry = mapper.readValue(wsClient.getLastMessage(), WsTelemetryResponse.class);
|
||||
|
||||
Assert.assertEquals(getExpectedLatestValues(123456789L).keySet(), actualLatestTelemetry.getLatestValues().keySet());
|
||||
Assert.assertEquals(4, actualLatestTelemetry.getData().size());
|
||||
Assert.assertEquals(Sets.newHashSet("booleanKey", "stringKey", "doubleKey", "longKey"),
|
||||
actualLatestTelemetry.getLatestValues().keySet());
|
||||
|
||||
Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", Boolean.TRUE.toString()));
|
||||
Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", "value1"));
|
||||
Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0)));
|
||||
Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73)));
|
||||
|
||||
restClient.getRestTemplate().delete(httpUrl + "/api/device/" + device.getId());
|
||||
restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -63,12 +84,13 @@ public class MqttClientTest extends AbstractContainerTest {
|
||||
Device device = createDevice("mqtt_");
|
||||
DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId());
|
||||
|
||||
WsClient mWs = subscribeToTelemetryWebSocket(device.getId());
|
||||
MqttClient mqttClient = getMqttClient(deviceCredentials);
|
||||
WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS);
|
||||
MqttClient mqttClient = getMqttClient(deviceCredentials, null);
|
||||
mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload(ts).toString().getBytes()));
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
WsTelemetryResponse actualLatestTelemetry = mapper.readValue(mWs.getLastMessage(), WsTelemetryResponse.class);
|
||||
WsTelemetryResponse actualLatestTelemetry = mapper.readValue(wsClient.getLastMessage(), WsTelemetryResponse.class);
|
||||
|
||||
Assert.assertEquals(4, actualLatestTelemetry.getData().size());
|
||||
Assert.assertEquals(getExpectedLatestValues(ts), actualLatestTelemetry.getLatestValues());
|
||||
|
||||
Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", ts, Boolean.TRUE.toString()));
|
||||
@ -76,15 +98,271 @@ public class MqttClientTest extends AbstractContainerTest {
|
||||
Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", ts, Double.toString(42.0)));
|
||||
Assert.assertTrue(verify(actualLatestTelemetry, "longKey", ts, Long.toString(73)));
|
||||
|
||||
restClient.getRestTemplate().delete(httpUrl + "/api/device/" + device.getId());
|
||||
restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId());
|
||||
}
|
||||
|
||||
private MqttClient getMqttClient(DeviceCredentials deviceCredentials) throws InterruptedException {
|
||||
MqttMessageListener queue = new MqttMessageListener();
|
||||
@Test
|
||||
public void publishAttributeUpdateToServer() throws Exception {
|
||||
restClient.login("tenant@thingsboard.org", "tenant");
|
||||
Device device = createDevice("mqtt_");
|
||||
DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId());
|
||||
|
||||
WsClient wsClient = subscribeToWebSocket(device.getId(), "CLIENT_SCOPE", CmdsType.ATTR_SUB_CMDS);
|
||||
MqttMessageListener listener = new MqttMessageListener();
|
||||
MqttClient mqttClient = getMqttClient(deviceCredentials, listener);
|
||||
JsonObject clientAttributes = new JsonObject();
|
||||
clientAttributes.addProperty("attr1", "value1");
|
||||
clientAttributes.addProperty("attr2", true);
|
||||
clientAttributes.addProperty("attr3", 42.0);
|
||||
clientAttributes.addProperty("attr4", 73);
|
||||
mqttClient.publish("v1/devices/me/attributes", Unpooled.wrappedBuffer(clientAttributes.toString().getBytes()));
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
WsTelemetryResponse actualLatestTelemetry = mapper.readValue(wsClient.getLastMessage(), WsTelemetryResponse.class);
|
||||
|
||||
Assert.assertEquals(4, actualLatestTelemetry.getData().size());
|
||||
Assert.assertEquals(Sets.newHashSet("attr1", "attr2", "attr3", "attr4"),
|
||||
actualLatestTelemetry.getLatestValues().keySet());
|
||||
|
||||
Assert.assertTrue(verify(actualLatestTelemetry, "attr1", "value1"));
|
||||
Assert.assertTrue(verify(actualLatestTelemetry, "attr2", Boolean.TRUE.toString()));
|
||||
Assert.assertTrue(verify(actualLatestTelemetry, "attr3", Double.toString(42.0)));
|
||||
Assert.assertTrue(verify(actualLatestTelemetry, "attr4", Long.toString(73)));
|
||||
|
||||
restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestAttributeValuesFromServer() throws Exception {
|
||||
restClient.login("tenant@thingsboard.org", "tenant");
|
||||
Device device = createDevice("mqtt_");
|
||||
DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId());
|
||||
|
||||
MqttMessageListener listener = new MqttMessageListener();
|
||||
MqttClient mqttClient = getMqttClient(deviceCredentials, listener);
|
||||
|
||||
// Add a new client attribute
|
||||
JsonObject clientAttributes = new JsonObject();
|
||||
String clientAttributeValue = RandomStringUtils.randomAlphanumeric(8);
|
||||
clientAttributes.addProperty("clientAttr", clientAttributeValue);
|
||||
mqttClient.publish("v1/devices/me/attributes", Unpooled.wrappedBuffer(clientAttributes.toString().getBytes()));
|
||||
|
||||
// Add a new shared attribute
|
||||
JsonObject sharedAttributes = new JsonObject();
|
||||
String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8);
|
||||
sharedAttributes.addProperty("sharedAttr", sharedAttributeValue);
|
||||
ResponseEntity sharedAttributesResponse = restClient.getRestTemplate()
|
||||
.postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE",
|
||||
mapper.readTree(sharedAttributes.toString()), ResponseEntity.class,
|
||||
device.getId());
|
||||
Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful());
|
||||
|
||||
// Subscribe to attributes response
|
||||
mqttClient.on("v1/devices/me/attributes/response/+", listener);
|
||||
// Request attributes
|
||||
JsonObject request = new JsonObject();
|
||||
request.addProperty("clientKeys", "clientAttr");
|
||||
request.addProperty("sharedKeys", "sharedAttr");
|
||||
mqttClient.publish("v1/devices/me/attributes/request/" + new Random().nextInt(100), Unpooled.wrappedBuffer(request.toString().getBytes()));
|
||||
MqttEvent event = listener.getEvents().poll(10, TimeUnit.SECONDS);
|
||||
AttributesResponse attributes = mapper.readValue(Objects.requireNonNull(event).getMessage(), AttributesResponse.class);
|
||||
|
||||
Assert.assertEquals(1, attributes.getClient().size());
|
||||
Assert.assertEquals(clientAttributeValue, attributes.getClient().get("clientAttr"));
|
||||
|
||||
Assert.assertEquals(1, attributes.getShared().size());
|
||||
Assert.assertEquals(sharedAttributeValue, attributes.getShared().get("sharedAttr"));
|
||||
|
||||
restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subscribeToAttributeUpdatesFromServer() throws Exception {
|
||||
restClient.login("tenant@thingsboard.org", "tenant");
|
||||
Device device = createDevice("mqtt_");
|
||||
DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId());
|
||||
|
||||
MqttMessageListener listener = new MqttMessageListener();
|
||||
MqttClient mqttClient = getMqttClient(deviceCredentials, listener);
|
||||
mqttClient.on("v1/devices/me/attributes", listener);
|
||||
|
||||
String sharedAttributeName = "sharedAttr";
|
||||
|
||||
// Add a new shared attribute
|
||||
JsonObject sharedAttributes = new JsonObject();
|
||||
String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8);
|
||||
sharedAttributes.addProperty(sharedAttributeName, sharedAttributeValue);
|
||||
ResponseEntity sharedAttributesResponse = restClient.getRestTemplate()
|
||||
.postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE",
|
||||
mapper.readTree(sharedAttributes.toString()), ResponseEntity.class,
|
||||
device.getId());
|
||||
Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful());
|
||||
|
||||
MqttEvent event = listener.getEvents().poll(10, TimeUnit.SECONDS);
|
||||
Assert.assertEquals(sharedAttributeValue,
|
||||
mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText());
|
||||
|
||||
// Update the shared attribute value
|
||||
JsonObject updatedSharedAttributes = new JsonObject();
|
||||
String updatedSharedAttributeValue = RandomStringUtils.randomAlphanumeric(8);
|
||||
updatedSharedAttributes.addProperty(sharedAttributeName, updatedSharedAttributeValue);
|
||||
ResponseEntity updatedSharedAttributesResponse = restClient.getRestTemplate()
|
||||
.postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE",
|
||||
mapper.readTree(updatedSharedAttributes.toString()), ResponseEntity.class,
|
||||
device.getId());
|
||||
Assert.assertTrue(updatedSharedAttributesResponse.getStatusCode().is2xxSuccessful());
|
||||
|
||||
event = listener.getEvents().poll(10, TimeUnit.SECONDS);
|
||||
Assert.assertEquals(updatedSharedAttributeValue,
|
||||
mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText());
|
||||
|
||||
restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverSideRpc() throws Exception {
|
||||
restClient.login("tenant@thingsboard.org", "tenant");
|
||||
Device device = createDevice("mqtt_");
|
||||
DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId());
|
||||
|
||||
MqttMessageListener listener = new MqttMessageListener();
|
||||
MqttClient mqttClient = getMqttClient(deviceCredentials, listener);
|
||||
mqttClient.on("v1/devices/me/rpc/request/+", listener);
|
||||
|
||||
// Send an RPC from the server
|
||||
JsonObject serverRpcPayload = new JsonObject();
|
||||
serverRpcPayload.addProperty("method", "getValue");
|
||||
serverRpcPayload.addProperty("params", true);
|
||||
serverRpcPayload.addProperty("timeout", 1000);
|
||||
ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
|
||||
ListenableFuture<ResponseEntity> future = service.submit(() -> {
|
||||
try {
|
||||
return restClient.getRestTemplate()
|
||||
.postForEntity(HTTPS_URL + "/api/plugins/rpc/twoway/{deviceId}",
|
||||
mapper.readTree(serverRpcPayload.toString()), String.class,
|
||||
device.getId());
|
||||
} catch (IOException e) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for RPC call from the server and send the response
|
||||
MqttEvent requestFromServer = listener.getEvents().poll(10, TimeUnit.SECONDS);
|
||||
|
||||
Assert.assertEquals("{\"method\":\"getValue\",\"params\":true}", Objects.requireNonNull(requestFromServer).getMessage());
|
||||
|
||||
Integer requestId = Integer.valueOf(Objects.requireNonNull(requestFromServer).getTopic().substring("v1/devices/me/rpc/request/".length()));
|
||||
JsonObject clientResponse = new JsonObject();
|
||||
clientResponse.addProperty("response", "someResponse");
|
||||
// Send a response to the server's RPC request
|
||||
mqttClient.publish("v1/devices/me/rpc/response/" + requestId, Unpooled.wrappedBuffer(clientResponse.toString().getBytes()));
|
||||
|
||||
ResponseEntity serverResponse = future.get(5, TimeUnit.SECONDS);
|
||||
Assert.assertTrue(serverResponse.getStatusCode().is2xxSuccessful());
|
||||
Assert.assertEquals(clientResponse.toString(), serverResponse.getBody());
|
||||
|
||||
restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clientSideRpc() throws Exception {
|
||||
restClient.login("tenant@thingsboard.org", "tenant");
|
||||
Device device = createDevice("mqtt_");
|
||||
DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId());
|
||||
|
||||
MqttMessageListener listener = new MqttMessageListener();
|
||||
MqttClient mqttClient = getMqttClient(deviceCredentials, listener);
|
||||
mqttClient.on("v1/devices/me/rpc/request/+", listener);
|
||||
|
||||
// Get the default rule chain id to make it root again after test finished
|
||||
RuleChainId defaultRuleChainId = getDefaultRuleChainId();
|
||||
|
||||
// Create a new root rule chain
|
||||
RuleChainId ruleChainId = createRootRuleChainForRpcResponse();
|
||||
|
||||
// Send the request to the server
|
||||
JsonObject clientRequest = new JsonObject();
|
||||
clientRequest.addProperty("method", "getResponse");
|
||||
clientRequest.addProperty("params", true);
|
||||
Integer requestId = 42;
|
||||
mqttClient.publish("v1/devices/me/rpc/request/" + requestId, Unpooled.wrappedBuffer(clientRequest.toString().getBytes()));
|
||||
|
||||
// Check the response from the server
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
MqttEvent responseFromServer = listener.getEvents().poll(1, TimeUnit.SECONDS);
|
||||
Integer responseId = Integer.valueOf(Objects.requireNonNull(responseFromServer).getTopic().substring("v1/devices/me/rpc/response/".length()));
|
||||
Assert.assertEquals(requestId, responseId);
|
||||
Assert.assertEquals("requestReceived", mapper.readTree(responseFromServer.getMessage()).get("response").asText());
|
||||
|
||||
// Make the default rule chain a root again
|
||||
ResponseEntity<RuleChain> rootRuleChainResponse = restClient.getRestTemplate()
|
||||
.postForEntity(HTTPS_URL + "/api/ruleChain/{ruleChainId}/root",
|
||||
null,
|
||||
RuleChain.class,
|
||||
defaultRuleChainId);
|
||||
Assert.assertTrue(rootRuleChainResponse.getStatusCode().is2xxSuccessful());
|
||||
|
||||
// Delete the created rule chain
|
||||
restClient.getRestTemplate().delete(HTTPS_URL + "/api/ruleChain/{ruleChainId}", ruleChainId);
|
||||
restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId());
|
||||
}
|
||||
|
||||
private RuleChainId createRootRuleChainForRpcResponse() throws Exception {
|
||||
RuleChain newRuleChain = new RuleChain();
|
||||
newRuleChain.setName("testRuleChain");
|
||||
ResponseEntity<RuleChain> ruleChainResponse = restClient.getRestTemplate()
|
||||
.postForEntity(HTTPS_URL + "/api/ruleChain",
|
||||
newRuleChain,
|
||||
RuleChain.class);
|
||||
Assert.assertTrue(ruleChainResponse.getStatusCode().is2xxSuccessful());
|
||||
RuleChain ruleChain = ruleChainResponse.getBody();
|
||||
|
||||
JsonNode configuration = mapper.readTree(this.getClass().getClassLoader().getResourceAsStream("RpcResponseRuleChainMetadata.json"));
|
||||
RuleChainMetaData ruleChainMetaData = new RuleChainMetaData();
|
||||
ruleChainMetaData.setRuleChainId(ruleChain.getId());
|
||||
ruleChainMetaData.setFirstNodeIndex(configuration.get("firstNodeIndex").asInt());
|
||||
ruleChainMetaData.setNodes(Arrays.asList(mapper.treeToValue(configuration.get("nodes"), RuleNode[].class)));
|
||||
ruleChainMetaData.setConnections(Arrays.asList(mapper.treeToValue(configuration.get("connections"), NodeConnectionInfo[].class)));
|
||||
|
||||
ResponseEntity<RuleChainMetaData> ruleChainMetadataResponse = restClient.getRestTemplate()
|
||||
.postForEntity(HTTPS_URL + "/api/ruleChain/metadata",
|
||||
ruleChainMetaData,
|
||||
RuleChainMetaData.class);
|
||||
Assert.assertTrue(ruleChainMetadataResponse.getStatusCode().is2xxSuccessful());
|
||||
|
||||
// Set a new rule chain as root
|
||||
ResponseEntity<RuleChain> rootRuleChainResponse = restClient.getRestTemplate()
|
||||
.postForEntity(HTTPS_URL + "/api/ruleChain/{ruleChainId}/root",
|
||||
null,
|
||||
RuleChain.class,
|
||||
ruleChain.getId());
|
||||
Assert.assertTrue(rootRuleChainResponse.getStatusCode().is2xxSuccessful());
|
||||
|
||||
return ruleChain.getId();
|
||||
}
|
||||
|
||||
private RuleChainId getDefaultRuleChainId() {
|
||||
ResponseEntity<TextPageData<RuleChain>> ruleChains = restClient.getRestTemplate().exchange(
|
||||
HTTPS_URL + "/api/ruleChains?limit=40&textSearch=",
|
||||
HttpMethod.GET,
|
||||
null,
|
||||
new ParameterizedTypeReference<TextPageData<RuleChain>>() {
|
||||
});
|
||||
|
||||
Optional<RuleChain> defaultRuleChain = ruleChains.getBody().getData()
|
||||
.stream()
|
||||
.filter(RuleChain::isRoot)
|
||||
.findFirst();
|
||||
if (!defaultRuleChain.isPresent()) {
|
||||
Assert.fail("Root rule chain wasn't found");
|
||||
}
|
||||
return defaultRuleChain.get().getId();
|
||||
}
|
||||
|
||||
private MqttClient getMqttClient(DeviceCredentials deviceCredentials, MqttMessageListener listener) throws InterruptedException {
|
||||
MqttClientConfig clientConfig = new MqttClientConfig();
|
||||
clientConfig.setClientId("MQTT client from test");
|
||||
clientConfig.setUsername(deviceCredentials.getCredentialsId());
|
||||
MqttClient mqttClient = MqttClient.create(clientConfig, queue);
|
||||
MqttClient mqttClient = MqttClient.create(clientConfig, listener);
|
||||
mqttClient.connect("localhost", 1883).sync();
|
||||
return mqttClient;
|
||||
}
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright © 2016-2018 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thingsboard.server.msa.mapper;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class AttributesResponse {
|
||||
private Map<String, Object> client;
|
||||
private Map<String, Object> shared;
|
||||
}
|
||||
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thingsboard.server.msa;
|
||||
package org.thingsboard.server.msa.mapper;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
{
|
||||
"firstNodeIndex": 0,
|
||||
"nodes": [
|
||||
{
|
||||
"additionalInfo": {
|
||||
"layoutX": 325,
|
||||
"layoutY": 150
|
||||
},
|
||||
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
|
||||
"name": "msgTypeSwitch",
|
||||
"debugMode": true,
|
||||
"configuration": {
|
||||
"version": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"additionalInfo": {
|
||||
"layoutX": 60,
|
||||
"layoutY": 300
|
||||
},
|
||||
"type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode",
|
||||
"name": "formResponse",
|
||||
"debugMode": true,
|
||||
"configuration": {
|
||||
"jsScript": "if (msg.method == \"getResponse\") {\n return {msg: {\"response\": \"requestReceived\"}, metadata: metadata, msgType: msgType};\n}\n\nreturn {msg: msg, metadata: metadata, msgType: msgType};"
|
||||
}
|
||||
},
|
||||
{
|
||||
"additionalInfo": {
|
||||
"layoutX": 450,
|
||||
"layoutY": 300
|
||||
},
|
||||
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCReplyNode",
|
||||
"name": "rpcReply",
|
||||
"debugMode": true,
|
||||
"configuration": {
|
||||
"requestIdMetaDataAttribute": "requestId"
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"fromIndex": 0,
|
||||
"toIndex": 1,
|
||||
"type": "RPC Request from Device"
|
||||
},
|
||||
{
|
||||
"fromIndex": 1,
|
||||
"toIndex": 2,
|
||||
"type": "Success"
|
||||
},
|
||||
{
|
||||
"fromIndex": 1,
|
||||
"toIndex": 2,
|
||||
"type": "Failure"
|
||||
}
|
||||
],
|
||||
"ruleChainConnections": null
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user