Add tests for compilation errors when evaluating scripts
This commit is contained in:
parent
2055dc83be
commit
a0e8b01429
@ -25,11 +25,13 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.test.context.TestPropertySource;
|
import org.springframework.test.context.TestPropertySource;
|
||||||
import org.thingsboard.common.util.TbStopWatch;
|
import org.thingsboard.common.util.TbStopWatch;
|
||||||
import org.thingsboard.script.api.ScriptType;
|
import org.thingsboard.script.api.ScriptType;
|
||||||
|
import org.thingsboard.script.api.TbScriptException;
|
||||||
import org.thingsboard.script.api.js.NashornJsInvokeService;
|
import org.thingsboard.script.api.js.NashornJsInvokeService;
|
||||||
import org.thingsboard.server.common.data.id.TenantId;
|
import org.thingsboard.server.common.data.id.TenantId;
|
||||||
import org.thingsboard.server.controller.AbstractControllerTest;
|
import org.thingsboard.server.controller.AbstractControllerTest;
|
||||||
import org.thingsboard.server.dao.service.DaoSqlTest;
|
import org.thingsboard.server.dao.service.DaoSqlTest;
|
||||||
|
|
||||||
|
import javax.script.ScriptException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -39,6 +41,7 @@ import java.util.concurrent.TimeoutException;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST;
|
import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST;
|
||||||
|
|
||||||
@ -59,6 +62,25 @@ class NashornJsInvokeServiceTest extends AbstractControllerTest {
|
|||||||
@Value("${js.local.max_errors}")
|
@Value("${js.local.max_errors}")
|
||||||
private int maxJsErrors;
|
private int maxJsErrors;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenUncompilableScript_whenEvaluating_thenThrowsErrorWithCompilationErrorCode() {
|
||||||
|
// GIVEN
|
||||||
|
var uncompilableScript = "return msg.temperature?.value;";
|
||||||
|
|
||||||
|
// WHEN-THEN
|
||||||
|
assertThatThrownBy(() -> evalScript(uncompilableScript))
|
||||||
|
.isInstanceOf(ExecutionException.class)
|
||||||
|
.cause()
|
||||||
|
.isInstanceOf(TbScriptException.class)
|
||||||
|
.asInstanceOf(type(TbScriptException.class))
|
||||||
|
.satisfies(ex -> {
|
||||||
|
assertThat(ex.getScriptId()).isNotNull();
|
||||||
|
assertThat(ex.getErrorCode()).isEqualTo(TbScriptException.ErrorCode.COMPILATION);
|
||||||
|
assertThat(ex.getBody()).contains(uncompilableScript);
|
||||||
|
assertThat(ex.getCause()).isInstanceOf(ScriptException.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException {
|
void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException {
|
||||||
int iterations = 1000;
|
int iterations = 1000;
|
||||||
|
|||||||
@ -23,9 +23,9 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
import org.thingsboard.script.api.ScriptType;
|
import org.thingsboard.script.api.ScriptType;
|
||||||
|
import org.thingsboard.script.api.TbScriptException;
|
||||||
import org.thingsboard.server.common.data.ApiUsageState;
|
import org.thingsboard.server.common.data.ApiUsageState;
|
||||||
import org.thingsboard.server.common.data.id.TenantId;
|
import org.thingsboard.server.common.data.id.TenantId;
|
||||||
import org.thingsboard.server.common.stats.DefaultStatsFactory;
|
|
||||||
import org.thingsboard.server.common.stats.StatsCounter;
|
import org.thingsboard.server.common.stats.StatsCounter;
|
||||||
import org.thingsboard.server.common.stats.StatsFactory;
|
import org.thingsboard.server.common.stats.StatsFactory;
|
||||||
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
|
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
|
||||||
@ -42,8 +42,11 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.argThat;
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
@ -60,7 +63,6 @@ class RemoteJsInvokeServiceTest {
|
|||||||
private RemoteJsInvokeService remoteJsInvokeService;
|
private RemoteJsInvokeService remoteJsInvokeService;
|
||||||
private TbQueueRequestTemplate<TbProtoJsQueueMsg<RemoteJsRequest>, TbProtoQueueMsg<RemoteJsResponse>> jsRequestTemplate;
|
private TbQueueRequestTemplate<TbProtoJsQueueMsg<RemoteJsRequest>, TbProtoQueueMsg<RemoteJsResponse>> jsRequestTemplate;
|
||||||
|
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void beforeEach() {
|
public void beforeEach() {
|
||||||
TbApiUsageStateClient apiUsageStateClient = mock(TbApiUsageStateClient.class);
|
TbApiUsageStateClient apiUsageStateClient = mock(TbApiUsageStateClient.class);
|
||||||
@ -74,7 +76,7 @@ class RemoteJsInvokeServiceTest {
|
|||||||
remoteJsInvokeService.requestTemplate = jsRequestTemplate;
|
remoteJsInvokeService.requestTemplate = jsRequestTemplate;
|
||||||
StatsFactory statsFactory = mock(StatsFactory.class);
|
StatsFactory statsFactory = mock(StatsFactory.class);
|
||||||
when(statsFactory.createStatsCounter(any(), any())).thenReturn(mock(StatsCounter.class));
|
when(statsFactory.createStatsCounter(any(), any())).thenReturn(mock(StatsCounter.class));
|
||||||
ReflectionTestUtils.setField(remoteJsInvokeService, "statsFactory",statsFactory);
|
ReflectionTestUtils.setField(remoteJsInvokeService, "statsFactory", statsFactory);
|
||||||
remoteJsInvokeService.init();
|
remoteJsInvokeService.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +86,36 @@ class RemoteJsInvokeServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void whenInvokingFunction_thenDoNotSendScriptBody() throws Exception {
|
void givenUncompilableScript_whenEvaluating_thenThrowsErrorWithCompilationErrorCode() {
|
||||||
|
// GIVEN
|
||||||
|
doAnswer(methodCall -> Futures.immediateFuture(new TbProtoJsQueueMsg<>(UUID.randomUUID(), RemoteJsResponse.newBuilder()
|
||||||
|
.setCompileResponse(JsInvokeProtos.JsCompileResponse.newBuilder()
|
||||||
|
.setSuccess(false)
|
||||||
|
.setErrorCode(JsInvokeProtos.JsInvokeErrorCode.COMPILATION_ERROR)
|
||||||
|
.setErrorDetails("SyntaxError: Unexpected token 'const'")
|
||||||
|
.setScriptHash(methodCall.<TbProtoQueueMsg<RemoteJsRequest>>getArgument(0).getValue().getCompileRequest().getScriptHash())
|
||||||
|
.build())
|
||||||
|
.build())))
|
||||||
|
.when(jsRequestTemplate).send(argThat(jsQueueMsg -> jsQueueMsg.getValue().hasCompileRequest()));
|
||||||
|
|
||||||
|
var uncompilableScript = "let const = 'this is not allowed';";
|
||||||
|
|
||||||
|
// WHEN-THEN
|
||||||
|
assertThatThrownBy(() -> remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, uncompilableScript).get())
|
||||||
|
.isInstanceOf(ExecutionException.class)
|
||||||
|
.cause()
|
||||||
|
.isInstanceOf(TbScriptException.class)
|
||||||
|
.asInstanceOf(type(TbScriptException.class))
|
||||||
|
.satisfies(ex -> {
|
||||||
|
assertThat(ex.getScriptId()).isNotNull();
|
||||||
|
assertThat(ex.getErrorCode()).isEqualTo(TbScriptException.ErrorCode.COMPILATION);
|
||||||
|
assertThat(ex.getBody()).contains(uncompilableScript);
|
||||||
|
assertThat(ex.getCause()).isInstanceOf(RuntimeException.class).hasMessage("SyntaxError: Unexpected token 'const'");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenInvokingFunction_thenDoNotSendScriptBody() throws Exception {
|
||||||
mockJsEvalResponse();
|
mockJsEvalResponse();
|
||||||
String scriptBody = "return { a: 'b'};";
|
String scriptBody = "return { a: 'b'};";
|
||||||
UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get();
|
UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get();
|
||||||
@ -110,7 +141,7 @@ class RemoteJsInvokeServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void whenInvokingFunctionAndRemoteJsExecutorRemovedScript_thenHandleNotFoundErrorAndMakeInvokeRequestWithScriptBody() throws Exception {
|
void whenInvokingFunctionAndRemoteJsExecutorRemovedScript_thenHandleNotFoundErrorAndMakeInvokeRequestWithScriptBody() throws Exception {
|
||||||
mockJsEvalResponse();
|
mockJsEvalResponse();
|
||||||
String scriptBody = "return { a: 'b'};";
|
String scriptBody = "return { a: 'b'};";
|
||||||
UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get();
|
UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get();
|
||||||
@ -156,7 +187,7 @@ class RemoteJsInvokeServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void whenDoingEval_thenSaveScriptByHashOfTenantIdAndScriptBody() throws Exception {
|
void whenDoingEval_thenSaveScriptByHashOfTenantIdAndScriptBody() throws Exception {
|
||||||
mockJsEvalResponse();
|
mockJsEvalResponse();
|
||||||
|
|
||||||
TenantId tenantId1 = TenantId.fromUUID(UUID.randomUUID());
|
TenantId tenantId1 = TenantId.fromUUID(UUID.randomUUID());
|
||||||
@ -187,7 +218,7 @@ class RemoteJsInvokeServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void whenReleasingScript_thenCheckForHashUsages() throws Exception {
|
void whenReleasingScript_thenCheckForHashUsages() throws Exception {
|
||||||
mockJsEvalResponse();
|
mockJsEvalResponse();
|
||||||
String scriptBody = "return { a: 'b'};";
|
String scriptBody = "return { a: 'b'};";
|
||||||
UUID scriptId1 = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get();
|
UUID scriptId1 = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get();
|
||||||
|
|||||||
@ -20,10 +20,12 @@ import com.github.benmanes.caffeine.cache.Cache;
|
|||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mvel2.CompileException;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.test.context.TestPropertySource;
|
import org.springframework.test.context.TestPropertySource;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
import org.thingsboard.common.util.JacksonUtil;
|
import org.thingsboard.common.util.JacksonUtil;
|
||||||
|
import org.thingsboard.script.api.TbScriptException;
|
||||||
import org.thingsboard.script.api.tbel.TbelScript;
|
import org.thingsboard.script.api.tbel.TbelScript;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@ -37,6 +39,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||||
|
|
||||||
@TestPropertySource(properties = {
|
@TestPropertySource(properties = {
|
||||||
"tbel.max_script_body_size=100",
|
"tbel.max_script_body_size=100",
|
||||||
@ -50,6 +53,25 @@ class TbelInvokeServiceTest extends AbstractTbelInvokeTest {
|
|||||||
@Value("${tbel.max_errors}")
|
@Value("${tbel.max_errors}")
|
||||||
private int maxJsErrors;
|
private int maxJsErrors;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenUncompilableScript_whenEvaluating_thenThrowsErrorWithCompilationErrorCode() {
|
||||||
|
// GIVEN
|
||||||
|
var uncompilableScript = "return msg.property !== undefined;";
|
||||||
|
|
||||||
|
// WHEN-THEN
|
||||||
|
assertThatThrownBy(() -> evalScript(uncompilableScript))
|
||||||
|
.isInstanceOf(ExecutionException.class)
|
||||||
|
.cause()
|
||||||
|
.isInstanceOf(TbScriptException.class)
|
||||||
|
.asInstanceOf(type(TbScriptException.class))
|
||||||
|
.satisfies(ex -> {
|
||||||
|
assertThat(ex.getScriptId()).isNotNull();
|
||||||
|
assertThat(ex.getErrorCode()).isEqualTo(TbScriptException.ErrorCode.COMPILATION);
|
||||||
|
assertThat(ex.getBody()).isEqualTo(uncompilableScript);
|
||||||
|
assertThat(ex.getCause()).isInstanceOf(CompileException.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException {
|
void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException {
|
||||||
int iterations = 100000;
|
int iterations = 100000;
|
||||||
|
|||||||
@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2016-2025 The Thingsboard Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.thingsboard.script.api;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.EnumSource;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class TbScriptExceptionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenCompilationError_whenCheckingIsUnrecoverable_thenReturnsTrue() {
|
||||||
|
// GIVEN
|
||||||
|
var exception = new TbScriptException(null, TbScriptException.ErrorCode.COMPILATION, null, null);
|
||||||
|
|
||||||
|
// WHEN-THEN
|
||||||
|
assertThat(exception.isUnrecoverable()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@EnumSource(
|
||||||
|
value = TbScriptException.ErrorCode.class,
|
||||||
|
mode = EnumSource.Mode.EXCLUDE,
|
||||||
|
names = "COMPILATION"
|
||||||
|
)
|
||||||
|
void givenRecoverableErrorCodes_whenCheckingIsUnrecoverable_thenReturnsFalse(TbScriptException.ErrorCode errorCode) {
|
||||||
|
// GIVEN
|
||||||
|
var exception = new TbScriptException(null, errorCode, null, null);
|
||||||
|
|
||||||
|
// WHEN-THEN
|
||||||
|
assertThat(exception.isUnrecoverable()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user