From 220665bf0f553d7a7818972ab1a69210fd8997c9 Mon Sep 17 00:00:00 2001 From: desoliture Date: Tue, 7 Dec 2021 17:51:58 +0200 Subject: [PATCH 01/13] fix tenant access denied to creating relations with themselves --- .../controller/EntityRelationController.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java index f5c5bdd284..44cc111336 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java @@ -27,6 +27,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; @@ -41,6 +42,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION; @@ -79,8 +81,8 @@ public class EntityRelationController extends BaseController { @RequestBody EntityRelation relation) throws ThingsboardException { try { checkNotNull(relation); - checkEntityId(relation.getFrom(), Operation.WRITE); - checkEntityId(relation.getTo(), Operation.WRITE); + checkRelation(relation.getFrom(), relation.getTo()); + if (relation.getTypeGroup() == null) { relation.setTypeGroup(RelationTypeGroup.COMMON); } @@ -119,8 +121,8 @@ public class EntityRelationController extends BaseController { checkParameter(TO_TYPE, strToType); EntityId fromId = EntityIdFactory.getByTypeAndId(strFromType, strFromId); EntityId toId = EntityIdFactory.getByTypeAndId(strToType, strToId); - checkEntityId(fromId, Operation.WRITE); - checkEntityId(toId, Operation.WRITE); + checkRelation(fromId, toId); + RelationTypeGroup relationTypeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); EntityRelation relation = new EntityRelation(fromId, toId, strRelationType, relationTypeGroup); try { @@ -375,6 +377,16 @@ public class EntityRelationController extends BaseController { } } + private void checkRelation(EntityId from, EntityId to) throws ThingsboardException { + UUID currentUserTenantId = getCurrentUser().getTenantId().getId(); + if (from.getEntityType() != EntityType.TENANT || !from.getId().equals(currentUserTenantId)) { + checkEntityId(from, Operation.WRITE); + } + if (to.getEntityType() != EntityType.TENANT || !to.getId().equals(currentUserTenantId)) { + checkEntityId(to, Operation.WRITE); + } + } + private List filterRelationsByReadPermission(List relationsByQuery) { return relationsByQuery.stream().filter(relationByQuery -> { try { From 5a47bf3b8b1a96d203ab2670a04128d136a0f7e7 Mon Sep 17 00:00:00 2001 From: desoliture Date: Wed, 19 Jan 2022 16:35:30 +0200 Subject: [PATCH 02/13] refactor --- .../server/controller/EntityRelationController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java index 44cc111336..7e15d2f692 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java @@ -379,10 +379,10 @@ public class EntityRelationController extends BaseController { private void checkRelation(EntityId from, EntityId to) throws ThingsboardException { UUID currentUserTenantId = getCurrentUser().getTenantId().getId(); - if (from.getEntityType() != EntityType.TENANT || !from.getId().equals(currentUserTenantId)) { + if (!(from.getEntityType() == EntityType.TENANT && from.getId().equals(currentUserTenantId))) { checkEntityId(from, Operation.WRITE); } - if (to.getEntityType() != EntityType.TENANT || !to.getId().equals(currentUserTenantId)) { + if (!(to.getEntityType() == EntityType.TENANT && to.getId().equals(currentUserTenantId))) { checkEntityId(to, Operation.WRITE); } } From 116d1ca6664c3979910cf355f6a1a106e7fcb830 Mon Sep 17 00:00:00 2001 From: desoliture Date: Wed, 19 Jan 2022 18:34:08 +0200 Subject: [PATCH 03/13] refactor checkRelation --- .../controller/EntityRelationController.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java index 7e15d2f692..a9ae6d7edf 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.relation.EntityRelationInfo; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import java.util.List; @@ -81,7 +82,8 @@ public class EntityRelationController extends BaseController { @RequestBody EntityRelation relation) throws ThingsboardException { try { checkNotNull(relation); - checkRelation(relation.getFrom(), relation.getTo()); + checkCanCreateRelation(relation.getFrom()); + checkCanCreateRelation(relation.getTo()); if (relation.getTypeGroup() == null) { relation.setTypeGroup(RelationTypeGroup.COMMON); @@ -121,7 +123,8 @@ public class EntityRelationController extends BaseController { checkParameter(TO_TYPE, strToType); EntityId fromId = EntityIdFactory.getByTypeAndId(strFromType, strFromId); EntityId toId = EntityIdFactory.getByTypeAndId(strToType, strToId); - checkRelation(fromId, toId); + checkCanCreateRelation(fromId); + checkCanCreateRelation(toId); RelationTypeGroup relationTypeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); EntityRelation relation = new EntityRelation(fromId, toId, strRelationType, relationTypeGroup); @@ -377,13 +380,10 @@ public class EntityRelationController extends BaseController { } } - private void checkRelation(EntityId from, EntityId to) throws ThingsboardException { - UUID currentUserTenantId = getCurrentUser().getTenantId().getId(); - if (!(from.getEntityType() == EntityType.TENANT && from.getId().equals(currentUserTenantId))) { - checkEntityId(from, Operation.WRITE); - } - if (!(to.getEntityType() == EntityType.TENANT && to.getId().equals(currentUserTenantId))) { - checkEntityId(to, Operation.WRITE); + private void checkCanCreateRelation(EntityId entityId) throws ThingsboardException { + SecurityUser currentUser = getCurrentUser(); + if (!(currentUser.isTenantAdmin() && currentUser.getTenantId().equals(entityId))) { + checkEntityId(entityId, Operation.WRITE); } } From 631348a60c07ab5dd025f61e485741dfa27207b3 Mon Sep 17 00:00:00 2001 From: desoliture Date: Mon, 24 Jan 2022 13:41:07 +0200 Subject: [PATCH 04/13] refactor checkCanCreateRelation --- .../server/controller/EntityRelationController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java index a9ae6d7edf..d1278b271b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java @@ -382,7 +382,8 @@ public class EntityRelationController extends BaseController { private void checkCanCreateRelation(EntityId entityId) throws ThingsboardException { SecurityUser currentUser = getCurrentUser(); - if (!(currentUser.isTenantAdmin() && currentUser.getTenantId().equals(entityId))) { + var isTenantAdminAndRelateToSelf = currentUser.isTenantAdmin() && currentUser.getTenantId().equals(entityId); + if (!isTenantAdminAndRelateToSelf) { checkEntityId(entityId, Operation.WRITE); } } From 4dccfecb2bcda63e6cc23f48d320e99c4ad463da Mon Sep 17 00:00:00 2001 From: desoliture Date: Tue, 4 Jan 2022 17:46:46 +0200 Subject: [PATCH 05/13] add tests for `EntityRelationController` --- .../BaseEntityRelationControllerTest.java | 424 ++++++++++++++++++ .../sql/EntityRelationControllerSqlTest.java | 8 + 2 files changed, 432 insertions(+) create mode 100644 application/src/test/java/org/thingsboard/server/controller/BaseEntityRelationControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/sql/EntityRelationControllerSqlTest.java diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityRelationControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityRelationControllerTest.java new file mode 100644 index 0000000000..5f07c4ea54 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityRelationControllerTest.java @@ -0,0 +1,424 @@ +package org.thingsboard.server.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.EntityRelationInfo; +import org.thingsboard.server.common.data.relation.EntityRelationsQuery; +import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.relation.RelationsSearchParameters; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.relation.RelationService; + +import java.util.Collections; +import java.util.List; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Slf4j +public class BaseEntityRelationControllerTest extends AbstractControllerTest { + + public static final String BASE_DEVICE_NAME = "Test dummy device"; + + @Autowired + RelationService relationService; + + private IdComparator idComparator; + private Tenant savedTenant; + private User tenantAdmin; + private Device mainDevice; + + @Before + public void beforeTest() throws Exception { + loginSysAdmin(); + idComparator = new IdComparator<>(); + + Tenant tenant = new Tenant(); + tenant.setTitle("Test tenant"); + + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + Device device = new Device(); + device.setName("Main test device"); + device.setType("default"); + mainDevice = doPost("/api/device", device, Device.class); + } + + @After + public void afterTest() throws Exception { + loginSysAdmin(); + + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSaveAndFindRelation() throws Exception { + Device device = buildSimpleDevice("Test device 1"); + + EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + doPost("/api/relation", relation).andExpect(status().isOk()); + + String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, + "CONTAINS", device.getUuidId(), EntityType.DEVICE + ); + + EntityRelation foundRelation = doGet(url, EntityRelation.class); + + Assert.assertNotNull("Relation is not found!", foundRelation); + Assert.assertEquals("Found relation is not equals origin!", relation, foundRelation); + } + + @Test + public void testSaveAndFindRelationsByFrom() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + String url = String.format("/api/relations?fromId=%s&fromType=%s", + mainDevice.getUuidId(), EntityType.DEVICE + ); + + assertFoundList(url, numOfDevices); + } + + @Test + public void testSaveAndFindRelationsByTo() throws Exception { + final int numOfDevices = 30; + createDevicesByTo(numOfDevices, BASE_DEVICE_NAME); + String url = String.format("/api/relations?toId=%s&toType=%s", + mainDevice.getUuidId(), EntityType.DEVICE + ); + + assertFoundList(url, numOfDevices); + } + + @Test + public void testSaveAndFindRelationsByFromWithRelationType() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + + Device device = buildSimpleDevice("Unique dummy test device "); + EntityRelation relation = createFromRelation(mainDevice, device, "TEST"); + + doPost("/api/relation", relation).andExpect(status().isOk()); + String url = String.format("/api/relations?fromId=%s&fromType=%s&relationType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, "TEST" + ); + + assertFoundList(url, 1); + } + + @Test + public void testSaveAndFindRelationsByToWithRelationType() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + + Device device = buildSimpleDevice("Unique dummy test device "); + EntityRelation relation = createFromRelation(device, mainDevice, "TEST"); + + doPost("/api/relation", relation).andExpect(status().isOk()); + String url = String.format("/api/relations?toId=%s&toType=%s&relationType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, "TEST" + ); + + assertFoundList(url, 1); + } + + @Test + public void testFindRelationsInfoByFrom() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + String url = String.format("/api/relations/info?fromId=%s&fromType=%s", + mainDevice.getUuidId(), EntityType.DEVICE + ); + + List relationsInfos = + JacksonUtil.convertValue(doGet(url, JsonNode.class), new TypeReference>() { + }); + + Assert.assertNotNull("Relations is not found!", relationsInfos); + Assert.assertEquals("List of found relationsInfos is not equal to number of created relations!", + numOfDevices, relationsInfos.size()); + + assertRelationsInfosByFrom(relationsInfos); + } + + @Test + public void testFindRelationsInfoByTo() throws Exception { + final int numOfDevices = 30; + createDevicesByTo(numOfDevices, BASE_DEVICE_NAME); + String url = String.format("/api/relations/info?toId=%s&toType=%s", + mainDevice.getUuidId(), EntityType.DEVICE + ); + + List relationsInfos = + JacksonUtil.convertValue(doGet(url, JsonNode.class), new TypeReference>() { + }); + + Assert.assertNotNull("Relations is not found!", relationsInfos); + Assert.assertEquals("List of found relationsInfos is not equal to number of created relations!", + numOfDevices, relationsInfos.size()); + + assertRelationsInfosByTo(relationsInfos); + } + + @Test + public void testDeleteRelation() throws Exception { + Device device = buildSimpleDevice("Test device 1"); + + EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + doPost("/api/relation", relation).andExpect(status().isOk()); + + String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, + "CONTAINS", device.getUuidId(), EntityType.DEVICE + ); + + EntityRelation foundRelation = doGet(url, EntityRelation.class); + + Assert.assertNotNull("Relation is not found!", foundRelation); + Assert.assertEquals("Found relation is not equals origin!", relation, foundRelation); + + doDelete(url).andExpect(status().isOk()); + doGet(url).andExpect(status().is4xxClientError()); + } + + @Test + public void testDeleteRelations() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME + " from"); + createDevicesByTo(numOfDevices, BASE_DEVICE_NAME + " to"); + + String urlTo = String.format("/api/relations?toId=%s&toType=%s", + mainDevice.getUuidId(), EntityType.DEVICE + ); + String urlFrom = String.format("/api/relations?fromId=%s&fromType=%s", + mainDevice.getUuidId(), EntityType.DEVICE + ); + + assertFoundList(urlTo, numOfDevices); + assertFoundList(urlFrom, numOfDevices); + + String url = String.format("/api/relations?entityId=%s&entityType=%s", + mainDevice.getUuidId(), EntityType.DEVICE + ); + doDelete(url).andExpect(status().isOk()); + + Assert.assertTrue( + "Performed deletion of all relations but some relations were found!", + doGet(urlTo, List.class).isEmpty() + ); + Assert.assertTrue( + "Performed deletion of all relations but some relations were found!", + doGet(urlFrom, List.class).isEmpty() + ); + } + + @Test + public void testFindRelationsByFromQuery() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters( + mainDevice.getUuidId(), EntityType.DEVICE, + EntitySearchDirection.FROM, + RelationTypeGroup.COMMON, + 1, true + )); + query.setFilters(Collections.singletonList( + new RelationEntityTypeFilter("CONTAINS", List.of(EntityType.DEVICE)) + )); + + List relations = readResponse( + doPost("/api/relations", query).andExpect(status().isOk()), + new TypeReference>() {} + ); + + assertFoundRelations(relations, numOfDevices); + } + + @Test + public void testFindRelationsByToQuery() throws Exception { + final int numOfDevices = 30; + createDevicesByTo(numOfDevices, BASE_DEVICE_NAME); + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters( + mainDevice.getUuidId(), EntityType.DEVICE, + EntitySearchDirection.TO, + RelationTypeGroup.COMMON, + 1, true + )); + query.setFilters(Collections.singletonList( + new RelationEntityTypeFilter("CONTAINS", List.of(EntityType.DEVICE)) + )); + + List relations = readResponse( + doPost("/api/relations", query).andExpect(status().isOk()), + new TypeReference>() {} + ); + + assertFoundRelations(relations, numOfDevices); + } + + @Test + public void testFindRelationsInfoByFromQuery() throws Exception{ + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters( + mainDevice.getUuidId(), EntityType.DEVICE, + EntitySearchDirection.FROM, + RelationTypeGroup.COMMON, + 1, true + )); + query.setFilters(Collections.singletonList( + new RelationEntityTypeFilter("CONTAINS", List.of(EntityType.DEVICE)) + )); + + List relationsInfo = readResponse( + doPost("/api/relations/info", query).andExpect(status().isOk()), + new TypeReference>() {} + ); + + assertRelationsInfosByFrom(relationsInfo); + } + + @Test + public void testFindRelationsInfoByToQuery() throws Exception{ + final int numOfDevices = 30; + createDevicesByTo(numOfDevices, BASE_DEVICE_NAME); + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters( + mainDevice.getUuidId(), EntityType.DEVICE, + EntitySearchDirection.TO, + RelationTypeGroup.COMMON, + 1, true + )); + query.setFilters(Collections.singletonList( + new RelationEntityTypeFilter("CONTAINS", List.of(EntityType.DEVICE)) + )); + + List relationsInfo = readResponse( + doPost("/api/relations/info", query).andExpect(status().isOk()), + new TypeReference>() {} + ); + + assertRelationsInfosByTo(relationsInfo); + } + + @Test + public void testCreateRelationFromTenantToDevice() throws Exception{ + EntityRelation relation = new EntityRelation(tenantAdmin.getTenantId(), mainDevice.getId(), "CONTAINS"); + doPost("/api/relation", relation).andExpect(status().isOk()); + + String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", + tenantAdmin.getTenantId(), EntityType.TENANT, + "CONTAINS", mainDevice.getUuidId(), EntityType.DEVICE + ); + + EntityRelation foundRelation = doGet(url, EntityRelation.class); + + Assert.assertNotNull("Relation is not found!", foundRelation); + Assert.assertEquals("Found relation is not equals origin!", relation, foundRelation); + } + + @Test + public void testCreateRelationFromDeviceToTenant() throws Exception{ + EntityRelation relation = new EntityRelation(mainDevice.getId(), tenantAdmin.getTenantId(), "CONTAINS"); + doPost("/api/relation", relation).andExpect(status().isOk()); + + String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, + "CONTAINS", tenantAdmin.getTenantId(), EntityType.TENANT + ); + + EntityRelation foundRelation = doGet(url, EntityRelation.class); + + Assert.assertNotNull("Relation is not found!", foundRelation); + Assert.assertEquals("Found relation is not equals origin!", relation, foundRelation); + } + + private Device buildSimpleDevice(String name) throws Exception { + Device device = new Device(); + device.setName(name); + device.setType("default"); + device = doPost("/api/device", device, Device.class); + return device; + } + + private EntityRelation createFromRelation(Device mainDevice, Device device, String relationType) { + return new EntityRelation(mainDevice.getId(), device.getId(), relationType); + } + + private void createDevicesByFrom(int numOfDevices, String baseName) throws Exception { + for (int i = 0; i < numOfDevices; i++) { + Device device = buildSimpleDevice(baseName + i); + + EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + doPost("/api/relation", relation).andExpect(status().isOk()); + } + } + + private void createDevicesByTo(int numOfDevices, String baseName) throws Exception { + for (int i = 0; i < numOfDevices; i++) { + Device device = buildSimpleDevice(baseName + i); + EntityRelation relation = createFromRelation(device, mainDevice, "CONTAINS"); + doPost("/api/relation", relation).andExpect(status().isOk()); + } + } + + private void assertFoundRelations(List relations, int numOfDevices) { + Assert.assertNotNull("Relations is not found!", relations); + Assert.assertEquals("List of found relations is not equal to number of created relations!", + numOfDevices, relations.size()); + } + + private void assertFoundList(String url, int numOfDevices) throws Exception { + @SuppressWarnings("unchecked") + List relations = doGet(url, List.class); + assertFoundRelations(relations, numOfDevices); + } + + private void assertRelationsInfosByFrom(List relationsInfos) { + for (EntityRelationInfo info : relationsInfos) { + Assert.assertEquals("Wrong FROM entityId!", mainDevice.getId(), info.getFrom()); + Assert.assertTrue("Wrong FROM name!", info.getToName().contains(BASE_DEVICE_NAME)); + Assert.assertEquals("Wrong relationType!", "CONTAINS", info.getType()); + } + } + + private void assertRelationsInfosByTo(List relationsInfos) { + for (EntityRelationInfo info : relationsInfos) { + Assert.assertEquals("Wrong TO entityId!", mainDevice.getId(), info.getTo()); + Assert.assertTrue("Wrong TO name!", info.getFromName().contains(BASE_DEVICE_NAME)); + Assert.assertEquals("Wrong relationType!", "CONTAINS", info.getType()); + } + } +} diff --git a/application/src/test/java/org/thingsboard/server/controller/sql/EntityRelationControllerSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/sql/EntityRelationControllerSqlTest.java new file mode 100644 index 0000000000..28b77a1785 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/sql/EntityRelationControllerSqlTest.java @@ -0,0 +1,8 @@ +package org.thingsboard.server.controller.sql; + +import org.thingsboard.server.controller.BaseEntityRelationControllerTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class EntityRelationControllerSqlTest extends BaseEntityRelationControllerTest { +} From 756f62062b2d85b1b7736c38b8db567e847b6f6d Mon Sep 17 00:00:00 2001 From: desoliture Date: Tue, 8 Feb 2022 14:54:11 +0200 Subject: [PATCH 06/13] fix headers --- .../BaseEntityRelationControllerTest.java | 15 +++++++++++++++ .../sql/EntityRelationControllerSqlTest.java | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityRelationControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityRelationControllerTest.java index 5f07c4ea54..364dad10e4 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityRelationControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityRelationControllerTest.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2021 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.server.controller; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/application/src/test/java/org/thingsboard/server/controller/sql/EntityRelationControllerSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/sql/EntityRelationControllerSqlTest.java index 28b77a1785..b8f2b35552 100644 --- a/application/src/test/java/org/thingsboard/server/controller/sql/EntityRelationControllerSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/sql/EntityRelationControllerSqlTest.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2021 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.server.controller.sql; import org.thingsboard.server.controller.BaseEntityRelationControllerTest; From a95f947738d6b0d9b3a109b85766ab191ede2b09 Mon Sep 17 00:00:00 2001 From: desoliture Date: Tue, 15 Mar 2022 15:40:34 +0200 Subject: [PATCH 07/13] change url string to uri object with encoding on `HttpClient` exchange --- .../java/org/thingsboard/rule/engine/rest/TbHttpClient.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index c2a6867f94..7d8a670b60 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -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 = UriComponentsBuilder.fromUriString(endpointUrl).build().encode().toUri(); ListenableFuture> future = httpClient.exchange( - endpointUrl, method, entity, String.class); + uri, method, entity, String.class); future.addCallback(new ListenableFutureCallback>() { @Override public void onFailure(Throwable throwable) { From f3a29aadf2f0087a2ed255629ca4ecd59fedb573 Mon Sep 17 00:00:00 2001 From: desoliture Date: Tue, 15 Mar 2022 20:30:40 +0200 Subject: [PATCH 08/13] add corresponding test --- .../rule/engine/rest/TbHttpClientTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java index 9834ef631d..4a2f085f81 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java @@ -21,13 +21,26 @@ import io.netty.channel.nio.NioEventLoopGroup; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.credentials.AnonymousCredentials; +import org.thingsboard.rule.engine.credentials.ClientCredentials; +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.util.Map; 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.BDDMockito.willCallRealMethod; +import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class TbHttpClientTest { @@ -58,4 +71,23 @@ public class TbHttpClientTest { eventLoop = client.getSharedOrCreateEventLoopGroup(null); assertThat(eventLoop, instanceOf(NioEventLoopGroup.class)); } + + @Test + public void testProcessMessageWithJsonInUrlVariable() throws Exception{ + var config = mock(TbRestApiCallNodeConfiguration.class); + when(config.getCredentials()).thenReturn(new AnonymousCredentials()); + when(config.getRequestMethod()).thenReturn("GET"); + when(config.getRestEndpointUrlPattern()) + .thenReturn("http://localhost/api?data=[{\\\"test\\\":\\\"test\\\"}]"); + + var httpClient = new TbHttpClient(config, eventLoop); + var ctx = mock(TbContext.class); + var msg = TbMsg.newMsg( + "Main", "GET", new DeviceId(EntityId.NULL_UUID), + TbMsgMetaData.EMPTY, "" + ); + + + httpClient.processMessage(ctx, msg); + } } \ No newline at end of file From a72534f461dc4d02bfae182032e5aad1bacf8f69 Mon Sep 17 00:00:00 2001 From: desoliture Date: Wed, 16 Mar 2022 22:42:46 +0200 Subject: [PATCH 09/13] fix test --- .../rule/engine/rest/TbHttpClientTest.java | 81 +++++++++++++++---- 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java index 4a2f085f81..1fb30b262a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java @@ -19,24 +19,32 @@ package org.thingsboard.rule.engine.rest; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.AsyncResult; +import org.springframework.web.client.AsyncRestTemplate; +import org.springframework.web.util.UriComponentsBuilder; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.credentials.AnonymousCredentials; -import org.thingsboard.rule.engine.credentials.ClientCredentials; 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.util.Map; +import java.net.URI; 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.doCallRealMethod; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -47,6 +55,9 @@ public class TbHttpClientTest { EventLoopGroup eventLoop; TbHttpClient client; + private final String ENDPOINT_URL = "http://localhost/api?data=[{\\\"test\\\":\\\"test\\\"}]"; + private final String GET_METHOD = "GET"; + @Before public void setUp() throws Exception { client = mock(TbHttpClient.class); @@ -73,21 +84,61 @@ public class TbHttpClientTest { } @Test - public void testProcessMessageWithJsonInUrlVariable() throws Exception{ - var config = mock(TbRestApiCallNodeConfiguration.class); - when(config.getCredentials()).thenReturn(new AnonymousCredentials()); - when(config.getRequestMethod()).thenReturn("GET"); - when(config.getRestEndpointUrlPattern()) - .thenReturn("http://localhost/api?data=[{\\\"test\\\":\\\"test\\\"}]"); + public void testProcessMessageWithJsonInUrlVariable() throws Exception { + var config = new TbRestApiCallNodeConfiguration() + .defaultConfiguration(); + config.setRequestMethod(GET_METHOD); + config.setRestEndpointUrlPattern(ENDPOINT_URL); + config.setUseSimpleClientHttpFactory(true); - var httpClient = new TbHttpClient(config, eventLoop); - var ctx = mock(TbContext.class); - var msg = TbMsg.newMsg( - "Main", "GET", new DeviceId(EntityId.NULL_UUID), - TbMsgMetaData.EMPTY, "" + var asyncRestTemplate = mock(AsyncRestTemplate.class); + var uriCaptor = ArgumentCaptor.forClass(URI.class); + + var responseEntity = new ResponseEntity<>( + "{}", + new HttpHeaders(), + HttpStatus.OK ); + when(asyncRestTemplate.exchange( + uriCaptor.capture(), + any(), + any(), + eq(String.class) + )).thenReturn(new AsyncResult<>(responseEntity)); + + 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); httpClient.processMessage(ctx, msg); + + verify(ctx, times(1)).transformMsg( + eq(msg), eq(msg.getType()), + eq(msg.getOriginator()), + eq(msg.getMetaData()), + eq(msg.getData()) + ); + verify(ctx, times(1)) + .tellSuccess(eq(successMsg)); + + URI uri = UriComponentsBuilder.fromUriString(ENDPOINT_URL).build().encode().toUri(); + Assert.assertEquals("URI encoding was not performed!!", uri, uriCaptor.getValue()); } } \ No newline at end of file From b299ee073094d130265bf97286dafd4b3f8adeab Mon Sep 17 00:00:00 2001 From: ivankozka Date: Tue, 5 Apr 2022 23:24:44 +0300 Subject: [PATCH 10/13] extract encoded uri building to the method, add unit tests for this method and fix http client test --- rule-engine/rule-engine-components/pom.xml | 11 ++ .../rule/engine/rest/TbHttpClient.java | 24 ++- .../rule/engine/rest/TbHttpClientTest.java | 145 +++++++++++++----- 3 files changed, 138 insertions(+), 42 deletions(-) diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index f06b4a7395..c0b1f409de 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -136,6 +136,17 @@ awaitility test + + org.mock-server + mockserver-netty + 5.13.1 + + + org.mock-server + mockserver-client-java + 5.13.1 + + org.cassandraunit cassandra-unit diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 7d8a670b60..e035d415ed 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -191,7 +191,7 @@ public class TbHttpClient { entity = new HttpEntity<>(msg.getData(), headers); } - URI uri = UriComponentsBuilder.fromUriString(endpointUrl).build().encode().toUri(); + URI uri = buildEncodedUri(endpointUrl); ListenableFuture> future = httpClient.exchange( uri, method, entity, String.class); future.addCallback(new ListenableFutureCallback>() { @@ -217,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 response) { TbMsgMetaData metaData = origMsg.getMetaData(); metaData.putValue(STATUS, response.getStatusCode().name()); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java index 1fb30b262a..f5c6977dd5 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java @@ -18,19 +18,15 @@ 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.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.scheduling.annotation.AsyncResult; +import org.mockito.Mockito; +import org.mockserver.integration.ClientAndServer; import org.springframework.web.client.AsyncRestTemplate; -import org.springframework.web.util.UriComponentsBuilder; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; @@ -38,7 +34,9 @@ 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; @@ -49,15 +47,15 @@ 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 { EventLoopGroup eventLoop; TbHttpClient client; - private final String ENDPOINT_URL = "http://localhost/api?data=[{\\\"test\\\":\\\"test\\\"}]"; - private final String GET_METHOD = "GET"; - @Before public void setUp() throws Exception { client = mock(TbHttpClient.class); @@ -83,29 +81,61 @@ public class TbHttpClientTest { 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(GET_METHOD); - config.setRestEndpointUrlPattern(ENDPOINT_URL); + config.setRequestMethod(method); + config.setRestEndpointUrlPattern(endpointUrl); config.setUseSimpleClientHttpFactory(true); - var asyncRestTemplate = mock(AsyncRestTemplate.class); - var uriCaptor = ArgumentCaptor.forClass(URI.class); - - var responseEntity = new ResponseEntity<>( - "{}", - new HttpHeaders(), - HttpStatus.OK - ); - - when(asyncRestTemplate.exchange( - uriCaptor.capture(), - any(), - any(), - eq(String.class) - )).thenReturn(new AsyncResult<>(responseEntity)); + var asyncRestTemplate = new AsyncRestTemplate(); var httpClient = new TbHttpClient(config, eventLoop); httpClient.setHttpClient(asyncRestTemplate); @@ -121,24 +151,57 @@ public class TbHttpClientTest { var ctx = mock(TbContext.class); when(ctx.transformMsg( - eq(msg), eq(msg.getType()), - eq(msg.getOriginator()), - eq(msg.getMetaData()), - eq(msg.getData()) - )).thenReturn(successMsg); - - httpClient.processMessage(ctx, msg); - - verify(ctx, times(1)).transformMsg( eq(msg), eq(msg.getType()), eq(msg.getOriginator()), eq(msg.getMetaData()), eq(msg.getData()) - ); - verify(ctx, times(1)) - .tellSuccess(eq(successMsg)); + )).thenReturn(successMsg); - URI uri = UriComponentsBuilder.fromUriString(ENDPOINT_URL).build().encode().toUri(); - Assert.assertEquals("URI encoding was not performed!!", uri, uriCaptor.getValue()); + 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) + ); + } + + } \ No newline at end of file From f3deb9c885e5bf2d841c311375bcdf96506bf4b7 Mon Sep 17 00:00:00 2001 From: ivankozka Date: Wed, 6 Apr 2022 18:36:39 +0300 Subject: [PATCH 11/13] extract mock-server dependencies to the parent pom --- pom.xml | 13 +++++++++++++ rule-engine/rule-engine-components/pom.xml | 2 -- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d572580132..2b1679fa89 100755 --- a/pom.xml +++ b/pom.xml @@ -129,6 +129,7 @@ 1.5.2 5.6.3 2.6.0 + 5.13.1 1.3.0 1.2.7 @@ -1875,6 +1876,18 @@ ${zeroturnaround.version} test + + org.mock-server + mockserver-netty + ${mock-server.version} + test + + + org.mock-server + mockserver-client-java + ${mock-server.version} + test + diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index c0b1f409de..4496afc7ab 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -139,12 +139,10 @@ org.mock-server mockserver-netty - 5.13.1 org.mock-server mockserver-client-java - 5.13.1 From 923dcff8e5d3b7c142c882a1b2ef97c60ea29ab5 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 22 Jun 2022 10:31:32 +0300 Subject: [PATCH 12/13] No more transaction cache --- .../server/cache/CacheSpecsMapTest.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/common/cache/src/test/java/org/thingsboard/server/cache/CacheSpecsMapTest.java b/common/cache/src/test/java/org/thingsboard/server/cache/CacheSpecsMapTest.java index 7e65d6a230..f561119f7f 100644 --- a/common/cache/src/test/java/org/thingsboard/server/cache/CacheSpecsMapTest.java +++ b/common/cache/src/test/java/org/thingsboard/server/cache/CacheSpecsMapTest.java @@ -21,8 +21,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cache.CacheManager; -import org.springframework.cache.transaction.TransactionAwareCacheDecorator; -import org.springframework.cache.transaction.TransactionAwareCacheManagerProxy; +import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.cache.support.SimpleCacheManager; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -45,14 +45,16 @@ public class CacheSpecsMapTest { CacheManager cacheManager; @Test - public void verifyTransactionAwareCacheManagerProxy() { - assertThat(cacheManager).isInstanceOf(TransactionAwareCacheManagerProxy.class); + public void verifyNotTransactionAwareCacheManagerProxy() { + // We no longer use built-in transaction support for the caches, because we have our own cache cleanup and transaction logic that implements CAS. + assertThat(cacheManager).isInstanceOf(SimpleCacheManager.class); } @Test - public void givenCacheConfig_whenCacheManagerReady_thenVerifyExistedCachesWithTransactionAwareCacheDecorator() { - assertThat(cacheManager.getCache("relations")).isInstanceOf(TransactionAwareCacheDecorator.class); - assertThat(cacheManager.getCache("devices")).isInstanceOf(TransactionAwareCacheDecorator.class); + public void givenCacheConfig_whenCacheManagerReady_thenVerifyExistedCachesWithNoTransactionAwareCacheDecorator() { + // We no longer use built-in transaction support for the caches, because we have our own cache cleanup and transaction logic that implements CAS. + assertThat(cacheManager.getCache("relations")).isInstanceOf(CaffeineCache.class); + assertThat(cacheManager.getCache("devices")).isInstanceOf(CaffeineCache.class); } @Test From c744192d37bc2218b1cdb3124bc607e6d335e0f0 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 22 Jun 2022 11:29:44 +0300 Subject: [PATCH 13/13] Merge PR #6262 --- .../org/thingsboard/rule/engine/rest/TbHttpClientTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java index f5c6977dd5..f7a1d83ac9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java @@ -140,10 +140,7 @@ public class TbHttpClientTest { var httpClient = new TbHttpClient(config, eventLoop); httpClient.setHttpClient(asyncRestTemplate); - var msg = TbMsg.newMsg( - "Main", "GET", new DeviceId(EntityId.NULL_UUID), - TbMsgMetaData.EMPTY, "{}" - ); + var msg = TbMsg.newMsg("GET", new DeviceId(EntityId.NULL_UUID), TbMsgMetaData.EMPTY, "{}"); var successMsg = TbMsg.newMsg( "SUCCESS", msg.getOriginator(), msg.getMetaData(), msg.getData()