Integration-tests subproject

This commit is contained in:
Viacheslav Kukhtyn 2018-10-16 14:05:52 +03:00
parent 0f14a36cfe
commit 4b0554b274
9 changed files with 531 additions and 0 deletions

View File

@ -0,0 +1,18 @@
## Integration tests execution
To run the integration tests with using Docker, the local Docker images of Thingsboard's microservices should be built. <br />
- Build the local Docker images in the directory with the Thingsboard's main [pom.xml](./../../pom.xml):
mvn clean install -Ddockerfile.skip=false
- Verify that the new local images were built:
docker image ls
As result, in REPOSITORY column, next images should be present:
local-maven-build/tb-node
local-maven-build/tb-web-ui
local-maven-build/tb-web-ui
- Run the integration tests in the [msa/integration-tests](../integration-tests) directory:
mvn clean install -Dintegrationtests.skip=false

View File

@ -0,0 +1,98 @@
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.2.0-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>
<artifactId>integration-tests</artifactId>
<name>ThingsBoard Integration Tests</name>
<url>https://thingsboard.io</url>
<description>Project for ThingsBoard integration tests with using Docker</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/../..</main.dir>
<integrationtests.skip>true</integrationtests.skip>
<testcontainers.version>1.9.1</testcontainers.version>
<java-websocket.version>1.3.9</java-websocket.version>
</properties>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>${testcontainers.version}</version>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>${java-websocket.version}</version>
</dependency>
<dependency>
<groupId>io.takari.junit</groupId>
<artifactId>takari-cpsuite</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard</groupId>
<artifactId>netty-mqtt</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard</groupId>
<artifactId>tools</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*TestSuite.java</include>
</includes>
<skipTests>${integrationtests.skip}</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,112 @@
/**
* 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;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.*;
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 java.net.URI;
import java.net.URISyntaxException;
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 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);
}
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);
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 wsRequest = new JsonObject();
wsRequest.add("tsSubCmds", wsTsSubCmds);
wsRequest.add("historyCmds", new JsonArray());
wsRequest.add("attrSubCmds", new JsonArray());
mWs.send(wsRequest.toString());
return mWs;
}
protected Map<String, Long> getExpectedLatestValues(long ts) {
return ImmutableMap.<String, Long>builder()
.put("booleanKey", ts)
.put("stringKey", ts)
.put("doubleKey", ts)
.put("longKey", ts)
.build();
}
protected boolean verify(WsTelemetryResponse wsTelemetryResponse, String key, Long expectedTs, String expectedValue) {
List<Object> list = wsTelemetryResponse.getDataValuesByKey(key);
return expectedTs.equals(list.get(0)) && expectedValue.equals(list.get(1));
}
protected boolean verify(WsTelemetryResponse wsTelemetryResponse, String key, String expectedValue) {
List<Object> list = wsTelemetryResponse.getDataValuesByKey(key);
return expectedValue.equals(list.get(1));
}
protected JsonObject createPayload(long ts) {
JsonObject values = createPayload();
JsonObject payload = new JsonObject();
payload.addProperty("ts", ts);
payload.add("values", values);
return payload;
}
protected JsonObject createPayload() {
JsonObject values = new JsonObject();
values.addProperty("stringKey", "value1");
values.addProperty("booleanKey", true);
values.addProperty("doubleKey", 42.0);
values.addProperty("longKey", 73L);
return values;
}
}

View File

@ -0,0 +1,37 @@
/**
* 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;
import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
import org.testcontainers.containers.DockerComposeContainer;
import org.testcontainers.containers.wait.strategy.Wait;
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"));
}

View File

@ -0,0 +1,57 @@
/**
* 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;
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
public void onOpen(ServerHandshake serverHandshake) {
}
@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
public void onError(Exception ex) {
ex.printStackTrace();
}
public String getLastMessage() {
return this.message;
}
}

View File

@ -0,0 +1,40 @@
/**
* 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;
import lombok.Data;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Data
public class WsTelemetryResponse implements Serializable {
private int subscriptionId;
private int errorCode;
private String errorMsg;
private Map<String, List<List<Object>>> data;
private Map<String, Object> latestValues;
public List<Object> getDataValuesByKey(String key) {
return data.entrySet().stream()
.filter(e -> e.getKey().equals(key))
.flatMap(e -> e.getValue().stream().flatMap(Collection::stream))
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,57 @@
/**
* 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.connectivity;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.http.ResponseEntity;
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 java.util.concurrent.TimeUnit;
public class HttpClientTest extends AbstractContainerTest {
@Test
public void telemetryUpdate() throws Exception {
restClient.login("tenant@thingsboard.org", "tenant");
Device device = createDevice("http_");
DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId());
WsClient mWs = subscribeToTelemetryWebSocket(device.getId());
ResponseEntity deviceTelemetryResponse = restClient.getRestTemplate()
.postForEntity(httpUrl + "/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);
Assert.assertEquals(getExpectedLatestValues(123456789L).keySet(), 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());
}
}

View File

@ -0,0 +1,111 @@
/**
* 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.connectivity;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.Data;
import org.junit.*;
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.security.DeviceCredentials;
import org.thingsboard.server.msa.AbstractContainerTest;
import org.thingsboard.server.msa.WsClient;
import org.thingsboard.server.msa.WsTelemetryResponse;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.*;
public class MqttClientTest extends AbstractContainerTest {
@Test
public void telemetryUpload() throws Exception {
restClient.login("tenant@thingsboard.org", "tenant");
Device device = createDevice("mqtt_");
DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId());
WsClient mWs = subscribeToTelemetryWebSocket(device.getId());
MqttClient mqttClient = getMqttClient(deviceCredentials);
mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload().toString().getBytes()));
TimeUnit.SECONDS.sleep(1);
WsTelemetryResponse actualLatestTelemetry = mapper.readValue(mWs.getLastMessage(), WsTelemetryResponse.class);
Assert.assertEquals(getExpectedLatestValues(123456789L).keySet(), 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());
}
@Test
public void telemetryUploadWithTs() throws Exception {
long ts = 1451649600512L;
restClient.login("tenant@thingsboard.org", "tenant");
Device device = createDevice("mqtt_");
DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId());
WsClient mWs = subscribeToTelemetryWebSocket(device.getId());
MqttClient mqttClient = getMqttClient(deviceCredentials);
mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload(ts).toString().getBytes()));
TimeUnit.SECONDS.sleep(1);
WsTelemetryResponse actualLatestTelemetry = mapper.readValue(mWs.getLastMessage(), WsTelemetryResponse.class);
Assert.assertEquals(getExpectedLatestValues(ts), actualLatestTelemetry.getLatestValues());
Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", ts, Boolean.TRUE.toString()));
Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", ts, "value1"));
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());
}
private MqttClient getMqttClient(DeviceCredentials deviceCredentials) throws InterruptedException {
MqttMessageListener queue = new MqttMessageListener();
MqttClientConfig clientConfig = new MqttClientConfig();
clientConfig.setClientId("MQTT client from test");
clientConfig.setUsername(deviceCredentials.getCredentialsId());
MqttClient mqttClient = MqttClient.create(clientConfig, queue);
mqttClient.connect("localhost", 1883).sync();
return mqttClient;
}
@Data
private class MqttMessageListener implements MqttHandler {
private final BlockingQueue<MqttEvent> events;
private MqttMessageListener() {
events = new ArrayBlockingQueue<>(100);
}
@Override
public void onMessage(String topic, ByteBuf message) {
events.add(new MqttEvent(topic, message.toString(StandardCharsets.UTF_8)));
}
}
@Data
private class MqttEvent {
private final String topic;
private final String message;
}
}

View File

@ -40,6 +40,7 @@
<module>js-executor</module> <module>js-executor</module>
<module>web-ui</module> <module>web-ui</module>
<module>tb-node</module> <module>tb-node</module>
<module>integration-tests</module>
</modules> </modules>
<build> <build>