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 | |
\ No newline at end of file | ... | ... |
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 | +} | |
\ No newline at end of file | ... | ... |
... | ... | @@ -16,7 +16,7 @@ |
16 | 16 | |
17 | 17 | --> |
18 | 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 | 20 | <modelVersion>4.0.0</modelVersion> |
21 | 21 | <parent> |
22 | 22 | <groupId>org.thingsboard</groupId> |
... | ... | @@ -41,6 +41,7 @@ |
41 | 41 | <module>web-ui</module> |
42 | 42 | <module>tb-node</module> |
43 | 43 | <module>transport</module> |
44 | + <module>black-box-tests</module> | |
44 | 45 | </modules> |
45 | 46 | |
46 | 47 | <build> | ... | ... |