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.thingsboard.common.util.TbStopWatch;
|
||||
import org.thingsboard.script.api.ScriptType;
|
||||
import org.thingsboard.script.api.TbScriptException;
|
||||
import org.thingsboard.script.api.js.NashornJsInvokeService;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.controller.AbstractControllerTest;
|
||||
import org.thingsboard.server.dao.service.DaoSqlTest;
|
||||
|
||||
import javax.script.ScriptException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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.assertThatThrownBy;
|
||||
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
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}")
|
||||
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
|
||||
void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException {
|
||||
int iterations = 1000;
|
||||
|
||||
@ -23,9 +23,9 @@ import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
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.id.TenantId;
|
||||
import org.thingsboard.server.common.stats.DefaultStatsFactory;
|
||||
import org.thingsboard.server.common.stats.StatsCounter;
|
||||
import org.thingsboard.server.common.stats.StatsFactory;
|
||||
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
|
||||
@ -42,8 +42,11 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
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.argThat;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
@ -60,7 +63,6 @@ class RemoteJsInvokeServiceTest {
|
||||
private RemoteJsInvokeService remoteJsInvokeService;
|
||||
private TbQueueRequestTemplate<TbProtoJsQueueMsg<RemoteJsRequest>, TbProtoQueueMsg<RemoteJsResponse>> jsRequestTemplate;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() {
|
||||
TbApiUsageStateClient apiUsageStateClient = mock(TbApiUsageStateClient.class);
|
||||
@ -74,7 +76,7 @@ class RemoteJsInvokeServiceTest {
|
||||
remoteJsInvokeService.requestTemplate = jsRequestTemplate;
|
||||
StatsFactory statsFactory = mock(StatsFactory.class);
|
||||
when(statsFactory.createStatsCounter(any(), any())).thenReturn(mock(StatsCounter.class));
|
||||
ReflectionTestUtils.setField(remoteJsInvokeService, "statsFactory",statsFactory);
|
||||
ReflectionTestUtils.setField(remoteJsInvokeService, "statsFactory", statsFactory);
|
||||
remoteJsInvokeService.init();
|
||||
}
|
||||
|
||||
@ -84,7 +86,36 @@ class RemoteJsInvokeServiceTest {
|
||||
}
|
||||
|
||||
@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();
|
||||
String scriptBody = "return { a: 'b'};";
|
||||
UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get();
|
||||
@ -110,7 +141,7 @@ class RemoteJsInvokeServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenInvokingFunctionAndRemoteJsExecutorRemovedScript_thenHandleNotFoundErrorAndMakeInvokeRequestWithScriptBody() throws Exception {
|
||||
void whenInvokingFunctionAndRemoteJsExecutorRemovedScript_thenHandleNotFoundErrorAndMakeInvokeRequestWithScriptBody() throws Exception {
|
||||
mockJsEvalResponse();
|
||||
String scriptBody = "return { a: 'b'};";
|
||||
UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get();
|
||||
@ -156,7 +187,7 @@ class RemoteJsInvokeServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenDoingEval_thenSaveScriptByHashOfTenantIdAndScriptBody() throws Exception {
|
||||
void whenDoingEval_thenSaveScriptByHashOfTenantIdAndScriptBody() throws Exception {
|
||||
mockJsEvalResponse();
|
||||
|
||||
TenantId tenantId1 = TenantId.fromUUID(UUID.randomUUID());
|
||||
@ -187,7 +218,7 @@ class RemoteJsInvokeServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenReleasingScript_thenCheckForHashUsages() throws Exception {
|
||||
void whenReleasingScript_thenCheckForHashUsages() throws Exception {
|
||||
mockJsEvalResponse();
|
||||
String scriptBody = "return { a: 'b'};";
|
||||
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.Ignore;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mvel2.CompileException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.script.api.TbScriptException;
|
||||
import org.thingsboard.script.api.tbel.TbelScript;
|
||||
|
||||
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.assertThatThrownBy;
|
||||
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||
|
||||
@TestPropertySource(properties = {
|
||||
"tbel.max_script_body_size=100",
|
||||
@ -50,6 +53,25 @@ class TbelInvokeServiceTest extends AbstractTbelInvokeTest {
|
||||
@Value("${tbel.max_errors}")
|
||||
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
|
||||
void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException {
|
||||
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