Commit 000741444a0160639d002a33ad516eea8ff93aa3
Committed by
GitHub
Merge pull request #1183 from vkukhtyn/master
Cover MQTT API with black box tests
Showing
11 changed files
with
991 additions
and
1 deletions
msa/black-box-tests/README.md
0 → 100644
1 | + | ||
2 | +## Black box tests execution | ||
3 | +To run the black box tests with using Docker, the local Docker images of Thingsboard's microservices should be built. <br /> | ||
4 | +- Build the local Docker images in the directory with the Thingsboard's main [pom.xml](./../../pom.xml): | ||
5 | + | ||
6 | + mvn clean install -Ddockerfile.skip=false | ||
7 | +- Verify that the new local images were built: | ||
8 | + | ||
9 | + docker image ls | ||
10 | +As result, in REPOSITORY column, next images should be present: | ||
11 | + | ||
12 | + thingsboard/tb-coap-transport | ||
13 | + thingsboard/tb-http-transport | ||
14 | + thingsboard/tb-mqtt-transport | ||
15 | + thingsboard/tb-node | ||
16 | + thingsboard/tb-web-ui | ||
17 | + thingsboard/tb-js-executor | ||
18 | + | ||
19 | +- Run the black box tests in the [msa/black-box-tests](../black-box-tests) directory: | ||
20 | + | ||
21 | + mvn clean install -DblackBoxTests.skip=false |
msa/black-box-tests/pom.xml
0 → 100644
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2018 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
19 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
20 | + <modelVersion>4.0.0</modelVersion> | ||
21 | + | ||
22 | + <parent> | ||
23 | + <groupId>org.thingsboard</groupId> | ||
24 | + <version>2.2.0-SNAPSHOT</version> | ||
25 | + <artifactId>msa</artifactId> | ||
26 | + </parent> | ||
27 | + <groupId>org.thingsboard.msa</groupId> | ||
28 | + <artifactId>black-box-tests</artifactId> | ||
29 | + | ||
30 | + <name>ThingsBoard Black Box Tests</name> | ||
31 | + <url>https://thingsboard.io</url> | ||
32 | + <description>Project for ThingsBoard black box testing with using Docker</description> | ||
33 | + | ||
34 | + <properties> | ||
35 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
36 | + <main.dir>${basedir}/../..</main.dir> | ||
37 | + <blackBoxTests.skip>true</blackBoxTests.skip> | ||
38 | + <testcontainers.version>1.9.1</testcontainers.version> | ||
39 | + <java-websocket.version>1.3.9</java-websocket.version> | ||
40 | + <httpclient.version>4.5.6</httpclient.version> | ||
41 | + </properties> | ||
42 | + | ||
43 | + <dependencies> | ||
44 | + <dependency> | ||
45 | + <groupId>org.testcontainers</groupId> | ||
46 | + <artifactId>testcontainers</artifactId> | ||
47 | + <version>${testcontainers.version}</version> | ||
48 | + </dependency> | ||
49 | + <dependency> | ||
50 | + <groupId>org.java-websocket</groupId> | ||
51 | + <artifactId>Java-WebSocket</artifactId> | ||
52 | + <version>${java-websocket.version}</version> | ||
53 | + </dependency> | ||
54 | + <dependency> | ||
55 | + <groupId>org.apache.httpcomponents</groupId> | ||
56 | + <artifactId>httpclient</artifactId> | ||
57 | + <version>${httpclient.version}</version> | ||
58 | + </dependency> | ||
59 | + <dependency> | ||
60 | + <groupId>io.takari.junit</groupId> | ||
61 | + <artifactId>takari-cpsuite</artifactId> | ||
62 | + </dependency> | ||
63 | + <dependency> | ||
64 | + <groupId>ch.qos.logback</groupId> | ||
65 | + <artifactId>logback-classic</artifactId> | ||
66 | + </dependency> | ||
67 | + <dependency> | ||
68 | + <groupId>com.google.code.gson</groupId> | ||
69 | + <artifactId>gson</artifactId> | ||
70 | + </dependency> | ||
71 | + <dependency> | ||
72 | + <groupId>org.apache.commons</groupId> | ||
73 | + <artifactId>commons-lang3</artifactId> | ||
74 | + </dependency> | ||
75 | + <dependency> | ||
76 | + <groupId>com.google.guava</groupId> | ||
77 | + <artifactId>guava</artifactId> | ||
78 | + </dependency> | ||
79 | + <dependency> | ||
80 | + <groupId>org.thingsboard</groupId> | ||
81 | + <artifactId>netty-mqtt</artifactId> | ||
82 | + </dependency> | ||
83 | + <dependency> | ||
84 | + <groupId>org.thingsboard</groupId> | ||
85 | + <artifactId>tools</artifactId> | ||
86 | + </dependency> | ||
87 | + </dependencies> | ||
88 | + | ||
89 | + <build> | ||
90 | + <plugins> | ||
91 | + <plugin> | ||
92 | + <groupId>org.apache.maven.plugins</groupId> | ||
93 | + <artifactId>maven-surefire-plugin</artifactId> | ||
94 | + <configuration> | ||
95 | + <includes> | ||
96 | + <include>**/*TestSuite.java</include> | ||
97 | + </includes> | ||
98 | + <skipTests>${blackBoxTests.skip}</skipTests> | ||
99 | + </configuration> | ||
100 | + </plugin> | ||
101 | + </plugins> | ||
102 | + </build> | ||
103 | + | ||
104 | +</project> |
1 | +/** | ||
2 | + * Copyright © 2016-2018 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.msa; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
19 | +import com.google.common.collect.ImmutableMap; | ||
20 | +import com.google.gson.JsonArray; | ||
21 | +import com.google.gson.JsonObject; | ||
22 | +import lombok.extern.slf4j.Slf4j; | ||
23 | +import org.apache.commons.lang3.RandomStringUtils; | ||
24 | +import org.apache.http.config.Registry; | ||
25 | +import org.apache.http.config.RegistryBuilder; | ||
26 | +import org.apache.http.conn.socket.ConnectionSocketFactory; | ||
27 | +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; | ||
28 | +import org.apache.http.conn.ssl.TrustStrategy; | ||
29 | +import org.apache.http.conn.ssl.X509HostnameVerifier; | ||
30 | +import org.apache.http.impl.client.CloseableHttpClient; | ||
31 | +import org.apache.http.impl.client.HttpClients; | ||
32 | +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; | ||
33 | +import org.apache.http.ssl.SSLContextBuilder; | ||
34 | +import org.apache.http.ssl.SSLContexts; | ||
35 | +import org.junit.*; | ||
36 | +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; | ||
37 | +import org.thingsboard.client.tools.RestClient; | ||
38 | +import org.thingsboard.server.common.data.Device; | ||
39 | +import org.thingsboard.server.common.data.EntityType; | ||
40 | +import org.thingsboard.server.common.data.id.DeviceId; | ||
41 | +import org.thingsboard.server.msa.mapper.WsTelemetryResponse; | ||
42 | + | ||
43 | +import javax.net.ssl.*; | ||
44 | +import java.net.URI; | ||
45 | +import java.security.cert.X509Certificate; | ||
46 | +import java.util.List; | ||
47 | +import java.util.Map; | ||
48 | +import java.util.Random; | ||
49 | + | ||
50 | +@Slf4j | ||
51 | +public abstract class AbstractContainerTest { | ||
52 | + protected static final String HTTPS_URL = "https://localhost"; | ||
53 | + protected static final String WSS_URL = "wss://localhost"; | ||
54 | + protected static RestClient restClient; | ||
55 | + protected ObjectMapper mapper = new ObjectMapper(); | ||
56 | + | ||
57 | + @BeforeClass | ||
58 | + public static void before() throws Exception { | ||
59 | + restClient = new RestClient(HTTPS_URL); | ||
60 | + restClient.getRestTemplate().setRequestFactory(getRequestFactoryForSelfSignedCert()); | ||
61 | + } | ||
62 | + | ||
63 | + protected Device createDevice(String name) { | ||
64 | + return restClient.createDevice(name + RandomStringUtils.randomAlphanumeric(7), "DEFAULT"); | ||
65 | + } | ||
66 | + | ||
67 | + protected WsClient subscribeToWebSocket(DeviceId deviceId, String scope, CmdsType property) throws Exception { | ||
68 | + WsClient wsClient = new WsClient(new URI(WSS_URL + "/api/ws/plugins/telemetry?token=" + restClient.getToken())); | ||
69 | + SSLContextBuilder builder = SSLContexts.custom(); | ||
70 | + builder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true); | ||
71 | + wsClient.setSocket(builder.build().getSocketFactory().createSocket()); | ||
72 | + wsClient.connectBlocking(); | ||
73 | + | ||
74 | + JsonObject cmdsObject = new JsonObject(); | ||
75 | + cmdsObject.addProperty("entityType", EntityType.DEVICE.name()); | ||
76 | + cmdsObject.addProperty("entityId", deviceId.toString()); | ||
77 | + cmdsObject.addProperty("scope", scope); | ||
78 | + cmdsObject.addProperty("cmdId", new Random().nextInt(100)); | ||
79 | + | ||
80 | + JsonArray cmd = new JsonArray(); | ||
81 | + cmd.add(cmdsObject); | ||
82 | + JsonObject wsRequest = new JsonObject(); | ||
83 | + wsRequest.add(property.toString(), cmd); | ||
84 | + wsClient.send(wsRequest.toString()); | ||
85 | + return wsClient; | ||
86 | + } | ||
87 | + | ||
88 | + protected Map<String, Long> getExpectedLatestValues(long ts) { | ||
89 | + return ImmutableMap.<String, Long>builder() | ||
90 | + .put("booleanKey", ts) | ||
91 | + .put("stringKey", ts) | ||
92 | + .put("doubleKey", ts) | ||
93 | + .put("longKey", ts) | ||
94 | + .build(); | ||
95 | + } | ||
96 | + | ||
97 | + protected boolean verify(WsTelemetryResponse wsTelemetryResponse, String key, Long expectedTs, String expectedValue) { | ||
98 | + List<Object> list = wsTelemetryResponse.getDataValuesByKey(key); | ||
99 | + return expectedTs.equals(list.get(0)) && expectedValue.equals(list.get(1)); | ||
100 | + } | ||
101 | + | ||
102 | + protected boolean verify(WsTelemetryResponse wsTelemetryResponse, String key, String expectedValue) { | ||
103 | + List<Object> list = wsTelemetryResponse.getDataValuesByKey(key); | ||
104 | + return expectedValue.equals(list.get(1)); | ||
105 | + } | ||
106 | + | ||
107 | + protected JsonObject createPayload(long ts) { | ||
108 | + JsonObject values = createPayload(); | ||
109 | + JsonObject payload = new JsonObject(); | ||
110 | + payload.addProperty("ts", ts); | ||
111 | + payload.add("values", values); | ||
112 | + return payload; | ||
113 | + } | ||
114 | + | ||
115 | + protected JsonObject createPayload() { | ||
116 | + JsonObject values = new JsonObject(); | ||
117 | + values.addProperty("stringKey", "value1"); | ||
118 | + values.addProperty("booleanKey", true); | ||
119 | + values.addProperty("doubleKey", 42.0); | ||
120 | + values.addProperty("longKey", 73L); | ||
121 | + | ||
122 | + return values; | ||
123 | + } | ||
124 | + | ||
125 | + protected enum CmdsType { | ||
126 | + TS_SUB_CMDS("tsSubCmds"), | ||
127 | + HISTORY_CMDS("historyCmds"), | ||
128 | + ATTR_SUB_CMDS("attrSubCmds"); | ||
129 | + | ||
130 | + private final String text; | ||
131 | + | ||
132 | + CmdsType(final String text) { | ||
133 | + this.text = text; | ||
134 | + } | ||
135 | + | ||
136 | + @Override | ||
137 | + public String toString() { | ||
138 | + return text; | ||
139 | + } | ||
140 | + } | ||
141 | + | ||
142 | + private static HttpComponentsClientHttpRequestFactory getRequestFactoryForSelfSignedCert() throws Exception { | ||
143 | + SSLContextBuilder builder = SSLContexts.custom(); | ||
144 | + builder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true); | ||
145 | + SSLContext sslContext = builder.build(); | ||
146 | + SSLConnectionSocketFactory sslSelfSigned = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() { | ||
147 | + @Override | ||
148 | + public void verify(String host, SSLSocket ssl) { | ||
149 | + } | ||
150 | + | ||
151 | + @Override | ||
152 | + public void verify(String host, X509Certificate cert) { | ||
153 | + } | ||
154 | + | ||
155 | + @Override | ||
156 | + public void verify(String host, String[] cns, String[] subjectAlts) { | ||
157 | + } | ||
158 | + | ||
159 | + @Override | ||
160 | + public boolean verify(String s, SSLSession sslSession) { | ||
161 | + return true; | ||
162 | + } | ||
163 | + }); | ||
164 | + | ||
165 | + Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder | ||
166 | + .<ConnectionSocketFactory>create() | ||
167 | + .register("https", sslSelfSigned) | ||
168 | + .build(); | ||
169 | + | ||
170 | + PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry); | ||
171 | + CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build(); | ||
172 | + return new HttpComponentsClientHttpRequestFactory(httpClient); | ||
173 | + } | ||
174 | + | ||
175 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2018 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.msa; | ||
17 | + | ||
18 | +import org.junit.ClassRule; | ||
19 | +import org.junit.extensions.cpsuite.ClasspathSuite; | ||
20 | +import org.junit.runner.RunWith; | ||
21 | +import org.testcontainers.containers.DockerComposeContainer; | ||
22 | +import org.testcontainers.containers.wait.strategy.Wait; | ||
23 | + | ||
24 | +import java.io.File; | ||
25 | +import java.time.Duration; | ||
26 | + | ||
27 | +@RunWith(ClasspathSuite.class) | ||
28 | +@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*Test"}) | ||
29 | +public class ContainerTestSuite { | ||
30 | + | ||
31 | + @ClassRule | ||
32 | + public static DockerComposeContainer composeContainer = new DockerComposeContainer( | ||
33 | + new File("./../../docker/docker-compose.yml"), | ||
34 | + new File("./../../docker/docker-compose.postgres.yml")) | ||
35 | + .withPull(false) | ||
36 | + .withLocalCompose(true) | ||
37 | + .withTailChildContainers(true) | ||
38 | + .withExposedService("tb-web-ui1", 8080, Wait.forHttp("/login").withStartupTimeout(Duration.ofSeconds(120))); | ||
39 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2018 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.msa; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
19 | +import lombok.extern.slf4j.Slf4j; | ||
20 | +import org.java_websocket.client.WebSocketClient; | ||
21 | +import org.java_websocket.handshake.ServerHandshake; | ||
22 | +import org.thingsboard.server.msa.mapper.WsTelemetryResponse; | ||
23 | + | ||
24 | +import java.io.IOException; | ||
25 | +import java.net.URI; | ||
26 | +import java.util.concurrent.CountDownLatch; | ||
27 | +import java.util.concurrent.TimeUnit; | ||
28 | + | ||
29 | +@Slf4j | ||
30 | +public class WsClient extends WebSocketClient { | ||
31 | + private static final ObjectMapper mapper = new ObjectMapper(); | ||
32 | + private WsTelemetryResponse message; | ||
33 | + | ||
34 | + private CountDownLatch latch = new CountDownLatch(1);; | ||
35 | + | ||
36 | + public WsClient(URI serverUri) { | ||
37 | + super(serverUri); | ||
38 | + } | ||
39 | + | ||
40 | + @Override | ||
41 | + public void onOpen(ServerHandshake serverHandshake) { | ||
42 | + } | ||
43 | + | ||
44 | + @Override | ||
45 | + public void onMessage(String message) { | ||
46 | + try { | ||
47 | + WsTelemetryResponse response = mapper.readValue(message, WsTelemetryResponse.class); | ||
48 | + if (!response.getData().isEmpty()) { | ||
49 | + this.message = response; | ||
50 | + latch.countDown(); | ||
51 | + } | ||
52 | + } catch (IOException e) { | ||
53 | + log.error("ws message can't be read"); | ||
54 | + } | ||
55 | + } | ||
56 | + | ||
57 | + @Override | ||
58 | + public void onClose(int code, String reason, boolean remote) { | ||
59 | + log.info("ws is closed, due to [{}]", reason); | ||
60 | + } | ||
61 | + | ||
62 | + @Override | ||
63 | + public void onError(Exception ex) { | ||
64 | + ex.printStackTrace(); | ||
65 | + } | ||
66 | + | ||
67 | + public WsTelemetryResponse getLastMessage() { | ||
68 | + try { | ||
69 | + latch.await(10, TimeUnit.SECONDS); | ||
70 | + return this.message; | ||
71 | + } catch (InterruptedException e) { | ||
72 | + log.error("Timeout, ws message wasn't received"); | ||
73 | + } | ||
74 | + return null; | ||
75 | + } | ||
76 | +} |
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2018 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.msa.connectivity; | ||
17 | + | ||
18 | +import com.google.common.collect.Sets; | ||
19 | +import org.junit.Assert; | ||
20 | +import org.junit.Test; | ||
21 | +import org.springframework.http.ResponseEntity; | ||
22 | +import org.thingsboard.server.common.data.Device; | ||
23 | +import org.thingsboard.server.common.data.security.DeviceCredentials; | ||
24 | +import org.thingsboard.server.msa.AbstractContainerTest; | ||
25 | +import org.thingsboard.server.msa.WsClient; | ||
26 | +import org.thingsboard.server.msa.mapper.WsTelemetryResponse; | ||
27 | + | ||
28 | +public class HttpClientTest extends AbstractContainerTest { | ||
29 | + | ||
30 | + @Test | ||
31 | + public void telemetryUpload() throws Exception { | ||
32 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
33 | + | ||
34 | + Device device = createDevice("http_"); | ||
35 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
36 | + | ||
37 | + WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); | ||
38 | + ResponseEntity deviceTelemetryResponse = restClient.getRestTemplate() | ||
39 | + .postForEntity(HTTPS_URL + "/api/v1/{credentialsId}/telemetry", | ||
40 | + mapper.readTree(createPayload().toString()), | ||
41 | + ResponseEntity.class, | ||
42 | + deviceCredentials.getCredentialsId()); | ||
43 | + Assert.assertTrue(deviceTelemetryResponse.getStatusCode().is2xxSuccessful()); | ||
44 | + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); | ||
45 | + wsClient.closeBlocking(); | ||
46 | + | ||
47 | + Assert.assertEquals(Sets.newHashSet("booleanKey", "stringKey", "doubleKey", "longKey"), | ||
48 | + actualLatestTelemetry.getLatestValues().keySet()); | ||
49 | + | ||
50 | + Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", Boolean.TRUE.toString())); | ||
51 | + Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", "value1")); | ||
52 | + Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0))); | ||
53 | + Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73))); | ||
54 | + | ||
55 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
56 | + } | ||
57 | +} |
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2018 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.msa.connectivity; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import com.google.common.collect.Sets; | ||
20 | +import com.google.common.util.concurrent.ListenableFuture; | ||
21 | +import com.google.common.util.concurrent.ListeningExecutorService; | ||
22 | +import com.google.common.util.concurrent.MoreExecutors; | ||
23 | +import com.google.gson.JsonObject; | ||
24 | +import io.netty.buffer.ByteBuf; | ||
25 | +import io.netty.buffer.Unpooled; | ||
26 | +import io.netty.handler.codec.mqtt.MqttQoS; | ||
27 | +import lombok.Data; | ||
28 | +import lombok.extern.slf4j.Slf4j; | ||
29 | +import org.apache.commons.lang3.RandomStringUtils; | ||
30 | +import org.junit.*; | ||
31 | +import org.springframework.core.ParameterizedTypeReference; | ||
32 | +import org.springframework.http.HttpMethod; | ||
33 | +import org.springframework.http.ResponseEntity; | ||
34 | +import org.thingsboard.mqtt.MqttClient; | ||
35 | +import org.thingsboard.mqtt.MqttClientConfig; | ||
36 | +import org.thingsboard.mqtt.MqttHandler; | ||
37 | +import org.thingsboard.server.common.data.Device; | ||
38 | +import org.thingsboard.server.common.data.id.RuleChainId; | ||
39 | +import org.thingsboard.server.common.data.page.TextPageData; | ||
40 | +import org.thingsboard.server.common.data.rule.NodeConnectionInfo; | ||
41 | +import org.thingsboard.server.common.data.rule.RuleChain; | ||
42 | +import org.thingsboard.server.common.data.rule.RuleChainMetaData; | ||
43 | +import org.thingsboard.server.common.data.rule.RuleNode; | ||
44 | +import org.thingsboard.server.common.data.security.DeviceCredentials; | ||
45 | +import org.thingsboard.server.msa.AbstractContainerTest; | ||
46 | +import org.thingsboard.server.msa.WsClient; | ||
47 | +import org.thingsboard.server.msa.mapper.AttributesResponse; | ||
48 | +import org.thingsboard.server.msa.mapper.WsTelemetryResponse; | ||
49 | + | ||
50 | +import java.io.IOException; | ||
51 | +import java.nio.charset.StandardCharsets; | ||
52 | +import java.util.*; | ||
53 | +import java.util.concurrent.*; | ||
54 | + | ||
55 | +@Slf4j | ||
56 | +public class MqttClientTest extends AbstractContainerTest { | ||
57 | + | ||
58 | + @Test | ||
59 | + public void telemetryUpload() throws Exception { | ||
60 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
61 | + Device device = createDevice("mqtt_"); | ||
62 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
63 | + | ||
64 | + WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); | ||
65 | + MqttClient mqttClient = getMqttClient(deviceCredentials, null); | ||
66 | + mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload().toString().getBytes())); | ||
67 | + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); | ||
68 | + wsClient.closeBlocking(); | ||
69 | + | ||
70 | + Assert.assertEquals(4, actualLatestTelemetry.getData().size()); | ||
71 | + Assert.assertEquals(Sets.newHashSet("booleanKey", "stringKey", "doubleKey", "longKey"), | ||
72 | + actualLatestTelemetry.getLatestValues().keySet()); | ||
73 | + | ||
74 | + Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", Boolean.TRUE.toString())); | ||
75 | + Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", "value1")); | ||
76 | + Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0))); | ||
77 | + Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73))); | ||
78 | + | ||
79 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
80 | + } | ||
81 | + | ||
82 | + @Test | ||
83 | + public void telemetryUploadWithTs() throws Exception { | ||
84 | + long ts = 1451649600512L; | ||
85 | + | ||
86 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
87 | + Device device = createDevice("mqtt_"); | ||
88 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
89 | + | ||
90 | + WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); | ||
91 | + MqttClient mqttClient = getMqttClient(deviceCredentials, null); | ||
92 | + mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload(ts).toString().getBytes())); | ||
93 | + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); | ||
94 | + wsClient.closeBlocking(); | ||
95 | + | ||
96 | + Assert.assertEquals(4, actualLatestTelemetry.getData().size()); | ||
97 | + Assert.assertEquals(getExpectedLatestValues(ts), actualLatestTelemetry.getLatestValues()); | ||
98 | + | ||
99 | + Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", ts, Boolean.TRUE.toString())); | ||
100 | + Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", ts, "value1")); | ||
101 | + Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", ts, Double.toString(42.0))); | ||
102 | + Assert.assertTrue(verify(actualLatestTelemetry, "longKey", ts, Long.toString(73))); | ||
103 | + | ||
104 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
105 | + } | ||
106 | + | ||
107 | + @Test | ||
108 | + public void publishAttributeUpdateToServer() throws Exception { | ||
109 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
110 | + Device device = createDevice("mqtt_"); | ||
111 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
112 | + | ||
113 | + WsClient wsClient = subscribeToWebSocket(device.getId(), "CLIENT_SCOPE", CmdsType.ATTR_SUB_CMDS); | ||
114 | + MqttMessageListener listener = new MqttMessageListener(); | ||
115 | + MqttClient mqttClient = getMqttClient(deviceCredentials, listener); | ||
116 | + JsonObject clientAttributes = new JsonObject(); | ||
117 | + clientAttributes.addProperty("attr1", "value1"); | ||
118 | + clientAttributes.addProperty("attr2", true); | ||
119 | + clientAttributes.addProperty("attr3", 42.0); | ||
120 | + clientAttributes.addProperty("attr4", 73); | ||
121 | + mqttClient.publish("v1/devices/me/attributes", Unpooled.wrappedBuffer(clientAttributes.toString().getBytes())); | ||
122 | + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); | ||
123 | + wsClient.closeBlocking(); | ||
124 | + | ||
125 | + Assert.assertEquals(4, actualLatestTelemetry.getData().size()); | ||
126 | + Assert.assertEquals(Sets.newHashSet("attr1", "attr2", "attr3", "attr4"), | ||
127 | + actualLatestTelemetry.getLatestValues().keySet()); | ||
128 | + | ||
129 | + Assert.assertTrue(verify(actualLatestTelemetry, "attr1", "value1")); | ||
130 | + Assert.assertTrue(verify(actualLatestTelemetry, "attr2", Boolean.TRUE.toString())); | ||
131 | + Assert.assertTrue(verify(actualLatestTelemetry, "attr3", Double.toString(42.0))); | ||
132 | + Assert.assertTrue(verify(actualLatestTelemetry, "attr4", Long.toString(73))); | ||
133 | + | ||
134 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
135 | + } | ||
136 | + | ||
137 | + @Test | ||
138 | + public void requestAttributeValuesFromServer() throws Exception { | ||
139 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
140 | + Device device = createDevice("mqtt_"); | ||
141 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
142 | + | ||
143 | + MqttMessageListener listener = new MqttMessageListener(); | ||
144 | + MqttClient mqttClient = getMqttClient(deviceCredentials, listener); | ||
145 | + | ||
146 | + // Add a new client attribute | ||
147 | + JsonObject clientAttributes = new JsonObject(); | ||
148 | + String clientAttributeValue = RandomStringUtils.randomAlphanumeric(8); | ||
149 | + clientAttributes.addProperty("clientAttr", clientAttributeValue); | ||
150 | + mqttClient.publish("v1/devices/me/attributes", Unpooled.wrappedBuffer(clientAttributes.toString().getBytes())); | ||
151 | + | ||
152 | + // Add a new shared attribute | ||
153 | + JsonObject sharedAttributes = new JsonObject(); | ||
154 | + String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); | ||
155 | + sharedAttributes.addProperty("sharedAttr", sharedAttributeValue); | ||
156 | + ResponseEntity sharedAttributesResponse = restClient.getRestTemplate() | ||
157 | + .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", | ||
158 | + mapper.readTree(sharedAttributes.toString()), ResponseEntity.class, | ||
159 | + device.getId()); | ||
160 | + Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); | ||
161 | + | ||
162 | + // Subscribe to attributes response | ||
163 | + mqttClient.on("v1/devices/me/attributes/response/+", listener, MqttQoS.AT_LEAST_ONCE); | ||
164 | + // Request attributes | ||
165 | + JsonObject request = new JsonObject(); | ||
166 | + request.addProperty("clientKeys", "clientAttr"); | ||
167 | + request.addProperty("sharedKeys", "sharedAttr"); | ||
168 | + mqttClient.publish("v1/devices/me/attributes/request/" + new Random().nextInt(100), Unpooled.wrappedBuffer(request.toString().getBytes())); | ||
169 | + MqttEvent event = listener.getEvents().poll(10, TimeUnit.SECONDS); | ||
170 | + AttributesResponse attributes = mapper.readValue(Objects.requireNonNull(event).getMessage(), AttributesResponse.class); | ||
171 | + | ||
172 | + Assert.assertEquals(1, attributes.getClient().size()); | ||
173 | + Assert.assertEquals(clientAttributeValue, attributes.getClient().get("clientAttr")); | ||
174 | + | ||
175 | + Assert.assertEquals(1, attributes.getShared().size()); | ||
176 | + Assert.assertEquals(sharedAttributeValue, attributes.getShared().get("sharedAttr")); | ||
177 | + | ||
178 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
179 | + } | ||
180 | + | ||
181 | + @Test | ||
182 | + public void subscribeToAttributeUpdatesFromServer() throws Exception { | ||
183 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
184 | + Device device = createDevice("mqtt_"); | ||
185 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
186 | + | ||
187 | + MqttMessageListener listener = new MqttMessageListener(); | ||
188 | + MqttClient mqttClient = getMqttClient(deviceCredentials, listener); | ||
189 | + mqttClient.on("v1/devices/me/attributes", listener, MqttQoS.AT_LEAST_ONCE); | ||
190 | + | ||
191 | + String sharedAttributeName = "sharedAttr"; | ||
192 | + | ||
193 | + // Add a new shared attribute | ||
194 | + JsonObject sharedAttributes = new JsonObject(); | ||
195 | + String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); | ||
196 | + sharedAttributes.addProperty(sharedAttributeName, sharedAttributeValue); | ||
197 | + ResponseEntity sharedAttributesResponse = restClient.getRestTemplate() | ||
198 | + .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", | ||
199 | + mapper.readTree(sharedAttributes.toString()), ResponseEntity.class, | ||
200 | + device.getId()); | ||
201 | + Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); | ||
202 | + | ||
203 | + MqttEvent event = listener.getEvents().poll(10, TimeUnit.SECONDS); | ||
204 | + Assert.assertEquals(sharedAttributeValue, | ||
205 | + mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText()); | ||
206 | + | ||
207 | + // Update the shared attribute value | ||
208 | + JsonObject updatedSharedAttributes = new JsonObject(); | ||
209 | + String updatedSharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); | ||
210 | + updatedSharedAttributes.addProperty(sharedAttributeName, updatedSharedAttributeValue); | ||
211 | + ResponseEntity updatedSharedAttributesResponse = restClient.getRestTemplate() | ||
212 | + .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", | ||
213 | + mapper.readTree(updatedSharedAttributes.toString()), ResponseEntity.class, | ||
214 | + device.getId()); | ||
215 | + Assert.assertTrue(updatedSharedAttributesResponse.getStatusCode().is2xxSuccessful()); | ||
216 | + | ||
217 | + event = listener.getEvents().poll(10, TimeUnit.SECONDS); | ||
218 | + Assert.assertEquals(updatedSharedAttributeValue, | ||
219 | + mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText()); | ||
220 | + | ||
221 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
222 | + } | ||
223 | + | ||
224 | + @Test | ||
225 | + public void serverSideRpc() throws Exception { | ||
226 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
227 | + Device device = createDevice("mqtt_"); | ||
228 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
229 | + | ||
230 | + MqttMessageListener listener = new MqttMessageListener(); | ||
231 | + MqttClient mqttClient = getMqttClient(deviceCredentials, listener); | ||
232 | + mqttClient.on("v1/devices/me/rpc/request/+", listener, MqttQoS.AT_LEAST_ONCE); | ||
233 | + | ||
234 | + // Send an RPC from the server | ||
235 | + JsonObject serverRpcPayload = new JsonObject(); | ||
236 | + serverRpcPayload.addProperty("method", "getValue"); | ||
237 | + serverRpcPayload.addProperty("params", true); | ||
238 | + ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); | ||
239 | + ListenableFuture<ResponseEntity> future = service.submit(() -> { | ||
240 | + try { | ||
241 | + return restClient.getRestTemplate() | ||
242 | + .postForEntity(HTTPS_URL + "/api/plugins/rpc/twoway/{deviceId}", | ||
243 | + mapper.readTree(serverRpcPayload.toString()), String.class, | ||
244 | + device.getId()); | ||
245 | + } catch (IOException e) { | ||
246 | + return ResponseEntity.badRequest().build(); | ||
247 | + } | ||
248 | + }); | ||
249 | + | ||
250 | + // Wait for RPC call from the server and send the response | ||
251 | + MqttEvent requestFromServer = listener.getEvents().poll(10, TimeUnit.SECONDS); | ||
252 | + | ||
253 | + Assert.assertEquals("{\"method\":\"getValue\",\"params\":true}", Objects.requireNonNull(requestFromServer).getMessage()); | ||
254 | + | ||
255 | + Integer requestId = Integer.valueOf(Objects.requireNonNull(requestFromServer).getTopic().substring("v1/devices/me/rpc/request/".length())); | ||
256 | + JsonObject clientResponse = new JsonObject(); | ||
257 | + clientResponse.addProperty("response", "someResponse"); | ||
258 | + // Send a response to the server's RPC request | ||
259 | + mqttClient.publish("v1/devices/me/rpc/response/" + requestId, Unpooled.wrappedBuffer(clientResponse.toString().getBytes())); | ||
260 | + | ||
261 | + ResponseEntity serverResponse = future.get(5, TimeUnit.SECONDS); | ||
262 | + Assert.assertTrue(serverResponse.getStatusCode().is2xxSuccessful()); | ||
263 | + Assert.assertEquals(clientResponse.toString(), serverResponse.getBody()); | ||
264 | + | ||
265 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
266 | + } | ||
267 | + | ||
268 | + @Test | ||
269 | + public void clientSideRpc() throws Exception { | ||
270 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
271 | + Device device = createDevice("mqtt_"); | ||
272 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
273 | + | ||
274 | + MqttMessageListener listener = new MqttMessageListener(); | ||
275 | + MqttClient mqttClient = getMqttClient(deviceCredentials, listener); | ||
276 | + mqttClient.on("v1/devices/me/rpc/request/+", listener, MqttQoS.AT_LEAST_ONCE); | ||
277 | + | ||
278 | + // Get the default rule chain id to make it root again after test finished | ||
279 | + RuleChainId defaultRuleChainId = getDefaultRuleChainId(); | ||
280 | + | ||
281 | + // Create a new root rule chain | ||
282 | + RuleChainId ruleChainId = createRootRuleChainForRpcResponse(); | ||
283 | + | ||
284 | + // Send the request to the server | ||
285 | + JsonObject clientRequest = new JsonObject(); | ||
286 | + clientRequest.addProperty("method", "getResponse"); | ||
287 | + clientRequest.addProperty("params", true); | ||
288 | + Integer requestId = 42; | ||
289 | + mqttClient.publish("v1/devices/me/rpc/request/" + requestId, Unpooled.wrappedBuffer(clientRequest.toString().getBytes())); | ||
290 | + | ||
291 | + // Check the response from the server | ||
292 | + TimeUnit.SECONDS.sleep(1); | ||
293 | + MqttEvent responseFromServer = listener.getEvents().poll(1, TimeUnit.SECONDS); | ||
294 | + Integer responseId = Integer.valueOf(Objects.requireNonNull(responseFromServer).getTopic().substring("v1/devices/me/rpc/response/".length())); | ||
295 | + Assert.assertEquals(requestId, responseId); | ||
296 | + Assert.assertEquals("requestReceived", mapper.readTree(responseFromServer.getMessage()).get("response").asText()); | ||
297 | + | ||
298 | + // Make the default rule chain a root again | ||
299 | + ResponseEntity<RuleChain> rootRuleChainResponse = restClient.getRestTemplate() | ||
300 | + .postForEntity(HTTPS_URL + "/api/ruleChain/{ruleChainId}/root", | ||
301 | + null, | ||
302 | + RuleChain.class, | ||
303 | + defaultRuleChainId); | ||
304 | + Assert.assertTrue(rootRuleChainResponse.getStatusCode().is2xxSuccessful()); | ||
305 | + | ||
306 | + // Delete the created rule chain | ||
307 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/ruleChain/{ruleChainId}", ruleChainId); | ||
308 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
309 | + } | ||
310 | + | ||
311 | + private RuleChainId createRootRuleChainForRpcResponse() throws Exception { | ||
312 | + RuleChain newRuleChain = new RuleChain(); | ||
313 | + newRuleChain.setName("testRuleChain"); | ||
314 | + ResponseEntity<RuleChain> ruleChainResponse = restClient.getRestTemplate() | ||
315 | + .postForEntity(HTTPS_URL + "/api/ruleChain", | ||
316 | + newRuleChain, | ||
317 | + RuleChain.class); | ||
318 | + Assert.assertTrue(ruleChainResponse.getStatusCode().is2xxSuccessful()); | ||
319 | + RuleChain ruleChain = ruleChainResponse.getBody(); | ||
320 | + | ||
321 | + JsonNode configuration = mapper.readTree(this.getClass().getClassLoader().getResourceAsStream("RpcResponseRuleChainMetadata.json")); | ||
322 | + RuleChainMetaData ruleChainMetaData = new RuleChainMetaData(); | ||
323 | + ruleChainMetaData.setRuleChainId(ruleChain.getId()); | ||
324 | + ruleChainMetaData.setFirstNodeIndex(configuration.get("firstNodeIndex").asInt()); | ||
325 | + ruleChainMetaData.setNodes(Arrays.asList(mapper.treeToValue(configuration.get("nodes"), RuleNode[].class))); | ||
326 | + ruleChainMetaData.setConnections(Arrays.asList(mapper.treeToValue(configuration.get("connections"), NodeConnectionInfo[].class))); | ||
327 | + | ||
328 | + ResponseEntity<RuleChainMetaData> ruleChainMetadataResponse = restClient.getRestTemplate() | ||
329 | + .postForEntity(HTTPS_URL + "/api/ruleChain/metadata", | ||
330 | + ruleChainMetaData, | ||
331 | + RuleChainMetaData.class); | ||
332 | + Assert.assertTrue(ruleChainMetadataResponse.getStatusCode().is2xxSuccessful()); | ||
333 | + | ||
334 | + // Set a new rule chain as root | ||
335 | + ResponseEntity<RuleChain> rootRuleChainResponse = restClient.getRestTemplate() | ||
336 | + .postForEntity(HTTPS_URL + "/api/ruleChain/{ruleChainId}/root", | ||
337 | + null, | ||
338 | + RuleChain.class, | ||
339 | + ruleChain.getId()); | ||
340 | + Assert.assertTrue(rootRuleChainResponse.getStatusCode().is2xxSuccessful()); | ||
341 | + | ||
342 | + return ruleChain.getId(); | ||
343 | + } | ||
344 | + | ||
345 | + private RuleChainId getDefaultRuleChainId() { | ||
346 | + ResponseEntity<TextPageData<RuleChain>> ruleChains = restClient.getRestTemplate().exchange( | ||
347 | + HTTPS_URL + "/api/ruleChains?limit=40&textSearch=", | ||
348 | + HttpMethod.GET, | ||
349 | + null, | ||
350 | + new ParameterizedTypeReference<TextPageData<RuleChain>>() { | ||
351 | + }); | ||
352 | + | ||
353 | + Optional<RuleChain> defaultRuleChain = ruleChains.getBody().getData() | ||
354 | + .stream() | ||
355 | + .filter(RuleChain::isRoot) | ||
356 | + .findFirst(); | ||
357 | + if (!defaultRuleChain.isPresent()) { | ||
358 | + Assert.fail("Root rule chain wasn't found"); | ||
359 | + } | ||
360 | + return defaultRuleChain.get().getId(); | ||
361 | + } | ||
362 | + | ||
363 | + private MqttClient getMqttClient(DeviceCredentials deviceCredentials, MqttMessageListener listener) throws InterruptedException { | ||
364 | + MqttClientConfig clientConfig = new MqttClientConfig(); | ||
365 | + clientConfig.setClientId("MQTT client from test"); | ||
366 | + clientConfig.setUsername(deviceCredentials.getCredentialsId()); | ||
367 | + MqttClient mqttClient = MqttClient.create(clientConfig, listener); | ||
368 | + mqttClient.connect("localhost", 1883).sync(); | ||
369 | + return mqttClient; | ||
370 | + } | ||
371 | + | ||
372 | + @Data | ||
373 | + private class MqttMessageListener implements MqttHandler { | ||
374 | + private final BlockingQueue<MqttEvent> events; | ||
375 | + | ||
376 | + private MqttMessageListener() { | ||
377 | + events = new ArrayBlockingQueue<>(100); | ||
378 | + } | ||
379 | + | ||
380 | + @Override | ||
381 | + public void onMessage(String topic, ByteBuf message) { | ||
382 | + log.info("MQTT message [{}], topic [{}]", message.toString(StandardCharsets.UTF_8), topic); | ||
383 | + events.add(new MqttEvent(topic, message.toString(StandardCharsets.UTF_8))); | ||
384 | + } | ||
385 | + } | ||
386 | + | ||
387 | + @Data | ||
388 | + private class MqttEvent { | ||
389 | + private final String topic; | ||
390 | + private final String message; | ||
391 | + } | ||
392 | +} |
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/mapper/AttributesResponse.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2018 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.msa.mapper; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | + | ||
20 | +import java.util.Map; | ||
21 | + | ||
22 | +@Data | ||
23 | +public class AttributesResponse { | ||
24 | + private Map<String, Object> client; | ||
25 | + private Map<String, Object> shared; | ||
26 | +} |
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/mapper/WsTelemetryResponse.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2018 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.msa.mapper; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | + | ||
20 | +import java.io.Serializable; | ||
21 | +import java.util.Collection; | ||
22 | +import java.util.List; | ||
23 | +import java.util.Map; | ||
24 | +import java.util.stream.Collectors; | ||
25 | + | ||
26 | +@Data | ||
27 | +public class WsTelemetryResponse implements Serializable { | ||
28 | + private int subscriptionId; | ||
29 | + private int errorCode; | ||
30 | + private String errorMsg; | ||
31 | + private Map<String, List<List<Object>>> data; | ||
32 | + private Map<String, Object> latestValues; | ||
33 | + | ||
34 | + public List<Object> getDataValuesByKey(String key) { | ||
35 | + return data.entrySet().stream() | ||
36 | + .filter(e -> e.getKey().equals(key)) | ||
37 | + .flatMap(e -> e.getValue().stream().flatMap(Collection::stream)) | ||
38 | + .collect(Collectors.toList()); | ||
39 | + } | ||
40 | +} |
1 | +{ | ||
2 | + "firstNodeIndex": 0, | ||
3 | + "nodes": [ | ||
4 | + { | ||
5 | + "additionalInfo": { | ||
6 | + "layoutX": 325, | ||
7 | + "layoutY": 150 | ||
8 | + }, | ||
9 | + "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", | ||
10 | + "name": "msgTypeSwitch", | ||
11 | + "debugMode": true, | ||
12 | + "configuration": { | ||
13 | + "version": 0 | ||
14 | + } | ||
15 | + }, | ||
16 | + { | ||
17 | + "additionalInfo": { | ||
18 | + "layoutX": 60, | ||
19 | + "layoutY": 300 | ||
20 | + }, | ||
21 | + "type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode", | ||
22 | + "name": "formResponse", | ||
23 | + "debugMode": true, | ||
24 | + "configuration": { | ||
25 | + "jsScript": "if (msg.method == \"getResponse\") {\n return {msg: {\"response\": \"requestReceived\"}, metadata: metadata, msgType: msgType};\n}\n\nreturn {msg: msg, metadata: metadata, msgType: msgType};" | ||
26 | + } | ||
27 | + }, | ||
28 | + { | ||
29 | + "additionalInfo": { | ||
30 | + "layoutX": 450, | ||
31 | + "layoutY": 300 | ||
32 | + }, | ||
33 | + "type": "org.thingsboard.rule.engine.rpc.TbSendRPCReplyNode", | ||
34 | + "name": "rpcReply", | ||
35 | + "debugMode": true, | ||
36 | + "configuration": { | ||
37 | + "requestIdMetaDataAttribute": "requestId" | ||
38 | + } | ||
39 | + } | ||
40 | + ], | ||
41 | + "connections": [ | ||
42 | + { | ||
43 | + "fromIndex": 0, | ||
44 | + "toIndex": 1, | ||
45 | + "type": "RPC Request from Device" | ||
46 | + }, | ||
47 | + { | ||
48 | + "fromIndex": 1, | ||
49 | + "toIndex": 2, | ||
50 | + "type": "Success" | ||
51 | + }, | ||
52 | + { | ||
53 | + "fromIndex": 1, | ||
54 | + "toIndex": 2, | ||
55 | + "type": "Failure" | ||
56 | + } | ||
57 | + ], | ||
58 | + "ruleChainConnections": null | ||
59 | +} |
@@ -16,7 +16,7 @@ | @@ -16,7 +16,7 @@ | ||
16 | 16 | ||
17 | --> | 17 | --> |
18 | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | 18 | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
19 | - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | 19 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
20 | <modelVersion>4.0.0</modelVersion> | 20 | <modelVersion>4.0.0</modelVersion> |
21 | <parent> | 21 | <parent> |
22 | <groupId>org.thingsboard</groupId> | 22 | <groupId>org.thingsboard</groupId> |
@@ -41,6 +41,7 @@ | @@ -41,6 +41,7 @@ | ||
41 | <module>web-ui</module> | 41 | <module>web-ui</module> |
42 | <module>tb-node</module> | 42 | <module>tb-node</module> |
43 | <module>transport</module> | 43 | <module>transport</module> |
44 | + <module>black-box-tests</module> | ||
44 | </modules> | 45 | </modules> |
45 | 46 | ||
46 | <build> | 47 | <build> |