Merge branch 'restApiCallNodeJsonVarFix' of github.com:desoliture1/thingsboard into develop/3.4

This commit is contained in:
Andrii Shvaika 2022-06-22 11:23:28 +03:00
commit 6f78d8245a
4 changed files with 194 additions and 1 deletions

13
pom.xml
View File

@ -128,6 +128,7 @@
<java-websocket.version>1.5.2</java-websocket.version>
<jupiter.version>5.8.2</jupiter.version> <!-- keep the same version as spring-boot-starter-test depend on jupiter-->
<json-path.version>2.6.0</json-path.version>
<mock-server.version>5.13.1</mock-server.version>
<spring-test-dbunit.version>1.3.0</spring-test-dbunit.version> <!-- 2016 -->
<takari-cpsuite.version>1.2.7</takari-cpsuite.version> <!-- 2015 -->
<!-- BLACKBOX TEST SCOPE -->
@ -1877,6 +1878,18 @@
<version>${zeroturnaround.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
<version>${mock-server.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-client-java</artifactId>
<version>${mock-server.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.opensmpp</groupId>
<artifactId>opensmpp-core</artifactId>

View File

@ -136,6 +136,15 @@
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-client-java</artifactId>
</dependency>
<dependency>
<groupId>org.cassandraunit</groupId>
<artifactId>cassandra-unit</artifactId>

View File

@ -40,6 +40,7 @@ import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.util.UriComponentsBuilder;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.TbRelationTypes;
@ -54,6 +55,7 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.util.Deque;
@ -189,8 +191,9 @@ public class TbHttpClient {
entity = new HttpEntity<>(msg.getData(), headers);
}
URI uri = buildEncodedUri(endpointUrl);
ListenableFuture<ResponseEntity<String>> future = httpClient.exchange(
endpointUrl, method, entity, String.class);
uri, method, entity, String.class);
future.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
@Override
public void onFailure(Throwable throwable) {
@ -214,6 +217,28 @@ public class TbHttpClient {
}
}
public URI buildEncodedUri(String endpointUrl) {
if (endpointUrl == null) {
throw new RuntimeException("Url string cannot be null!");
}
if (endpointUrl.isEmpty()) {
throw new RuntimeException("Url string cannot be empty!");
}
URI uri = UriComponentsBuilder.fromUriString(endpointUrl).build().encode().toUri();
if (uri.getScheme() == null || uri.getScheme().isEmpty()) {
throw new RuntimeException("Transport scheme(protocol) must be provided!");
}
boolean authorityNotValid = uri.getAuthority() == null || uri.getAuthority().isEmpty();
boolean hostNotValid = uri.getHost() == null || uri.getHost().isEmpty();
if (authorityNotValid || hostNotValid) {
throw new RuntimeException("Url string is invalid!");
}
return uri;
}
private TbMsg processResponse(TbContext ctx, TbMsg origMsg, ResponseEntity<String> response) {
TbMsgMetaData metaData = origMsg.getMetaData();
metaData.putValue(STATUS, response.getStatusCode().name());

View File

@ -18,16 +18,38 @@ package org.thingsboard.rule.engine.rest;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import org.awaitility.Awaitility;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockserver.integration.ClientAndServer;
import org.springframework.web.client.AsyncRestTemplate;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import java.net.URI;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockserver.integration.ClientAndServer.startClientAndServer;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
public class TbHttpClientTest {
@ -58,4 +80,128 @@ public class TbHttpClientTest {
eventLoop = client.getSharedOrCreateEventLoopGroup(null);
assertThat(eventLoop, instanceOf(NioEventLoopGroup.class));
}
@Test
public void testBuildSimpleUri() {
Mockito.when(client.buildEncodedUri(any())).thenCallRealMethod();
String url = "http://localhost:8080/";
URI uri = client.buildEncodedUri(url);
Assert.assertEquals(url, uri.toString());
}
@Test
public void testBuildUriWithoutProtocol() {
Mockito.when(client.buildEncodedUri(any())).thenCallRealMethod();
String url = "localhost:8080/";
assertThatThrownBy(() -> client.buildEncodedUri(url));
}
@Test
public void testBuildInvalidUri() {
Mockito.when(client.buildEncodedUri(any())).thenCallRealMethod();
String url = "aaa";
assertThatThrownBy(() -> client.buildEncodedUri(url));
}
@Test
public void testBuildUriWithSpecialSymbols() {
Mockito.when(client.buildEncodedUri(any())).thenCallRealMethod();
String url = "http://192.168.1.1/data?d={\"a\": 12}";
String expected = "http://192.168.1.1/data?d=%7B%22a%22:%2012%7D";
URI uri = client.buildEncodedUri(url);
Assert.assertEquals(expected, uri.toString());
}
@Test
public void testProcessMessageWithJsonInUrlVariable() throws Exception {
String host = "localhost";
String path = "/api";
String paramKey = "data";
String paramVal = "[{\"test\":\"test\"}]";
String successResponseBody = "SUCCESS";
var server = setUpDummyServer(host, path, paramKey, paramVal, successResponseBody);
String endpointUrl = String.format(
"http://%s:%d%s?%s=%s",
host, server.getPort(), path, paramKey, paramVal
);
String method = "GET";
var config = new TbRestApiCallNodeConfiguration()
.defaultConfiguration();
config.setRequestMethod(method);
config.setRestEndpointUrlPattern(endpointUrl);
config.setUseSimpleClientHttpFactory(true);
var asyncRestTemplate = new AsyncRestTemplate();
var httpClient = new TbHttpClient(config, eventLoop);
httpClient.setHttpClient(asyncRestTemplate);
var msg = TbMsg.newMsg(
"Main", "GET", new DeviceId(EntityId.NULL_UUID),
TbMsgMetaData.EMPTY, "{}"
);
var successMsg = TbMsg.newMsg(
"SUCCESS", msg.getOriginator(),
msg.getMetaData(), msg.getData()
);
var ctx = mock(TbContext.class);
when(ctx.transformMsg(
eq(msg), eq(msg.getType()),
eq(msg.getOriginator()),
eq(msg.getMetaData()),
eq(msg.getData())
)).thenReturn(successMsg);
var capturedData = ArgumentCaptor.forClass(String.class);
when(ctx.transformMsg(
eq(msg), eq(msg.getType()),
eq(msg.getOriginator()),
any(),
capturedData.capture()
)).thenReturn(successMsg);
httpClient.processMessage(ctx, msg);
Awaitility.await()
.atMost(30, TimeUnit.SECONDS)
.until(() -> {
try {
verify(ctx, times(1)).tellSuccess(any());
return true;
} catch (Exception e) {
return false;
}
});
verify(ctx, times(1)).tellSuccess(any());
verify(ctx, times(0)).tellFailure(any(), any());
Assert.assertEquals(successResponseBody, capturedData.getValue());
}
private ClientAndServer setUpDummyServer(String host, String path, String paramKey, String paramVal, String successResponseBody) {
var server = startClientAndServer(host, 1080);
createGetMethodExpectations(server, path, paramKey, paramVal, successResponseBody);
return server;
}
private void createGetMethodExpectations(ClientAndServer server, String path, String paramKey, String paramVal, String successResponseBody) {
server.when(
request()
.withMethod("GET")
.withPath(path)
.withQueryStringParameter(paramKey, paramVal)
).respond(
response()
.withStatusCode(200)
.withBody(successResponseBody)
);
}
}