diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java index 8100c097f6..a5af165699 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java @@ -25,12 +25,14 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.dao.audit.AuditLogService; @@ -74,6 +76,39 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { Mockito.reset(tbClusterService, auditLogService); } + protected void testNotifyEntityAllOneTimeRelation(EntityRelation relation, + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, Object... additionalInfo) { + int cntTime = 1; + Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId), + Mockito.isNull(), Mockito.isNull(), Mockito.any(), Mockito.eq(EdgeEventType.RELATION), + Mockito.eq(edgeTypeByActionType(actionType))); + ArgumentMatcher matcherOriginatorId = argument -> argument.equals(relation.getTo()); + ArgumentMatcher matcherEntityClassEquals = Objects::isNull; + testLogEntityActionAdditionalInfo(matcherEntityClassEquals, matcherOriginatorId, tenantId, customerId, userId, userName, actionType, cntTime, + extractMatcherAdditionalInfo(additionalInfo)); + testPushMsgToRuleEngineNever(relation.getTo()); + matcherOriginatorId = argument -> argument.equals(relation.getFrom()); + testLogEntityActionAdditionalInfo(matcherEntityClassEquals, matcherOriginatorId, tenantId, customerId, userId, userName, actionType, cntTime, + extractMatcherAdditionalInfo(additionalInfo)); + testPushMsgToRuleEngineNever(relation.getFrom()); + Mockito.reset(tbClusterService, auditLogService); + } + + protected void testNotifyEntityAllManyRelation(EntityRelation relation, + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, int cntTime) { + Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId), + Mockito.isNull(), Mockito.isNull(), Mockito.any(), Mockito.eq(EdgeEventType.RELATION), + Mockito.eq(edgeTypeByActionType(actionType))); + ArgumentMatcher matcherOriginatorId = argument -> argument.getClass().equals(relation.getFrom().getClass()); + ArgumentMatcher matcherEntityClassEquals = Objects::isNull; + testLogEntityActionAdditionalInfoAny(matcherEntityClassEquals, matcherOriginatorId, tenantId, customerId, userId, + userName, actionType, cntTime * 2, 1); + testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, cntTime); + Mockito.reset(tbClusterService, auditLogService); + } + protected void testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(HasName entity, EntityId entityId, EntityId originatorId, TenantId tenantId, CustomerId customerId, UserId userId, String userName, ActionType actionType, Object... additionalInfo) { @@ -100,7 +135,11 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { testNotificationMsgToEdgeServiceNever(entityId); testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo); ArgumentMatcher matcherOriginatorId = argument -> argument.equals(originatorId); - testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, cntTime); + if (ActionType.RELATIONS_DELETED.equals(actionType)) { + testPushMsgToRuleEngineNever(originatorId); + } else { + testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, cntTime); + } Mockito.reset(tbClusterService, auditLogService); } @@ -160,8 +199,8 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { } protected void testNotifyEntityBroadcastEntityStateChangeEventOneTime(HasName entity, EntityId entityId, EntityId originatorId, - TenantId tenantId, CustomerId customerId, UserId userId, String userName, - ActionType actionType, Object... additionalInfo) { + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, Object... additionalInfo) { int cntTime = 1; testSendNotificationMsgToEdgeServiceTime(entityId, tenantId, actionType, cntTime); testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo); @@ -184,8 +223,8 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { } protected void testNotifyEntityBroadcastEntityStateChangeEventManyMsgToEdgeServiceNever(HasName entity, HasName originator, - TenantId tenantId, CustomerId customerId, UserId userId, String userName, - ActionType actionType, int cntTime, int cntAdditionalInfo) { + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, int cntTime, int cntAdditionalInfo) { EntityId entityId = createEntityId_NULL_UUID(entity); EntityId originatorId = createEntityId_NULL_UUID(originator); testNotificationMsgToEdgeServiceNever(entityId); @@ -266,8 +305,10 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { } private void testLogEntityActionNever(EntityId entityId, HasName entity) { + ArgumentMatcher matcherEntity = entity == null ? Objects::isNull : + argument -> argument.getClass().equals(entity.getClass()); Mockito.verify(auditLogService, never()).logEntityAction(Mockito.any(), Mockito.any(), - Mockito.any(), Mockito.any(), Mockito.any(entityId.getClass()), Mockito.any(entity.getClass()), + Mockito.any(), Mockito.any(), Mockito.any(entityId.getClass()), Mockito.argThat(matcherEntity), Mockito.any(), Mockito.any()); } @@ -307,7 +348,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { private void testLogEntityAction(HasName entity, EntityId originatorId, TenantId tenantId, CustomerId customerId, UserId userId, String userName, ActionType actionType, int cntTime, Object... additionalInfo) { - ArgumentMatcher matcherEntityEquals = argument -> argument.equals(entity); + ArgumentMatcher matcherEntityEquals = entity == null ? Objects::isNull : argument -> argument.equals(entity); ArgumentMatcher matcherOriginatorId = argument -> argument.equals(originatorId); testLogEntityActionAdditionalInfo(matcherEntityEquals, matcherOriginatorId, tenantId, customerId, userId, userName, actionType, cntTime, extractMatcherAdditionalInfo(additionalInfo)); @@ -521,11 +562,11 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { return EntityIdFactory.getByTypeAndUuid(entityClassToEntityTypeName(entity), ModelConstants.NULL_UUID); } - protected String msgErrorFieldLength(String fieldName){ + protected String msgErrorFieldLength(String fieldName) { return "length of " + fieldName + " must be equal or less than 255"; } - protected String msgErrorNoFound(String entityClassName, String assetIdStr){ + protected String msgErrorNoFound(String entityClassName, String assetIdStr) { return entityClassName + " with id [" + assetIdStr + "] is not found"; } 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 65558e1955..08a157484f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityRelationControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityRelationControllerTest.java @@ -22,6 +22,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; @@ -29,6 +30,8 @@ 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.audit.ActionType; +import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelationInfo; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; @@ -41,7 +44,9 @@ import org.thingsboard.server.dao.relation.RelationService; import java.util.Collections; import java.util.List; +import java.util.UUID; +import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j @@ -93,8 +98,10 @@ public abstract class BaseEntityRelationControllerTest extends AbstractControlle @Test public void testSaveAndFindRelation() throws Exception { Device device = buildSimpleDevice("Test device 1"); - EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + + Mockito.reset(tbClusterService, auditLogService); + doPost("/api/relation", relation).andExpect(status().isOk()); String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", @@ -106,12 +113,73 @@ public abstract class BaseEntityRelationControllerTest extends AbstractControlle Assert.assertNotNull("Relation is not found!", foundRelation); Assert.assertEquals("Found relation is not equals origin!", relation, foundRelation); + + testNotifyEntityAllOneTimeRelation(foundRelation, + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.RELATION_ADD_OR_UPDATE, foundRelation); + } + + @Test + public void testSaveWithDeviceFromNotCreated() throws Exception { + Device device = new Device(); + device.setName("Test device 2"); + device.setType("default"); + EntityRelation relation = createFromRelation(device, mainDevice, "CONTAINS"); + + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/relation", relation) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Parameter entityId can't be empty!"))); + + testNotifyEntityNever(mainDevice.getId(), null); + } + + @Test + public void testSaveWithDeviceToNotCreated() throws Exception { + Device device = new Device(); + device.setName("Test device 2"); + device.setType("default"); + EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/relation", relation) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Parameter entityId can't be empty!"))); + + testNotifyEntityNever(mainDevice.getId(), null); + } + + @Test + public void testSaveWithDeviceToMissing() throws Exception { + Device device = new Device(); + device.setName("Test device 2"); + device.setType("default"); + device.setId(new DeviceId(UUID.randomUUID())); + EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/relation", relation) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Device", device.getId().getId().toString())))); + + testNotifyEntityNever(mainDevice.getId(), null); } @Test public void testSaveAndFindRelationsByFrom() throws Exception { final int numOfDevices = 30; + + Mockito.reset(tbClusterService, auditLogService); + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + + EntityRelation relationTest = createFromRelation(mainDevice, mainDevice, "TEST_NOTIFY_ENTITY"); + testNotifyEntityAllManyRelation(relationTest, savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.RELATION_ADD_OR_UPDATE, numOfDevices); + String url = String.format("/api/relations?fromId=%s&fromType=%s", mainDevice.getUuidId(), EntityType.DEVICE ); @@ -136,32 +204,73 @@ public abstract class BaseEntityRelationControllerTest extends AbstractControlle createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); Device device = buildSimpleDevice("Unique dummy test device "); - EntityRelation relation = createFromRelation(mainDevice, device, "TEST"); + String relationType = "TEST"; + EntityRelation relation = createFromRelation(mainDevice, device, relationType); doPost("/api/relation", relation).andExpect(status().isOk()); String url = String.format("/api/relations?fromId=%s&fromType=%s&relationType=%s", - mainDevice.getUuidId(), EntityType.DEVICE, "TEST" + mainDevice.getUuidId(), EntityType.DEVICE, relationType ); assertFoundList(url, 1); } + @Test + public void testSaveAndFindRelationsByFromWithRelationTypeOther() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + + Device device = buildSimpleDevice("Unique dummy test device "); + String relationType = "TEST"; + EntityRelation relation = createFromRelation(mainDevice, device, relationType); + + doPost("/api/relation", relation).andExpect(status().isOk()); + + String relationTypeOther = "TEST_OTHER"; + String url = String.format("/api/relations?fromId=%s&fromType=%s&relationType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, relationTypeOther + ); + + assertFoundList(url, 0); + } + @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"); + String relationType = "TEST"; + EntityRelation relation = createFromRelation(device, mainDevice, relationType); doPost("/api/relation", relation).andExpect(status().isOk()); String url = String.format("/api/relations?toId=%s&toType=%s&relationType=%s", - mainDevice.getUuidId(), EntityType.DEVICE, "TEST" + mainDevice.getUuidId(), EntityType.DEVICE, relationType ); assertFoundList(url, 1); } + + @Test + public void testSaveAndFindRelationsByToWithRelationTypeOther() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + + Device device = buildSimpleDevice("Unique dummy test device "); + String relationType = "TEST"; + EntityRelation relation = createFromRelation(device, mainDevice, relationType); + + doPost("/api/relation", relation).andExpect(status().isOk()); + + String relationTypeOther = "TEST_OTHER"; + String url = String.format("/api/relations?toId=%s&toType=%s&relationType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, relationTypeOther + ); + + assertFoundList(url, 0); + } + @Test public void testFindRelationsInfoByFrom() throws Exception { final int numOfDevices = 30; @@ -171,7 +280,7 @@ public abstract class BaseEntityRelationControllerTest extends AbstractControlle ); List relationsInfos = - JacksonUtil.convertValue(doGet(url, JsonNode.class), new TypeReference>() { + JacksonUtil.convertValue(doGet(url, JsonNode.class), new TypeReference<>() { }); Assert.assertNotNull("Relations is not found!", relationsInfos); @@ -190,7 +299,7 @@ public abstract class BaseEntityRelationControllerTest extends AbstractControlle ); List relationsInfos = - JacksonUtil.convertValue(doGet(url, JsonNode.class), new TypeReference>() { + JacksonUtil.convertValue(doGet(url, JsonNode.class), new TypeReference<>() { }); Assert.assertNotNull("Relations is not found!", relationsInfos); @@ -217,10 +326,62 @@ public abstract class BaseEntityRelationControllerTest extends AbstractControlle Assert.assertNotNull("Relation is not found!", foundRelation); Assert.assertEquals("Found relation is not equals origin!", relation, foundRelation); + Mockito.reset(tbClusterService, auditLogService); + doDelete(url).andExpect(status().isOk()); + + testNotifyEntityAllOneTimeRelation(foundRelation, + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.RELATION_DELETED, foundRelation); + doGet(url).andExpect(status().is4xxClientError()); } + @Test + public void testDeleteRelationWithOtherFromDeviceError() throws Exception { + Device device = buildSimpleDevice("Test device 1"); + + EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + doPost("/api/relation", relation).andExpect(status().isOk()); + + Device device2 = buildSimpleDevice("Test device 2"); + String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", + device2.getUuidId(), EntityType.DEVICE, + "CONTAINS", device.getUuidId(), EntityType.DEVICE + ); + + Mockito.reset(tbClusterService, auditLogService); + + doDelete(url) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString("Requested item wasn't found!"))); + + testNotifyEntityNever(mainDevice.getId(), null); + + } + + @Test + public void testDeleteRelationWithOtherToDeviceError() throws Exception { + Device device = buildSimpleDevice("Test device 1"); + + EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + doPost("/api/relation", relation).andExpect(status().isOk()); + + Device device2 = buildSimpleDevice("Test device 2"); + String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, + "CONTAINS", device2.getUuidId(), EntityType.DEVICE + ); + + Mockito.reset(tbClusterService, auditLogService); + + doDelete(url) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString("Requested item wasn't found!"))); + + testNotifyEntityNever(mainDevice.getId(), null); + } + @Test public void testDeleteRelations() throws Exception { final int numOfDevices = 30; @@ -240,8 +401,15 @@ public abstract class BaseEntityRelationControllerTest extends AbstractControlle String url = String.format("/api/relations?entityId=%s&entityType=%s", mainDevice.getUuidId(), EntityType.DEVICE ); + + Mockito.reset(tbClusterService, auditLogService); + doDelete(url).andExpect(status().isOk()); + testNotifyEntityOneTimeMsgToEdgeServiceNever(null, mainDevice.getId(), mainDevice.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.RELATIONS_DELETED); + Assert.assertTrue( "Performed deletion of all relations but some relations were found!", doGet(urlTo, List.class).isEmpty() @@ -263,14 +431,15 @@ public abstract class BaseEntityRelationControllerTest extends AbstractControlle 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>() {} + new TypeReference>() { + } ); assertFoundRelations(relations, numOfDevices); @@ -294,14 +463,15 @@ public abstract class BaseEntityRelationControllerTest extends AbstractControlle List relations = readResponse( doPost("/api/relations", query).andExpect(status().isOk()), - new TypeReference>() {} + new TypeReference<>() { + } ); assertFoundRelations(relations, numOfDevices); } @Test - public void testFindRelationsInfoByFromQuery() throws Exception{ + public void testFindRelationsInfoByFromQuery() throws Exception { final int numOfDevices = 30; createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); @@ -318,14 +488,15 @@ public abstract class BaseEntityRelationControllerTest extends AbstractControlle List relationsInfo = readResponse( doPost("/api/relations/info", query).andExpect(status().isOk()), - new TypeReference>() {} + new TypeReference<>() { + } ); assertRelationsInfosByFrom(relationsInfo); } @Test - public void testFindRelationsInfoByToQuery() throws Exception{ + public void testFindRelationsInfoByToQuery() throws Exception { final int numOfDevices = 30; createDevicesByTo(numOfDevices, BASE_DEVICE_NAME); @@ -342,14 +513,15 @@ public abstract class BaseEntityRelationControllerTest extends AbstractControlle List relationsInfo = readResponse( doPost("/api/relations/info", query).andExpect(status().isOk()), - new TypeReference>() {} + new TypeReference<>() { + } ); assertRelationsInfosByTo(relationsInfo); } @Test - public void testCreateRelationFromTenantToDevice() throws Exception{ + public void testCreateRelationFromTenantToDevice() throws Exception { EntityRelation relation = new EntityRelation(tenantAdmin.getTenantId(), mainDevice.getId(), "CONTAINS"); doPost("/api/relation", relation).andExpect(status().isOk()); @@ -365,7 +537,7 @@ public abstract class BaseEntityRelationControllerTest extends AbstractControlle } @Test - public void testCreateRelationFromDeviceToTenant() throws Exception{ + public void testCreateRelationFromDeviceToTenant() throws Exception { EntityRelation relation = new EntityRelation(mainDevice.getId(), tenantAdmin.getTenantId(), "CONTAINS"); doPost("/api/relation", relation).andExpect(status().isOk()); @@ -380,6 +552,28 @@ public abstract class BaseEntityRelationControllerTest extends AbstractControlle Assert.assertEquals("Found relation is not equals origin!", relation, foundRelation); } + @Test + public void testSaveAndFindRelationDifferentTenant() 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 + ); + + loginDifferentTenant(); + + doGet(url) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Device", relation.getFrom().getId().toString())))); + + + deleteDifferentTenant(); + } + private Device buildSimpleDevice(String name) throws Exception { Device device = new Device(); device.setName(name);