Browse Source

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

pull/6859/head
Andrii Shvaika 4 years ago
parent
commit
240e1ffc3b
  1. 4
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  2. 4
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  3. 4
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  4. 22
      application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java
  5. 2
      application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java
  6. 2
      application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java
  8. 3
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java
  9. 10
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
  10. 119
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java
  11. 6
      application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java
  12. 4
      application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java
  13. 254
      application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
  14. 36
      application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
  15. 123
      application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java
  16. 47
      application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java
  17. 294
      application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java
  18. 313
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
  19. 6
      common/data/src/main/java/org/thingsboard/server/common/data/queue/Queue.java
  20. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java
  21. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportRequest.java
  22. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportResult.java
  23. 3
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/BranchInfo.java
  24. 1
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/VersionLoadRequest.java
  25. 6
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java
  26. 24
      dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
  27. 10
      dao/src/main/java/org/thingsboard/server/dao/service/validator/QueueValidator.java
  28. 9
      dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java
  29. 28
      dao/src/test/java/org/thingsboard/server/dao/service/BaseQueueServiceTest.java
  30. 74
      docker/compose-utils.sh
  31. 21
      docker/docker-check-log-folders.sh
  32. 39
      docker/docker-create-log-folders.sh
  33. 2
      docker/docker-install-tb.sh
  34. 2
      docker/docker-start-services.sh
  35. 2
      docker/docker-upgrade-tb.sh
  36. 266
      rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
  37. 6
      ui-ngx/src/app/core/http/entities-version-control.service.ts
  38. 3
      ui-ngx/src/app/modules/home/components/queue/queue-form.component.html
  39. 4
      ui-ngx/src/app/modules/home/components/queue/queue-form.component.ts
  40. 4
      ui-ngx/src/app/modules/home/components/vc/complex-version-load.component.ts
  41. 6
      ui-ngx/src/app/modules/home/components/vc/entity-version-diff.component.ts
  42. 4
      ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.ts
  43. 3
      ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts
  44. 1
      ui-ngx/src/app/shared/models/vc.models.ts
  45. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

4
application/src/main/java/org/thingsboard/server/controller/AssetController.java

@ -51,8 +51,8 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.asset.AssetBulkImportService;
import org.thingsboard.server.service.sync.ie.importing.csv.BulkImportRequest;
import org.thingsboard.server.service.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.service.entitiy.asset.TbAssetService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;

4
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -66,8 +66,8 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.device.DeviceBulkImportService;
import org.thingsboard.server.service.sync.ie.importing.csv.BulkImportRequest;
import org.thingsboard.server.service.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.service.entitiy.device.TbDeviceService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;

4
application/src/main/java/org/thingsboard/server/controller/EdgeController.java

@ -53,8 +53,8 @@ import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeBulkImportService;
import org.thingsboard.server.service.entitiy.edge.TbEdgeService;
import org.thingsboard.server.service.sync.ie.importing.csv.BulkImportRequest;
import org.thingsboard.server.service.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;

22
application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java

@ -310,15 +310,13 @@ public class EntitiesVersionControlController extends BaseController {
"Each entity item in the result has `externalId` property. " +
"Entities order will be the same as in the repository." +
TENANT_AUTHORITY_PARAGRAPH)
@GetMapping(value = "/entity/{entityType}/{versionId}", params = {"branch"})
@GetMapping(value = "/entity/{entityType}/{versionId}")
public DeferredResult<List<VersionedEntityInfo>> listEntitiesAtVersion(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true)
@PathVariable EntityType entityType,
@ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true)
@PathVariable String versionId,
@ApiParam(value = BRANCH_PARAM_DESCRIPTION, required = true)
@RequestParam String branch) throws Exception {
@PathVariable String versionId) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
return wrapFuture(versionControlService.listEntitiesAtVersion(getTenantId(), branch, versionId, entityType));
return wrapFuture(versionControlService.listEntitiesAtVersion(getTenantId(), versionId, entityType));
}
@ApiOperation(value = "List all entities at version (listAllEntitiesAtVersion)", notes = "" +
@ -326,13 +324,11 @@ public class EntitiesVersionControlController extends BaseController {
"Response type is the same as for listAllEntitiesAtVersion API method. \n" +
"Returned entities order will be the same as in the repository." +
TENANT_AUTHORITY_PARAGRAPH)
@GetMapping(value = "/entity/{versionId}", params = {"branch"})
@GetMapping(value = "/entity/{versionId}")
public DeferredResult<List<VersionedEntityInfo>> listAllEntitiesAtVersion(@ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true)
@PathVariable String versionId,
@ApiParam(value = BRANCH_PARAM_DESCRIPTION, required = true)
@RequestParam String branch) throws Exception {
@PathVariable String versionId) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
return wrapFuture(versionControlService.listAllEntitiesAtVersion(getTenantId(), branch, versionId));
return wrapFuture(versionControlService.listAllEntitiesAtVersion(getTenantId(), versionId));
}
@ApiOperation(value = "Get entity data info (getEntityDataInfo)", notes = "" +
@ -357,18 +353,16 @@ public class EntitiesVersionControlController extends BaseController {
"Returns an object with current entity data and the one at a specific version. " +
"Entity data structure is the same as stored in a repository. " +
TENANT_AUTHORITY_PARAGRAPH)
@GetMapping(value = "/diff/{entityType}/{internalEntityUuid}", params = {"branch", "versionId"})
@GetMapping(value = "/diff/{entityType}/{internalEntityUuid}", params = {"versionId"})
public DeferredResult<EntityDataDiff> compareEntityDataToVersion(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true)
@PathVariable EntityType entityType,
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true)
@PathVariable UUID internalEntityUuid,
@ApiParam(value = BRANCH_PARAM_DESCRIPTION)
@RequestParam String branch,
@ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true)
@RequestParam String versionId) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, internalEntityUuid);
return wrapFuture(versionControlService.compareEntityDataToVersion(getCurrentUser(), branch, entityId, versionId));
return wrapFuture(versionControlService.compareEntityDataToVersion(getCurrentUser(), entityId, versionId));
}
@ApiOperation(value = "Load entities version (loadEntitiesVersion)", notes = "" +

2
application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java

@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService;
import org.thingsboard.server.service.sync.ie.importing.csv.BulkImportColumnType;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType;
import org.thingsboard.server.service.entitiy.asset.TbAssetService;
import org.thingsboard.server.service.security.model.SecurityUser;

2
application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java

@ -50,7 +50,7 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService;
import org.thingsboard.server.service.sync.ie.importing.csv.BulkImportColumnType;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType;
import org.thingsboard.server.service.entitiy.device.TbDeviceService;
import org.thingsboard.server.service.security.model.SecurityUser;

2
application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java

@ -29,7 +29,7 @@ import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService;
import org.thingsboard.server.service.sync.ie.importing.csv.BulkImportColumnType;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType;
import org.thingsboard.server.service.entitiy.edge.TbEdgeService;
import org.thingsboard.server.service.security.model.SecurityUser;

3
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java

@ -39,6 +39,9 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.controller.BaseController;

10
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java

@ -241,13 +241,13 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
}
@Override
public ListenableFuture<List<VersionedEntityInfo>> listEntitiesAtVersion(TenantId tenantId, String branch, String versionId, EntityType entityType) throws Exception {
return gitServiceQueue.listEntitiesAtVersion(tenantId, branch, versionId, entityType);
public ListenableFuture<List<VersionedEntityInfo>> listEntitiesAtVersion(TenantId tenantId, String versionId, EntityType entityType) throws Exception {
return gitServiceQueue.listEntitiesAtVersion(tenantId, versionId, entityType);
}
@Override
public ListenableFuture<List<VersionedEntityInfo>> listAllEntitiesAtVersion(TenantId tenantId, String branch, String versionId) throws Exception {
return gitServiceQueue.listEntitiesAtVersion(tenantId, branch, versionId);
public ListenableFuture<List<VersionedEntityInfo>> listAllEntitiesAtVersion(TenantId tenantId, String versionId) throws Exception {
return gitServiceQueue.listEntitiesAtVersion(tenantId, versionId);
}
@SuppressWarnings({"UnstableApiUsage", "rawtypes"})
@ -451,7 +451,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
}
@Override
public ListenableFuture<EntityDataDiff> compareEntityDataToVersion(User user, String branch, EntityId entityId, String versionId) throws Exception {
public ListenableFuture<EntityDataDiff> compareEntityDataToVersion(User user, EntityId entityId, String versionId) throws Exception {
HasId<EntityId> entity = exportableEntitiesService.findEntityByTenantIdAndId(user.getTenantId(), entityId);
if (!(entity instanceof ExportableEntity)) throw new IllegalArgumentException("Unsupported entity type");

119
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java

@ -245,7 +245,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
}
@Override
public ListenableFuture<List<VersionedEntityInfo>> listEntitiesAtVersion(TenantId tenantId, String branch, String versionId, EntityType entityType) {
public ListenableFuture<List<VersionedEntityInfo>> listEntitiesAtVersion(TenantId tenantId, String versionId, EntityType entityType) {
return listEntitiesAtVersion(tenantId, ListEntitiesRequestMsg.newBuilder()
.setVersionId(versionId)
.setEntityType(entityType.name())
@ -253,7 +253,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
}
@Override
public ListenableFuture<List<VersionedEntityInfo>> listEntitiesAtVersion(TenantId tenantId, String branch, String versionId) {
public ListenableFuture<List<VersionedEntityInfo>> listEntitiesAtVersion(TenantId tenantId, String versionId) {
return listEntitiesAtVersion(tenantId, ListEntitiesRequestMsg.newBuilder()
.setVersionId(versionId)
.build());
@ -385,66 +385,71 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
if (!StringUtils.isEmpty(vcResponseMsg.getError())) {
future.setException(new RuntimeException(vcResponseMsg.getError()));
} else {
if (vcResponseMsg.hasGenericResponse()) {
future.set(null);
} else if (vcResponseMsg.hasCommitResponse()) {
var commitResponse = vcResponseMsg.getCommitResponse();
var commitResult = new VersionCreationResult();
if (commitResponse.getTs() > 0) {
commitResult.setVersion(new EntityVersion(commitResponse.getTs(), commitResponse.getCommitId(), commitResponse.getName(), commitResponse.getAuthor()));
}
commitResult.setAdded(commitResponse.getAdded());
commitResult.setRemoved(commitResponse.getRemoved());
commitResult.setModified(commitResponse.getModified());
commitResult.setDone(true);
((CommitGitRequest) request).getFuture().set(commitResult);
} else if (vcResponseMsg.hasListBranchesResponse()) {
var listBranchesResponse = vcResponseMsg.getListBranchesResponse();
((ListBranchesGitRequest) request).getFuture().set(listBranchesResponse.getBranchesList().stream().map(this::getBranchInfo).collect(Collectors.toList()));
} else if (vcResponseMsg.hasListEntitiesResponse()) {
var listEntitiesResponse = vcResponseMsg.getListEntitiesResponse();
((ListEntitiesGitRequest) request).getFuture().set(
listEntitiesResponse.getEntitiesList().stream().map(this::getVersionedEntityInfo).collect(Collectors.toList()));
} else if (vcResponseMsg.hasListVersionsResponse()) {
var listVersionsResponse = vcResponseMsg.getListVersionsResponse();
((ListVersionsGitRequest) request).getFuture().set(toPageData(listVersionsResponse));
} else if (vcResponseMsg.hasEntityContentResponse()) {
TransportProtos.EntityContentResponseMsg responseMsg = vcResponseMsg.getEntityContentResponse();
log.trace("[{}] received chunk {} for 'getEntity'", responseMsg.getChunkedMsgId(), responseMsg.getChunkIndex());
var joined = joinChunks(requestId, responseMsg, 1);
if (joined.isPresent()) {
log.trace("[{}] collected all chunks for 'getEntity'", responseMsg.getChunkedMsgId());
((EntityContentGitRequest) request).getFuture().set(joined.get().get(0));
} else {
completed = false;
}
} else if (vcResponseMsg.hasEntitiesContentResponse()) {
TransportProtos.EntitiesContentResponseMsg responseMsg = vcResponseMsg.getEntitiesContentResponse();
TransportProtos.EntityContentResponseMsg item = responseMsg.getItem();
if (responseMsg.getItemsCount() > 0) {
var joined = joinChunks(requestId, item, responseMsg.getItemsCount());
try {
if (vcResponseMsg.hasGenericResponse()) {
future.set(null);
} else if (vcResponseMsg.hasCommitResponse()) {
var commitResponse = vcResponseMsg.getCommitResponse();
var commitResult = new VersionCreationResult();
if (commitResponse.getTs() > 0) {
commitResult.setVersion(new EntityVersion(commitResponse.getTs(), commitResponse.getCommitId(), commitResponse.getName(), commitResponse.getAuthor()));
}
commitResult.setAdded(commitResponse.getAdded());
commitResult.setRemoved(commitResponse.getRemoved());
commitResult.setModified(commitResponse.getModified());
commitResult.setDone(true);
((CommitGitRequest) request).getFuture().set(commitResult);
} else if (vcResponseMsg.hasListBranchesResponse()) {
var listBranchesResponse = vcResponseMsg.getListBranchesResponse();
((ListBranchesGitRequest) request).getFuture().set(listBranchesResponse.getBranchesList().stream().map(this::getBranchInfo).collect(Collectors.toList()));
} else if (vcResponseMsg.hasListEntitiesResponse()) {
var listEntitiesResponse = vcResponseMsg.getListEntitiesResponse();
((ListEntitiesGitRequest) request).getFuture().set(
listEntitiesResponse.getEntitiesList().stream().map(this::getVersionedEntityInfo).collect(Collectors.toList()));
} else if (vcResponseMsg.hasListVersionsResponse()) {
var listVersionsResponse = vcResponseMsg.getListVersionsResponse();
((ListVersionsGitRequest) request).getFuture().set(toPageData(listVersionsResponse));
} else if (vcResponseMsg.hasEntityContentResponse()) {
TransportProtos.EntityContentResponseMsg responseMsg = vcResponseMsg.getEntityContentResponse();
log.trace("[{}] received chunk {} for 'getEntity'", responseMsg.getChunkedMsgId(), responseMsg.getChunkIndex());
var joined = joinChunks(requestId, responseMsg, 1);
if (joined.isPresent()) {
((EntitiesContentGitRequest) request).getFuture().set(joined.get());
log.trace("[{}] collected all chunks for 'getEntity'", responseMsg.getChunkedMsgId());
((EntityContentGitRequest) request).getFuture().set(joined.get().get(0));
} else {
completed = false;
}
} else {
((EntitiesContentGitRequest) request).getFuture().set(Collections.emptyList());
} else if (vcResponseMsg.hasEntitiesContentResponse()) {
TransportProtos.EntitiesContentResponseMsg responseMsg = vcResponseMsg.getEntitiesContentResponse();
TransportProtos.EntityContentResponseMsg item = responseMsg.getItem();
if (responseMsg.getItemsCount() > 0) {
var joined = joinChunks(requestId, item, responseMsg.getItemsCount());
if (joined.isPresent()) {
((EntitiesContentGitRequest) request).getFuture().set(joined.get());
} else {
completed = false;
}
} else {
((EntitiesContentGitRequest) request).getFuture().set(Collections.emptyList());
}
} else if (vcResponseMsg.hasVersionsDiffResponse()) {
TransportProtos.VersionsDiffResponseMsg diffResponse = vcResponseMsg.getVersionsDiffResponse();
List<EntityVersionsDiff> entityVersionsDiffList = diffResponse.getDiffList().stream()
.map(diff -> EntityVersionsDiff.builder()
.externalId(EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(diff.getEntityType()),
new UUID(diff.getEntityIdMSB(), diff.getEntityIdLSB())))
.entityDataAtVersion1(StringUtils.isNotEmpty(diff.getEntityDataAtVersion1()) ?
toData(diff.getEntityDataAtVersion1()) : null)
.entityDataAtVersion2(StringUtils.isNotEmpty(diff.getEntityDataAtVersion2()) ?
toData(diff.getEntityDataAtVersion2()) : null)
.rawDiff(diff.getRawDiff())
.build())
.collect(Collectors.toList());
((VersionsDiffGitRequest) request).getFuture().set(entityVersionsDiffList);
}
} else if (vcResponseMsg.hasVersionsDiffResponse()) {
TransportProtos.VersionsDiffResponseMsg diffResponse = vcResponseMsg.getVersionsDiffResponse();
List<EntityVersionsDiff> entityVersionsDiffList = diffResponse.getDiffList().stream()
.map(diff -> EntityVersionsDiff.builder()
.externalId(EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(diff.getEntityType()),
new UUID(diff.getEntityIdMSB(), diff.getEntityIdLSB())))
.entityDataAtVersion1(StringUtils.isNotEmpty(diff.getEntityDataAtVersion1()) ?
toData(diff.getEntityDataAtVersion1()) : null)
.entityDataAtVersion2(StringUtils.isNotEmpty(diff.getEntityDataAtVersion2()) ?
toData(diff.getEntityDataAtVersion2()) : null)
.rawDiff(diff.getRawDiff())
.build())
.collect(Collectors.toList());
((VersionsDiffGitRequest) request).getFuture().set(entityVersionsDiffList);
} catch (Exception e) {
future.setException(e);
throw e;
}
}
if (completed) {

6
application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java

@ -49,15 +49,15 @@ public interface EntitiesVersionControlService {
ListenableFuture<PageData<EntityVersion>> listVersions(TenantId tenantId, String branch, PageLink pageLink) throws Exception;
ListenableFuture<List<VersionedEntityInfo>> listEntitiesAtVersion(TenantId tenantId, String branch, String versionId, EntityType entityType) throws Exception;
ListenableFuture<List<VersionedEntityInfo>> listEntitiesAtVersion(TenantId tenantId, String versionId, EntityType entityType) throws Exception;
ListenableFuture<List<VersionedEntityInfo>> listAllEntitiesAtVersion(TenantId tenantId, String branch, String versionId) throws Exception;
ListenableFuture<List<VersionedEntityInfo>> listAllEntitiesAtVersion(TenantId tenantId, String versionId) throws Exception;
UUID loadEntitiesVersion(User user, VersionLoadRequest request) throws Exception;
VersionLoadResult getVersionLoadStatus(User user, UUID requestId) throws ThingsboardException;
ListenableFuture<EntityDataDiff> compareEntityDataToVersion(User user, String branch, EntityId entityId, String versionId) throws Exception;
ListenableFuture<EntityDataDiff> compareEntityDataToVersion(User user, EntityId entityId, String versionId) throws Exception;
ListenableFuture<List<BranchInfo>> listBranches(TenantId tenantId) throws Exception;

4
application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java

@ -52,9 +52,9 @@ public interface GitVersionControlQueueService {
ListenableFuture<PageData<EntityVersion>> listVersions(TenantId tenantId, String branch, EntityId entityId, PageLink pageLink);
ListenableFuture<List<VersionedEntityInfo>> listEntitiesAtVersion(TenantId tenantId, String branch, String versionId, EntityType entityType);
ListenableFuture<List<VersionedEntityInfo>> listEntitiesAtVersion(TenantId tenantId, String versionId, EntityType entityType);
ListenableFuture<List<VersionedEntityInfo>> listEntitiesAtVersion(TenantId tenantId, String branch, String versionId);
ListenableFuture<List<VersionedEntityInfo>> listEntitiesAtVersion(TenantId tenantId, String versionId);
ListenableFuture<List<BranchInfo>> listBranches(TenantId tenantId);

254
application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java

@ -20,9 +20,11 @@ import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Device;
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.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -30,9 +32,13 @@ 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.msg.TbMsg;
import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.service.gateway_device.GatewayNotificationsService;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@ -49,13 +55,32 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
@SpyBean
protected AuditLogService auditLogService;
@SpyBean
protected GatewayNotificationsService gatewayNotificationsService;
protected final String msgErrorPermission = "You don't have permission to perform this operation!";
protected final String msgErrorShouldBeSpecified = "should be specified";
protected void testNotifyEntityAllOneTime(HasName entity, EntityId entityId, EntityId originatorId,
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);
testPushMsgToRuleEngineTime(originatorId, tenantId, cntTime);
ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.equals(originatorId);
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) {
int cntTime = 1;
testSendNotificationMsgToEdgeServiceTime(entityId, tenantId, actionType, cntTime);
testLogEntityActionEntityEqClass(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo);
ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.equals(originatorId);
testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, cntTime);
Mockito.reset(tbClusterService, auditLogService);
}
@ -73,7 +98,8 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
int cntTime = 1;
testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo);
testPushMsgToRuleEngineTime(originatorId, tenantId, cntTime);
ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.equals(originatorId);
testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, cntTime);
Mockito.reset(tbClusterService, auditLogService);
}
@ -86,8 +112,49 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
ArgumentMatcher<HasName> matcherEntityClassEquals = argument -> argument.getClass().equals(entity.getClass());
ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.getClass().equals(originatorId.getClass());
testLogEntityActionAdditionalInfo(matcherEntityClassEquals, matcherOriginatorId, tenantId, customerId, userId, userName, actionType, cntTime,
additionalInfo);
testPushMsgToRuleEngineTime(originatorId, tenantId, cntTime);
extractMatcherAdditionalInfo(additionalInfo));
testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, cntTime);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(HasName entity, HasName originator,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, ActionType actionTypeEdge,
int cntTime, int cntTimeEdge, int cntTimeRuleEngine, Object... additionalInfo) {
EntityId originatorId = createEntityId_NULL_UUID(originator);
testSendNotificationMsgToEdgeServiceTimeEntityEqAny(tenantId, actionTypeEdge, cntTimeEdge);
ArgumentMatcher<HasName> matcherEntityClassEquals = argument -> argument.getClass().equals(entity.getClass());
ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.getClass().equals(originatorId.getClass());
testLogEntityActionAdditionalInfo(matcherEntityClassEquals, matcherOriginatorId, tenantId, customerId, userId, userName, actionType, cntTime,
extractMatcherAdditionalInfo(additionalInfo));
testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, cntTimeRuleEngine);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAnyAdditionalInfoAny(HasName entity, HasName originator,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, ActionType actionTypeEdge, int cntTime, int cntTimeEdge, int cntAdditionalInfo) {
EntityId originatorId = createEntityId_NULL_UUID(originator);
testSendNotificationMsgToEdgeServiceTimeEntityEqAny(tenantId, actionTypeEdge, cntTimeEdge);
ArgumentMatcher<HasName> matcherEntityClassEquals = argument -> argument.getClass().equals(entity.getClass());
ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.getClass().equals(originatorId.getClass());
testLogEntityActionAdditionalInfoAny(matcherEntityClassEquals, matcherOriginatorId, tenantId, customerId, userId, userName, actionType, cntTime,
cntAdditionalInfo);
testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, cntTimeEdge);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyManyEntityManyTimeMsgToEdgeServiceNeverAdditionalInfoAny(HasName entity, HasName originator,
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);
ArgumentMatcher<HasName> matcherEntityClassEquals = argument -> argument.getClass().equals(entity.getClass());
ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.getClass().equals(originatorId.getClass());
testLogEntityActionAdditionalInfoAny(matcherEntityClassEquals, matcherOriginatorId, tenantId, customerId, userId, userName, actionType, cntTime,
cntAdditionalInfo);
testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, cntTime);
Mockito.reset(tbClusterService, auditLogService);
}
@ -97,11 +164,24 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
int cntTime = 1;
testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo);
testPushMsgToRuleEngineTime(originatorId, tenantId, cntTime);
ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.equals(originatorId);
testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, cntTime);
testBroadcastEntityStateChangeEventTime(entityId, tenantId, cntTime);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityMsgToEdgePushMsgToCoreOneTime(HasName entity, EntityId entityId, EntityId originatorId,
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);
tesPushMsgToCoreTime(cntTime);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityEqualsOneTimeError(HasName entity, TenantId tenantId,
UserId userId, String userName, ActionType actionType, Exception exp,
Object... additionalInfo) {
@ -112,7 +192,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
ArgumentMatcher<Exception> matcherError = argument -> argument.getMessage().contains(exp.getMessage())
& argument.getClass().equals(exp.getClass());
testLogEntityActionErrorAdditionalInfo(matcherEntityEquals, entity_originator_NULL_UUID, tenantId, customer_NULL_UUID, userId,
userName, actionType, 1, matcherError, additionalInfo);
userName, actionType, 1, matcherError, extractMatcherAdditionalInfo(additionalInfo));
testPushMsgToRuleEngineNever(entity_originator_NULL_UUID);
}
@ -126,7 +206,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
ArgumentMatcher<Exception> matcherError = argument -> argument.getMessage().contains(exp.getMessage()) &
argument.getClass().equals(exp.getClass());
testLogEntityActionErrorAdditionalInfo(matcherEntityIsNull, entity_originator_NULL_UUID, tenantId, customer_NULL_UUID,
userId, userName, actionType, 1, matcherError, additionalInfo);
userId, userName, actionType, 1, matcherError, extractMatcherAdditionalInfo(additionalInfo));
testPushMsgToRuleEngineNever(entity_originator_NULL_UUID);
}
@ -138,6 +218,26 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotificationUpdateGatewayOneTime(Device device, Device oldDevice) {
Mockito.verify(gatewayNotificationsService, times(1)).onDeviceUpdated(Mockito.eq(device), Mockito.eq(oldDevice));
}
protected void testNotificationUpdateGatewayTime(int cntTimes) {
Mockito.verify(gatewayNotificationsService, times(cntTimes)).onDeviceUpdated(Mockito.any(Device.class), Mockito.isNull());
}
protected void testNotificationUpdateGatewayNever() {
Mockito.verify(gatewayNotificationsService, never()).onDeviceUpdated(Mockito.any(Device.class), Mockito.any(Device.class));
}
protected void testNotificationDeleteGatewayOneTime(Device device) {
Mockito.verify(gatewayNotificationsService, times(1)).onDeviceDeleted(device);
}
protected void testNotificationDeleteGatewayNever() {
Mockito.verify(gatewayNotificationsService, never()).onDeviceDeleted(Mockito.any(Device.class));
}
private void testNotificationMsgToEdgeServiceNever(EntityId entityId) {
Mockito.verify(tbClusterService, never()).sendNotificationMsgToEdge(Mockito.any(),
Mockito.any(), Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any(), Mockito.any());
@ -154,16 +254,22 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any());
}
private void testPushMsgToRuleEngineTime(EntityId originatorId, TenantId tenantId, int cntTime) {
ArgumentMatcher<EntityId> matcherOriginatorId = cntTime == 1 ? argument -> argument.equals(originatorId) :
argument -> argument.getClass().equals(originatorId.getClass());
private void testPushMsgToRuleEngineTime(ArgumentMatcher<EntityId> matcherOriginatorId, TenantId tenantId, int cntTime) {
Mockito.verify(tbClusterService, times(cntTime)).pushMsgToRuleEngine(Mockito.eq(tenantId),
Mockito.argThat(matcherOriginatorId), Mockito.any(TbMsg.class), Mockito.isNull());
}
private void testSendNotificationMsgToEdgeServiceTime(EntityId entityId, TenantId tenantId, ActionType actionType, int cntTime) {
EdgeEventActionType edgeEventActionType = ActionType.CREDENTIALS_UPDATED.equals(actionType) ?
EdgeEventActionType.CREDENTIALS_UPDATED : edgeTypeByActionType(actionType);
Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId),
Mockito.any(), Mockito.eq(entityId), Mockito.any(), Mockito.isNull(),
Mockito.eq(edgeEventActionType));
}
private void testSendNotificationMsgToEdgeServiceTimeEntityEqAny(TenantId tenantId, ActionType actionType, int cntTime) {
Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId),
Mockito.any(), Mockito.any(EntityId.class), Mockito.any(), Mockito.isNull(),
Mockito.eq(edgeTypeByActionType(actionType)));
}
@ -172,19 +278,88 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
Mockito.any(entityId.getClass()), Mockito.any(ComponentLifecycleEvent.class));
}
private void tesPushMsgToCoreTime(int cntTime) {
Mockito.verify(tbClusterService, times(cntTime)).pushMsgToCore(Mockito.any(ToDeviceActorNotificationMsg.class), Mockito.isNull());
}
private void testLogEntityAction(HasName entity, EntityId originatorId, TenantId tenantId,
CustomerId customerId, UserId userId, String userName,
ActionType actionType, int cntTime, Object... additionalInfo) {
ArgumentMatcher<HasName> matcherEntityEquals = argument -> argument.equals(entity);
ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.equals(originatorId);
testLogEntityActionAdditionalInfo(matcherEntityEquals, matcherOriginatorId, tenantId, customerId, userId, userName,
actionType, cntTime, additionalInfo);
actionType, cntTime, extractMatcherAdditionalInfo(additionalInfo));
}
private void testLogEntityActionEntityEqClass(HasName entity, EntityId originatorId, TenantId tenantId,
CustomerId customerId, UserId userId, String userName,
ActionType actionType, int cntTime, Object... additionalInfo) {
ArgumentMatcher<HasName> matcherEntityEquals = argument -> argument.getClass().equals(entity.getClass());
ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.equals(originatorId);
testLogEntityActionAdditionalInfo(matcherEntityEquals, matcherOriginatorId, tenantId, customerId, userId, userName,
actionType, cntTime, extractMatcherAdditionalInfo(additionalInfo));
}
private void testLogEntityActionAdditionalInfo(ArgumentMatcher<HasName> matcherEntity, ArgumentMatcher<EntityId> matcherOriginatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, int cntTime, Object... additionalInfo) {
switch (additionalInfo.length) {
ActionType actionType, int cntTime, List<ArgumentMatcher<Object>> matcherAdditionalInfos) {
switch (matcherAdditionalInfos.size()) {
case 1:
Mockito.verify(auditLogService, times(cntTime))
.logEntityAction(Mockito.eq(tenantId),
Mockito.eq(customerId),
Mockito.eq(userId),
Mockito.eq(userName),
Mockito.argThat(matcherOriginatorId),
Mockito.argThat(matcherEntity),
Mockito.eq(actionType),
Mockito.isNull(),
Mockito.argThat(matcherAdditionalInfos.get(0)));
break;
case 2:
Mockito.verify(auditLogService, times(cntTime))
.logEntityAction(Mockito.eq(tenantId),
Mockito.eq(customerId),
Mockito.eq(userId),
Mockito.eq(userName),
Mockito.argThat(matcherOriginatorId),
Mockito.argThat(matcherEntity),
Mockito.eq(actionType),
Mockito.isNull(),
Mockito.argThat(matcherAdditionalInfos.get(0)),
Mockito.argThat(matcherAdditionalInfos.get(1)));
break;
case 3:
Mockito.verify(auditLogService, times(cntTime))
.logEntityAction(Mockito.eq(tenantId),
Mockito.eq(customerId),
Mockito.eq(userId),
Mockito.eq(userName),
Mockito.argThat(matcherOriginatorId),
Mockito.argThat(matcherEntity),
Mockito.eq(actionType),
Mockito.isNull(),
Mockito.argThat(matcherAdditionalInfos.get(0)),
Mockito.argThat(matcherAdditionalInfos.get(1)),
Mockito.argThat(matcherAdditionalInfos.get(2)));
break;
default:
Mockito.verify(auditLogService, times(cntTime))
.logEntityAction(Mockito.eq(tenantId),
Mockito.eq(customerId),
Mockito.eq(userId),
Mockito.eq(userName),
Mockito.argThat(matcherOriginatorId),
Mockito.argThat(matcherEntity),
Mockito.eq(actionType),
Mockito.isNull());
}
}
private void testLogEntityActionAdditionalInfoAny(ArgumentMatcher<HasName> matcherEntity, ArgumentMatcher<EntityId> matcherOriginatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, int cntTime, int cntAdditionalInfo) {
switch (cntAdditionalInfo) {
case 1:
Mockito.verify(auditLogService, times(cntTime))
.logEntityAction(Mockito.eq(tenantId),
@ -195,7 +370,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
Mockito.argThat(matcherEntity),
Mockito.eq(actionType),
Mockito.isNull(),
Mockito.eq(extractParameter(String.class, 0, additionalInfo)));
Mockito.anyString());
break;
case 2:
Mockito.verify(auditLogService, times(cntTime))
@ -207,8 +382,8 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
Mockito.argThat(matcherEntity),
Mockito.eq(actionType),
Mockito.isNull(),
Mockito.eq(extractParameter(String.class, 0, additionalInfo)),
Mockito.eq(extractParameter(String.class, 1, additionalInfo)));
Mockito.anyString(),
Mockito.anyString());
break;
case 3:
Mockito.verify(auditLogService, times(cntTime))
@ -220,9 +395,9 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
Mockito.argThat(matcherEntity),
Mockito.eq(actionType),
Mockito.isNull(),
Mockito.eq(extractParameter(String.class, 0, additionalInfo)),
Mockito.eq(extractParameter(String.class, 1, additionalInfo)),
Mockito.eq(extractParameter(String.class, 2, additionalInfo)));
Mockito.anyString(),
Mockito.anyString(),
Mockito.anyString());
break;
default:
Mockito.verify(auditLogService, times(cntTime))
@ -239,8 +414,9 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
private void testLogEntityActionErrorAdditionalInfo(ArgumentMatcher<HasName> matcherEntity, EntityId originatorId, TenantId tenantId,
CustomerId customerId, UserId userId, String userName, ActionType actionType,
int cntTime, ArgumentMatcher<Exception> matcherError, Object... additionalInfo) {
switch (additionalInfo.length) {
int cntTime, ArgumentMatcher<Exception> matcherError,
List<ArgumentMatcher<Object>> matcherAdditionalInfos) {
switch (matcherAdditionalInfos.size()) {
case 1:
Mockito.verify(auditLogService, times(cntTime))
.logEntityAction(Mockito.eq(tenantId),
@ -251,7 +427,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
Mockito.argThat(matcherEntity),
Mockito.eq(actionType),
Mockito.argThat(matcherError),
Mockito.eq(extractParameter(String.class, 0, additionalInfo)));
Mockito.argThat(matcherAdditionalInfos.get(0)));
break;
case 2:
Mockito.verify(auditLogService, times(cntTime))
@ -263,9 +439,9 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
Mockito.argThat(matcherEntity),
Mockito.eq(actionType),
Mockito.argThat(matcherError),
Mockito.eq(extractParameter(String.class, 0, additionalInfo)),
Mockito.eq(extractParameter(String.class, 1, additionalInfo)));
case 3:
Mockito.argThat(Mockito.eq(matcherAdditionalInfos.get(0))),
Mockito.argThat(Mockito.eq(matcherAdditionalInfos.get(1))));
case 3:
Mockito.verify(auditLogService, times(cntTime))
.logEntityAction(Mockito.eq(tenantId),
Mockito.eq(customerId),
@ -275,9 +451,9 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
Mockito.argThat(matcherEntity),
Mockito.eq(actionType),
Mockito.argThat(matcherError),
Mockito.eq(extractParameter(String.class, 0, additionalInfo)),
Mockito.eq(extractParameter(String.class, 1, additionalInfo)),
Mockito.eq(extractParameter(String.class, 3, additionalInfo)));
Mockito.argThat(Mockito.eq(matcherAdditionalInfos.get(0))),
Mockito.argThat(Mockito.eq(matcherAdditionalInfos.get(1))),
Mockito.argThat(Mockito.eq(matcherAdditionalInfos.get(2))));
break;
default:
Mockito.verify(auditLogService, times(cntTime))
@ -292,10 +468,18 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
}
}
private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) {
private List<ArgumentMatcher<Object>> extractMatcherAdditionalInfo(Object... additionalInfos) {
List<ArgumentMatcher<Object>> matcherAdditionalInfos = new ArrayList<>(additionalInfos.length);
for (Object additionalInfo : additionalInfos) {
matcherAdditionalInfos.add(argument -> argument.equals(extractParameter(additionalInfo.getClass(), additionalInfo)));
}
return matcherAdditionalInfos;
}
private <T> T extractParameter(Class<T> clazz, Object additionalInfo) {
T result = null;
if (additionalInfo != null && additionalInfo.length > index) {
Object paramObject = additionalInfo[index];
if (additionalInfo != null) {
Object paramObject = additionalInfo;
if (clazz.isInstance(paramObject)) {
result = clazz.cast(paramObject);
}
@ -308,4 +492,12 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
.substring(entity.getClass().toString().lastIndexOf(".") + 1).toUpperCase(Locale.ENGLISH)),
ModelConstants.NULL_UUID);
}
protected String msgErrorFieldLength(String fieldName){
return "length of " + fieldName + " must be equal or less than 255";
}
protected String msgErrorNoFound(String entityClassName, String assetIdStr){
return entityClassName + " with id [" + assetIdStr + "] is not found";
}
}

36
application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java

@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.page.PageData;
import java.util.LinkedList;
import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
@ -132,7 +133,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm", alarm).andExpect(status().isForbidden());
doPost("/api/alarm", alarm)
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(alarm.getId(), alarm);
}
@ -147,7 +150,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm", alarm).andExpect(status().isForbidden());
doPost("/api/alarm", alarm)
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(alarm.getId(), alarm);
}
@ -187,7 +192,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden());
doDelete("/api/alarm/" + alarm.getId())
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(alarm.getId(), alarm);
}
@ -201,7 +208,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden());
doDelete("/api/alarm/" + alarm.getId())
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(alarm.getId(), alarm);
}
@ -265,7 +274,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden());
doPost("/api/alarm/" + alarm.getId() + "/clear")
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(alarm.getId(), alarm);
}
@ -279,7 +290,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden());
doPost("/api/alarm/" + alarm.getId() + "/clear")
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(alarm.getId(), alarm);
}
@ -293,7 +306,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden());
doPost("/api/alarm/" + alarm.getId() + "/ack")
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(alarm.getId(), alarm);
}
@ -307,7 +322,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden());
doPost("/api/alarm/" + alarm.getId() + "/ack")
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
}
@Test
@ -355,7 +372,8 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
loginDifferentCustomer();
doGet("/api/alarm/" + EntityType.DEVICE + "/"
+ customerDevice.getUuidId() + "?page=0&pageSize=" + size)
.andExpect(status().isForbidden());
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
}
@Test

123
application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java

@ -122,9 +122,10 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
String msgError = "length of name must be equal or less than 255";
doPost("/api/asset", asset).andExpect(statusReason(containsString(msgError)));
String msgError = msgErrorFieldLength("name");
doPost("/api/asset", asset)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeError(asset, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
@ -132,8 +133,10 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
asset.setName("Normal name");
asset.setType(RandomStringUtils.randomAlphabetic(300));
msgError = "length of type must be equal or less than 255";
doPost("/api/asset", asset).andExpect(statusReason(containsString(msgError)));
msgError = msgErrorFieldLength("type");
doPost("/api/asset", asset)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeError(asset, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
@ -141,8 +144,10 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
asset.setType("default");
asset.setLabel(RandomStringUtils.randomAlphabetic(300));
msgError = "length of label must be equal or less than 255";
doPost("/api/asset", asset).andExpect(statusReason(containsString(msgError)));
msgError = msgErrorFieldLength("label");
doPost("/api/asset", asset)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeError(asset, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
@ -159,12 +164,15 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/asset", savedAsset, Asset.class, status().isForbidden());
doPost("/api/asset", savedAsset)
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(savedAsset.getId(), savedAsset);
doDelete("/api/asset" + savedAsset.getId().getId().toString())
.andExpect(status().isNotFound());
doDelete("/api/asset/" + savedAsset.getId().getId().toString())
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(savedAsset.getId(), savedAsset);
@ -239,8 +247,10 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.DELETED, savedAsset.getId().getId().toString());
doGet("/api/asset/" + savedAsset.getId().getId().toString())
.andExpect(status().isNotFound());
String assetIdStr = savedAsset.getId().getId().toString();
doGet("/api/asset/" + assetIdStr)
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Asset", assetIdStr))));
}
@Test
@ -264,12 +274,12 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/asset/" + savedAsset1.getId().getId().toString())
.andExpect(status().isBadRequest());
String msgError = "Can't delete asset that has entity views";
doDelete("/api/asset/" + savedAsset1.getId().getId().toString())
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityIsNullOneTimeError(savedAsset1, savedTenant.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
testNotifyEntityIsNullOneTimeError(savedAsset1, savedTenant.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.DELETED, new DataValidationException(msgError), savedAsset1.getId().getId().toString());
savedView.setEntityId(savedAsset2.getId());
@ -279,8 +289,10 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
doDelete("/api/asset/" + savedAsset1.getId().getId().toString())
.andExpect(status().isOk());
doGet("/api/asset/" + savedAsset1.getId().getId().toString())
.andExpect(status().isNotFound());
String assetIdStr = savedAsset1.getId().getId().toString();
doGet("/api/asset/" + assetIdStr)
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Asset", assetIdStr))));
}
@Test
@ -290,7 +302,7 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
String msgError = "Asset type should be specified";
String msgError = "Asset type " + msgErrorShouldBeSpecified;
doPost("/api/asset", asset)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
@ -306,7 +318,7 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
String msgError = "Asset name should be specified";
String msgError = "Asset name " + msgErrorShouldBeSpecified;
doPost("/api/asset", asset)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
@ -362,10 +374,11 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
String msgError = "Asset name should be specified";
doPost("/api/customer/" + Uuids.timeBased().toString()
String customerIdStr = Uuids.timeBased().toString();
doPost("/api/customer/" + customerIdStr
+ "/asset/" + savedAsset.getId().getId().toString())
.andExpect(status().isNotFound());
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Customer", customerIdStr))));
testNotifyEntityNever(asset.getId(), asset);
}
@ -401,9 +414,11 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
String msgError = "You don't have permission to perform this operation!";
doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString())
.andExpect(status().isForbidden());
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityNever(savedAsset.getId(), savedAsset);
@ -431,7 +446,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
PageData<Asset> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/tenant/assets?",
new TypeReference<PageData<Asset>>(){}, pageLink);
new TypeReference<PageData<Asset>>() {
}, pageLink);
loadedAssets.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -480,7 +496,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
PageData<Asset> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/tenant/assets?",
new TypeReference<PageData<Asset>>(){}, pageLink);
new TypeReference<PageData<Asset>>() {
}, pageLink);
loadedAssetsTitle1.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -496,7 +513,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
pageLink = new PageLink(4, 0, title2);
do {
pageData = doGetTypedWithPageLink("/api/tenant/assets?",
new TypeReference<PageData<Asset>>(){}, pageLink);
new TypeReference<PageData<Asset>>() {
}, pageLink);
loadedAssetsTitle2.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -515,7 +533,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
pageLink = new PageLink(4, 0, title1);
pageData = doGetTypedWithPageLink("/api/tenant/assets?",
new TypeReference<PageData<Asset>>(){}, pageLink);
new TypeReference<PageData<Asset>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
@ -526,7 +545,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
pageLink = new PageLink(4, 0, title2);
pageData = doGetTypedWithPageLink("/api/tenant/assets?",
new TypeReference<PageData<Asset>>(){}, pageLink);
new TypeReference<PageData<Asset>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
}
@ -563,7 +583,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
PageData<Asset> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
new TypeReference<PageData<Asset>>(){}, pageLink, type1);
new TypeReference<PageData<Asset>>() {
}, pageLink, type1);
loadedAssetsType1.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -579,7 +600,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
pageLink = new PageLink(4);
do {
pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
new TypeReference<PageData<Asset>>(){}, pageLink, type2);
new TypeReference<PageData<Asset>>() {
}, pageLink, type2);
loadedAssetsType2.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -598,7 +620,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
pageLink = new PageLink(4);
pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
new TypeReference<PageData<Asset>>(){}, pageLink, type1);
new TypeReference<PageData<Asset>>() {
}, pageLink, type1);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
@ -609,7 +632,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
pageLink = new PageLink(4);
pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
new TypeReference<PageData<Asset>>(){}, pageLink, type2);
new TypeReference<PageData<Asset>>() {
}, pageLink, type2);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
}
@ -636,7 +660,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
PageData<Asset> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
new TypeReference<PageData<Asset>>(){}, pageLink);
new TypeReference<PageData<Asset>>() {
}, pageLink);
loadedAssets.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -688,7 +713,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
PageData<Asset> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
new TypeReference<PageData<Asset>>(){}, pageLink);
new TypeReference<PageData<Asset>>() {
}, pageLink);
loadedAssetsTitle1.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -704,7 +730,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
pageLink = new PageLink(4, 0, title2);
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
new TypeReference<PageData<Asset>>(){}, pageLink);
new TypeReference<PageData<Asset>>() {
}, pageLink);
loadedAssetsTitle2.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -723,7 +750,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
pageLink = new PageLink(4, 0, title1);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
new TypeReference<PageData<Asset>>(){}, pageLink);
new TypeReference<PageData<Asset>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
@ -734,7 +762,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
pageLink = new PageLink(4, 0, title2);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
new TypeReference<PageData<Asset>>(){}, pageLink);
new TypeReference<PageData<Asset>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
}
@ -780,7 +809,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
PageData<Asset> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
new TypeReference<PageData<Asset>>(){}, pageLink, type1);
new TypeReference<PageData<Asset>>() {
}, pageLink, type1);
loadedAssetsType1.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -796,7 +826,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
pageLink = new PageLink(4);
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
new TypeReference<PageData<Asset>>(){}, pageLink, type2);
new TypeReference<PageData<Asset>>() {
}, pageLink, type2);
loadedAssetsType2.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -815,7 +846,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
pageLink = new PageLink(4);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
new TypeReference<PageData<Asset>>(){}, pageLink, type1);
new TypeReference<PageData<Asset>>() {
}, pageLink, type1);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
@ -826,7 +858,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
pageLink = new PageLink(4);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
new TypeReference<PageData<Asset>>(){}, pageLink, type2);
new TypeReference<PageData<Asset>>() {
}, pageLink, type2);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
}
@ -852,7 +885,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
PageData<Asset> pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/assets?",
new TypeReference<PageData<Asset>>() {}, new PageLink(100));
new TypeReference<PageData<Asset>>() {
}, new PageLink(100));
Assert.assertEquals(1, pageData.getData().size());
@ -867,7 +901,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
ActionType.UNASSIGNED_FROM_EDGE, savedAsset.getId().getId().toString(), savedEdge.getId().getId().toString(), savedEdge.getName());
pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/assets?",
new TypeReference<PageData<Asset>>() {}, new PageLink(100));
new TypeReference<PageData<Asset>>() {
}, new PageLink(100));
Assert.assertEquals(0, pageData.getData().size());
}

47
application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java

@ -126,8 +126,10 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
Mockito.reset(tbClusterService, auditLogService);
String msgError = "length of title must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
String msgError = msgErrorFieldLength("title");
doPost("/api/customer", customer)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
customer.setTenantId(savedTenant.getId());
testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(),
@ -136,8 +138,10 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
customer.setTitle("Normal title");
customer.setCity(RandomStringUtils.randomAlphabetic(300));
msgError = "length of city must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
msgError = msgErrorFieldLength("city");
doPost("/api/customer", customer)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
@ -145,8 +149,10 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
customer.setCity("Normal city");
customer.setCountry(RandomStringUtils.randomAlphabetic(300));
msgError = "length of country must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
msgError = msgErrorFieldLength("country");
doPost("/api/customer", customer)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
@ -154,8 +160,10 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
customer.setCountry("Ukraine");
customer.setPhone(RandomStringUtils.randomAlphabetic(300));
msgError = "length of phone must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
msgError = msgErrorFieldLength("phone");
doPost("/api/customer", customer)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
@ -163,8 +171,10 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
customer.setPhone("+3892555554512");
customer.setState(RandomStringUtils.randomAlphabetic(300));
msgError = "length of state must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
msgError = msgErrorFieldLength("state");
doPost("/api/customer", customer)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
@ -172,8 +182,10 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
customer.setState("Normal state");
customer.setZip(RandomStringUtils.randomAlphabetic(300));
msgError = "length of zip or postal code must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
msgError = msgErrorFieldLength("zip or postal code");
doPost("/api/customer", customer)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
@ -195,7 +207,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
testNotifyEntityNever(savedCustomer.getId(), savedCustomer);
doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isForbidden());
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(savedCustomer.getId(), savedCustomer);
@ -241,14 +254,16 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
savedCustomer.getId(), savedCustomer.getTenantId(), savedCustomer.getId(), tenantAdmin.getId(),
tenantAdmin.getEmail(), ActionType.DELETED, savedCustomer.getId().getId().toString());
doGet("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isNotFound());
String customerIdStr = savedCustomer.getId().getId().toString();
doGet("/api/customer/" + customerIdStr)
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Customer", customerIdStr))));
}
@Test
public void testSaveCustomerWithEmptyTitle() throws Exception {
Customer customer = new Customer();
String msgError = "Customer title should be specified";
String msgError = "Customer title " + msgErrorShouldBeSpecified;
Mockito.reset(tbClusterService, auditLogService);

294
application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java

@ -22,16 +22,19 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
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.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.ArrayList;
import java.util.Collections;
@ -41,54 +44,66 @@ import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseDashboardControllerTest extends AbstractControllerTest {
private IdComparator<DashboardInfo> idComparator = new IdComparator<>();
private Tenant savedTenant;
private User tenantAdmin;
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My 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");
}
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testSaveDashboard() throws Exception {
Dashboard dashboard = new Dashboard();
dashboard.setTitle("My dashboard");
Mockito.reset(tbClusterService, auditLogService);
Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDashboard, savedDashboard.getId(), savedDashboard.getId(), savedTenant.getId(),
tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED);
Assert.assertNotNull(savedDashboard);
Assert.assertNotNull(savedDashboard.getId());
Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
Assert.assertEquals(savedTenant.getId(), savedDashboard.getTenantId());
Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
savedDashboard.setTitle("My new dashboard");
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/dashboard", savedDashboard, Dashboard.class);
testNotifyEntityAllOneTime(savedDashboard, savedDashboard.getId(), savedDashboard.getId(), savedTenant.getId(),
tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED);
Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertEquals(foundDashboard.getTitle(), savedDashboard.getTitle());
}
@ -97,7 +112,18 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
public void testSaveDashboardInfoWithViolationOfValidation() throws Exception {
Dashboard dashboard = new Dashboard();
dashboard.setTitle(RandomStringUtils.randomAlphabetic(300));
doPost("/api/dashboard", dashboard).andExpect(statusReason(containsString("length of title must be equal or less than 255")));
String msgError = msgErrorFieldLength("title");
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/dashboard", dashboard)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
dashboard.setTenantId(savedTenant.getId());
testNotifyEntityEqualsOneTimeError(dashboard, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
Mockito.reset(tbClusterService, auditLogService);
}
@Test
@ -107,10 +133,16 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/dashboard", savedDashboard, Dashboard.class, status().isForbidden());
testNotifyEntityNever(savedDashboard.getId(), savedDashboard);
deleteDifferentTenant();
}
@Test
public void testFindDashboardById() throws Exception {
Dashboard dashboard = new Dashboard();
@ -120,48 +152,74 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
Assert.assertNotNull(foundDashboard);
Assert.assertEquals(savedDashboard, foundDashboard);
}
@Test
public void testDeleteDashboard() throws Exception {
Dashboard dashboard = new Dashboard();
dashboard.setTitle("My dashboard");
Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
doDelete("/api/dashboard/"+savedDashboard.getId().getId().toString())
.andExpect(status().isOk());
doGet("/api/dashboard/"+savedDashboard.getId().getId().toString())
.andExpect(status().isNotFound());
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/dashboard/" + savedDashboard.getId().getId().toString()).andExpect(status().isOk());
testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDashboard, savedDashboard.getId(), savedDashboard.getId(),
savedDashboard.getTenantId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.DELETED,
savedDashboard.getId().getId().toString());
String dashboardIdStr = savedDashboard.getId().getId().toString();
doGet("/api/dashboard/" + savedDashboard.getId().getId().toString())
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Dashboard", dashboardIdStr))));
}
@Test
public void testSaveDashboardWithEmptyTitle() throws Exception {
Dashboard dashboard = new Dashboard();
String msgError = "Dashboard title " + msgErrorShouldBeSpecified;;
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/dashboard", dashboard)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Dashboard title should be specified")));
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeError(dashboard, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
}
@Test
public void testAssignUnassignDashboardToCustomer() throws Exception {
Dashboard dashboard = new Dashboard();
dashboard.setTitle("My dashboard");
Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
Customer customer = new Customer();
customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
Mockito.reset(tbClusterService, auditLogService);
Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertTrue(assignedDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo()));
testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(assignedDashboard, assignedDashboard.getId(), assignedDashboard.getId(),
savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER,
savedDashboard.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle());
Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertTrue(foundDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo()));
Dashboard unassignedDashboard =
doDelete("/api/customer/"+savedCustomer.getId().getId().toString()+"/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Mockito.reset(tbClusterService, auditLogService);
Dashboard unassignedDashboard =
doDelete("/api/customer/" + savedCustomer.getId().getId().toString() + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(assignedDashboard, assignedDashboard.getId(), assignedDashboard.getId(),
savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UNASSIGNED_FROM_CUSTOMER,
savedDashboard.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle());
Assert.assertTrue(unassignedDashboard.getAssignedCustomers() == null || unassignedDashboard.getAssignedCustomers().isEmpty());
@ -169,22 +227,27 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
Assert.assertTrue(foundDashboard.getAssignedCustomers() == null || foundDashboard.getAssignedCustomers().isEmpty());
}
@Test
public void testAssignDashboardToNonExistentCustomer() throws Exception {
Dashboard dashboard = new Dashboard();
dashboard.setTitle("My dashboard");
Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
doPost("/api/customer/" + Uuids.timeBased().toString()
String customerIdStr = Uuids.timeBased().toString();
doPost("/api/customer/" + customerIdStr
+ "/dashboard/" + savedDashboard.getId().getId().toString())
.andExpect(status().isNotFound());
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Customer", customerIdStr))));
Mockito.reset(tbClusterService, auditLogService);
testNotifyEntityNever(savedDashboard.getId(), savedDashboard);
}
@Test
public void testAssignDashboardToCustomerFromDifferentTenant() throws Exception {
loginSysAdmin();
Tenant tenant2 = new Tenant();
tenant2.setTitle("Different tenant");
Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
@ -196,100 +259,123 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
tenantAdmin2.setEmail("tenant3@thingsboard.org");
tenantAdmin2.setFirstName("Joe");
tenantAdmin2.setLastName("Downs");
tenantAdmin2 = createUserAndLogin(tenantAdmin2, "testPassword1");
createUserAndLogin(tenantAdmin2, "testPassword1");
Customer customer = new Customer();
customer.setTitle("Different customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
login(tenantAdmin.getEmail(), "testPassword1");
Dashboard dashboard = new Dashboard();
dashboard.setTitle("My dashboard");
Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ "/dashboard/" + savedDashboard.getId().getId().toString())
.andExpect(status().isForbidden());
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
Mockito.reset(tbClusterService, auditLogService);
testNotifyEntityNever(savedDashboard.getId(), savedDashboard);
doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(savedDashboard.getId(), savedDashboard);
loginSysAdmin();
doDelete("/api/tenant/"+savedTenant2.getId().getId().toString())
.andExpect(status().isOk());
doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testFindTenantDashboards() throws Exception {
List<DashboardInfo> dashboards = new ArrayList<>();
for (int i=0;i<173;i++) {
Mockito.reset(tbClusterService, auditLogService);
int cntEntity = 173;
for (int i = 0; i < cntEntity; i++) {
Dashboard dashboard = new Dashboard();
dashboard.setTitle("Dashboard"+i);
dashboard.setTitle("Dashboard" + i);
dashboards.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class)));
}
testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Dashboard(), new Dashboard(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED, cntEntity);
List<DashboardInfo> loadedDashboards = new ArrayList<>();
PageLink pageLink = new PageLink(24);
PageData<DashboardInfo> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<PageData<DashboardInfo>>() {
}, pageLink);
loadedDashboards.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(dashboards, idComparator);
Collections.sort(loadedDashboards, idComparator);
Assert.assertEquals(dashboards, loadedDashboards);
}
@Test
public void testFindTenantDashboardsByTitle() throws Exception {
String title1 = "Dashboard title 1";
List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();
for (int i=0;i<134;i++) {
int cntEntity = 134;
for (int i = 0; i < cntEntity; i++) {
Dashboard dashboard = new Dashboard();
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title1+suffix;
String suffix = RandomStringUtils.randomAlphanumeric((int) (Math.random() * 15));
String title = title1 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title);
dashboardsTitle1.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class)));
}
String title2 = "Dashboard title 2";
List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();
for (int i=0;i<112;i++) {
for (int i = 0; i < 112; i++) {
Dashboard dashboard = new Dashboard();
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title2+suffix;
String suffix = RandomStringUtils.randomAlphanumeric((int) (Math.random() * 15));
String title = title2 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title);
dashboardsTitle2.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class)));
}
List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();
PageLink pageLink = new PageLink(15, 0, title1);
PageData<DashboardInfo> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<PageData<DashboardInfo>>() {
}, pageLink);
loadedDashboardsTitle1.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(dashboardsTitle1, idComparator);
Collections.sort(loadedDashboardsTitle1, idComparator);
Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
pageLink = new PageLink(4, 0, title2);
do {
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<PageData<DashboardInfo>>() {
}, pageLink);
loadedDashboardsTitle2.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -298,63 +384,79 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
Collections.sort(dashboardsTitle2, idComparator);
Collections.sort(loadedDashboardsTitle2, idComparator);
Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
Mockito.reset(tbClusterService, auditLogService);
for (DashboardInfo dashboard : loadedDashboardsTitle1) {
doDelete("/api/dashboard/"+dashboard.getId().getId().toString())
.andExpect(status().isOk());
doDelete("/api/dashboard/" + dashboard.getId().getId().toString())
.andExpect(status().isOk());
}
testNotifyManyEntityManyTimeMsgToEdgeServiceNeverAdditionalInfoAny(new Dashboard(), new Dashboard(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.DELETED, cntEntity, 1);
pageLink = new PageLink(4, 0, title1);
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<PageData<DashboardInfo>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
for (DashboardInfo dashboard : loadedDashboardsTitle2) {
doDelete("/api/dashboard/"+dashboard.getId().getId().toString())
.andExpect(status().isOk());
doDelete("/api/dashboard/" + dashboard.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new PageLink(4, 0, title2);
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<PageData<DashboardInfo>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
}
@Test
public void testFindCustomerDashboards() throws Exception {
Customer customer = new Customer();
customer.setTitle("Test customer");
customer = doPost("/api/customer", customer, Customer.class);
CustomerId customerId = customer.getId();
Mockito.reset(tbClusterService, auditLogService);
int cntEntity = 173;
List<DashboardInfo> dashboards = new ArrayList<>();
for (int i=0;i<173;i++) {
for (int i = 0; i < cntEntity; i++) {
Dashboard dashboard = new Dashboard();
dashboard.setTitle("Dashboard"+i);
dashboard.setTitle("Dashboard" + i);
dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
dashboards.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()
+ "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
+ "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
}
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Dashboard(), new Dashboard(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED, ActionType.ASSIGNED_TO_CUSTOMER, cntEntity, cntEntity, cntEntity*2);
List<DashboardInfo> loadedDashboards = new ArrayList<>();
PageLink pageLink = new PageLink(21);
PageData<DashboardInfo> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
new TypeReference<PageData<DashboardInfo>>() {
}, pageLink);
loadedDashboards.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(dashboards, idComparator);
Collections.sort(loadedDashboards, idComparator);
Assert.assertEquals(dashboards, loadedDashboards);
}
@ -367,11 +469,18 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
dashboard.setTitle("My dashboard");
Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/edge/" + savedEdge.getId().getId().toString()
+ "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
testNotifyEntityAllOneTime(savedDashboard, savedDashboard.getId(), savedDashboard.getId(), savedTenant.getId(),
tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_EDGE,
savedDashboard.getId().getId().toString(), savedEdge.getId().getId().toString(), savedEdge.getName());
PageData<Dashboard> pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/dashboards?",
new TypeReference<PageData<Dashboard>>() {}, new PageLink(100));
new TypeReference<PageData<Dashboard>>() {
}, new PageLink(100));
Assert.assertEquals(1, pageData.getData().size());
@ -379,7 +488,8 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
+ "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/dashboards?",
new TypeReference<PageData<Dashboard>>() {}, new PageLink(100));
new TypeReference<PageData<Dashboard>>() {
}, new PageLink(100));
Assert.assertEquals(0, pageData.getData().size());
}

313
application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java

@ -21,22 +21,24 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype;
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.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceCredentialsId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
@ -44,6 +46,8 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import java.util.ArrayList;
@ -55,7 +59,6 @@ import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@Slf4j
public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
static final TypeReference<PageData<Device>> PAGE_DATA_DEVICE_TYPE_REF = new TypeReference<>() {
};
@ -70,7 +73,6 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
@Before
public void beforeTest() throws Exception {
log.debug("beforeTest");
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
loginSysAdmin();
@ -92,14 +94,12 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
@After
public void afterTest() throws Exception {
log.debug("afterTest...");
executor.shutdownNow();
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId())
.andExpect(status().isOk());
log.debug("afterTest done");
}
@Test
@ -107,8 +107,17 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Device device = new Device();
device.setName("My device");
device.setType("default");
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
Device savedDevice = doPost("/api/device", device, Device.class);
Device oldDevice = new Device(savedDevice);
testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDevice, savedDevice.getId(), savedDevice.getId(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED);
testNotificationUpdateGatewayNever();
Assert.assertNotNull(savedDevice);
Assert.assertNotNull(savedDevice.getId());
Assert.assertTrue(savedDevice.getCreatedTime() > 0);
@ -127,9 +136,15 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Assert.assertNotNull(deviceCredentials.getCredentialsId());
Assert.assertEquals(20, deviceCredentials.getCredentialsId().length());
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
savedDevice.setName("My new device");
doPost("/api/device", savedDevice, Device.class);
testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(),
tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED);
testNotificationUpdateGatewayOneTime(savedDevice, oldDevice);
Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class);
Assert.assertEquals(foundDevice.getName(), savedDevice.getName());
}
@ -139,13 +154,41 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Device device = new Device();
device.setName(RandomStringUtils.randomAlphabetic(300));
device.setType("default");
doPost("/api/device", device).andExpect(statusReason(containsString("length of name must be equal or less than 255")));
device.setName("Normal Name");
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
String msgError = "length of name must be equal or less than 255";
doPost("/api/device", device)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeError(device, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
testNotificationUpdateGatewayNever();
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
device.setTenantId(savedTenant.getId());
msgError = "length of type must be equal or less than 255";
device.setType(RandomStringUtils.randomAlphabetic(300));
doPost("/api/device", device).andExpect(statusReason(containsString("length of type must be equal or less than 255")));
doPost("/api/device", device)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeError(device, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
testNotificationUpdateGatewayNever();
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
msgError = "length of label must be equal or less than 255";
device.setType("Normal type");
device.setLabel(RandomStringUtils.randomAlphabetic(300));
doPost("/api/device", device).andExpect(statusReason(containsString("length of label must be equal or less than 255")));
doPost("/api/device", device)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeError(device, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
testNotificationUpdateGatewayNever();
}
@Test
@ -155,7 +198,24 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
doPost("/api/device", savedDevice, Device.class, status().isNotFound());
testNotifyEntityNever(savedDevice.getId(), savedDevice);
testNotificationUpdateGatewayNever();
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
String savedDeviceIdStr = savedDevice.getId().getId().toString();
doDelete("/api/device/" + savedDeviceIdStr)
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Device", savedDeviceIdStr))));
testNotifyEntityNever(savedDevice.getId(), savedDevice);
testNotificationUpdateGatewayNever();
deleteDifferentTenant();
}
@ -173,12 +233,24 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
@Test
public void testFindDeviceTypesByTenantId() throws Exception {
List<Device> devices = new ArrayList<>();
for (int i = 0; i < 3; i++) {
int cntEntity = 3;
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
for (int i = 0; i < cntEntity; i++) {
Device device = new Device();
device.setName("My device B" + i);
device.setType("typeB");
devices.add(doPost("/api/device", device, Device.class));
}
testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Device(), new Device(),
savedTenant.getId(),
tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED, cntEntity);
testNotificationUpdateGatewayNever();
for (int i = 0; i < 7; i++) {
Device device = new Device();
device.setName("My device C" + i);
@ -192,7 +264,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
devices.add(doPost("/api/device", device, Device.class));
}
List<EntitySubtype> deviceTypes = doGetTyped("/api/device/types",
new TypeReference<List<EntitySubtype>>() {
new TypeReference<>() {
});
Assert.assertNotNull(deviceTypes);
@ -211,28 +283,52 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
doDelete("/api/device/" + savedDevice.getId().getId())
.andExpect(status().isOk());
doGet("/api/device/" + savedDevice.getId().getId())
.andExpect(status().isNotFound());
testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(),
tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.DELETED, savedDevice.getId().getId().toString());
testNotificationDeleteGatewayOneTime(savedDevice);
EntityId savedDeviceId = savedDevice.getId();
doGet("/api/device/" + savedDeviceId)
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Device", savedDeviceId.getId().toString()))));
}
@Test
public void testSaveDeviceWithEmptyType() throws Exception {
Device device = new Device();
device.setName("My device");
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
Device savedDevice = doPost("/api/device", device, Device.class);
Assert.assertEquals("default", savedDevice.getType());
testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDevice, savedDevice.getId(), savedDevice.getId(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED);
testNotificationUpdateGatewayNever();
}
@Test
public void testSaveDeviceWithEmptyName() throws Exception {
Device device = new Device();
device.setType("default");
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
String msgError = "Device name " + msgErrorShouldBeSpecified;
doPost("/api/device", device)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Device name should be specified")));
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeError(device, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
testNotificationUpdateGatewayNever();
}
@Test
@ -246,17 +342,33 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
Device assignedDevice = doPost("/api/customer/" + savedCustomer.getId().getId()
+ "/device/" + savedDevice.getId().getId(), Device.class);
Assert.assertEquals(savedCustomer.getId(), assignedDevice.getCustomerId());
testNotifyEntityAllOneTime(assignedDevice, assignedDevice.getId(), assignedDevice.getId(), savedTenant.getId(),
savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER,
assignedDevice.getId().getId().toString(), savedCustomer.getId().getId().toString(),
savedCustomer.getTitle());
testNotificationUpdateGatewayNever();
Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class);
Assert.assertEquals(savedCustomer.getId(), foundDevice.getCustomerId());
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
Device unassignedDevice =
doDelete("/api/customer/device/" + savedDevice.getId().getId(), Device.class);
Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDevice.getCustomerId().getId());
testNotifyEntityAllOneTime(unassignedDevice, unassignedDevice.getId(), unassignedDevice.getId(), savedTenant.getId(),
savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UNASSIGNED_FROM_CUSTOMER,
unassignedDevice.getId().getId().toString(), savedCustomer.getId().getId().toString(),
savedCustomer.getTitle());
testNotificationDeleteGatewayNever();
foundDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class);
Assert.assertEquals(ModelConstants.NULL_UUID, foundDevice.getCustomerId().getId());
}
@ -267,9 +379,17 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setName("My device");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
doPost("/api/customer/" + Uuids.timeBased().toString()
+ "/device/" + savedDevice.getId().getId())
.andExpect(status().isNotFound());
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
String customerIdStr = savedDevice.getId().toString();
doPost("/api/customer/" + customerIdStr
+ "/device/" + savedDevice.getId().getId())
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Customer", customerIdStr))));
testNotifyEntityNever(savedDevice.getId(), savedDevice);
testNotificationUpdateGatewayNever();
}
@Test
@ -288,7 +408,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
tenantAdmin2.setFirstName("Joe");
tenantAdmin2.setLastName("Downs");
tenantAdmin2 = createUserAndLogin(tenantAdmin2, "testPassword1");
createUserAndLogin(tenantAdmin2, "testPassword1");
Customer customer = new Customer();
customer.setTitle("Different customer");
@ -301,9 +421,15 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
doPost("/api/customer/" + savedCustomer.getId().getId()
+ "/device/" + savedDevice.getId().getId())
.andExpect(status().isForbidden());
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(savedDevice.getId(), savedDevice);
testNotificationUpdateGatewayNever();
loginSysAdmin();
@ -333,9 +459,16 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
deviceCredentials.setCredentialsId("access_token");
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isOk());
testNotifyEntityMsgToEdgePushMsgToCoreOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(),
tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.CREDENTIALS_UPDATED, deviceCredentials);
testNotificationUpdateGatewayNever();
DeviceCredentials foundDeviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
@ -345,8 +478,15 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
@Test
public void testSaveDeviceCredentialsWithEmptyDevice() throws Exception {
DeviceCredentials deviceCredentials = new DeviceCredentials();
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isBadRequest());
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Incorrect deviceId null")));
testNotifyEntityNever(deviceCredentials.getDeviceId(), new Device());
testNotificationUpdateGatewayNever();
}
@Test
@ -358,9 +498,18 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
deviceCredentials.setCredentialsType(null);
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
String msgError = "Device credentials type " + msgErrorShouldBeSpecified;
doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Device credentials type should be specified")));
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityIsNullOneTimeError(device, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.CREDENTIALS_UPDATED,
new DataValidationException(msgError), deviceCredentials);
testNotificationUpdateGatewayNever();
}
@Test
@ -372,9 +521,18 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
deviceCredentials.setCredentialsId(null);
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
String msgError = "Device credentials id " + msgErrorShouldBeSpecified;
doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Device credentials id should be specified")));
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityIsNullOneTimeError(device, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.CREDENTIALS_UPDATED,
new DeviceCredentialsValidationException(msgError), deviceCredentials);
testNotificationUpdateGatewayNever();
}
@Test
@ -390,9 +548,18 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
newDeviceCredentials.setDeviceId(deviceCredentials.getDeviceId());
newDeviceCredentials.setCredentialsType(deviceCredentials.getCredentialsType());
newDeviceCredentials.setCredentialsId(deviceCredentials.getCredentialsId());
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
String msgError = "Unable to update non-existent device credentials";
doPost("/api/device/credentials", newDeviceCredentials)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Unable to update non-existent device credentials")));
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityIsNullOneTimeError(device, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.CREDENTIALS_UPDATED,
new DeviceCredentialsValidationException(msgError), newDeviceCredentials);
testNotificationUpdateGatewayNever();
}
@Test
@ -401,29 +568,44 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setName("My device");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceId deviceTimeBasedId = new DeviceId(Uuids.timeBased());
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
deviceCredentials.setDeviceId(new DeviceId(Uuids.timeBased()));
deviceCredentials.setDeviceId(deviceTimeBasedId);
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isNotFound());
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Device", deviceTimeBasedId.toString()))));
testNotifyEntityNever(savedDevice.getId(), savedDevice);
testNotificationUpdateGatewayNever();
}
@Test
public void testFindTenantDevices() throws Exception {
log.debug("testFindTenantDevices");
futures = new ArrayList<>(178);
for (int i = 0; i < 178; i++) {
int cntEntity = 178;
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
futures = new ArrayList<>(cntEntity);
for (int i = 0; i < cntEntity; i++) {
Device device = new Device();
device.setName("Device" + i);
device.setType("default");
futures.add(executor.submit(() ->
doPost("/api/device", device, Device.class)));
}
log.debug("await create devices");
List<Device> devices = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
log.debug("start reading");
List<Device> loadedDevices = new ArrayList<>(178);
testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Device(), new Device(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED, cntEntity);
testNotificationUpdateGatewayNever();
List<Device> loadedDevices = new ArrayList<>(cntEntity);
PageLink pageLink = new PageLink(23);
do {
pageData = doGetTypedWithPageLink("/api/tenant/devices?",
@ -435,11 +617,16 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
}
} while (pageData.hasNext());
log.debug("asserting");
assertThat(devices).containsExactlyInAnyOrderElementsOf(loadedDevices);
log.debug("delete devices async");
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
deleteEntitiesAsync("/api/device/", loadedDevices, executor).get(TIMEOUT, TimeUnit.SECONDS);
log.debug("done");
testNotifyManyEntityManyTimeMsgToEdgeServiceNeverAdditionalInfoAny(new Device(), new Device(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.DELETED, cntEntity, 1);
testNotificationUpdateGatewayNever();
}
@Test
@ -604,9 +791,12 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
customer.setTitle("Test customer");
customer = doPost("/api/customer", customer, Customer.class);
CustomerId customerId = customer.getId();
int cntEntity = 128;
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
futures = new ArrayList<>(128);
for (int i = 0; i < 128; i++) {
futures = new ArrayList<>(cntEntity);
for (int i = 0; i < cntEntity; i++) {
Device device = new Device();
device.setName("Device" + i);
device.setType("default");
@ -618,7 +808,12 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
List<Device> devices = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
List<Device> loadedDevices = new ArrayList<>(128);
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Device(), new Device(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED, ActionType.ASSIGNED_TO_CUSTOMER, cntEntity, cntEntity, cntEntity * 2);
testNotificationUpdateGatewayNever();
List<Device> loadedDevices = new ArrayList<>(cntEntity);
PageLink pageLink = new PageLink(23);
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?",
@ -631,9 +826,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
assertThat(devices).containsExactlyInAnyOrderElementsOf(loadedDevices);
log.debug("delete devices async");
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
deleteEntitiesAsync("/api/customer/device/", loadedDevices, executor).get(TIMEOUT, TimeUnit.SECONDS);
log.debug("done");
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAnyAdditionalInfoAny(new Device(), new Device(),
savedTenant.getId(), customerId, tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UNASSIGNED_FROM_CUSTOMER, cntEntity, cntEntity, 3);
testNotificationUpdateGatewayNever();
}
@Test
@ -843,16 +1043,30 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
createUserAndLogin(user, "testPassword1");
login("tenant2@thingsboard.org", "testPassword1");
Device assignedDevice = doPost("/api/tenant/" + savedDifferentTenant.getId().getId() + "/device/" + savedDevice.getId().getId(), Device.class);
doGet("/api/device/" + assignedDevice.getId().getId(), Device.class, status().isNotFound());
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
Device assignedDevice = doPost("/api/tenant/" + savedDifferentTenant.getId().getId() + "/device/"
+ savedDevice.getId().getId(), Device.class);
doGet("/api/device/" + assignedDevice.getId().getId())
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Device", assignedDevice.getId().getId().toString()))));
testNotifyEntityOneTimeMsgToEdgeServiceNever(assignedDevice, assignedDevice.getId(), assignedDevice.getId(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ASSIGNED_TO_TENANT, savedDifferentTenant.getId().getId().toString(), savedDifferentTenant.getTitle());
testNotificationUpdateGatewayNever();
login("tenant9@thingsboard.org", "testPassword1");
Device foundDevice1 = doGet("/api/device/" + assignedDevice.getId().getId(), Device.class);
Assert.assertNotNull(foundDevice1);
doGet("/api/relation?fromId=" + savedDevice.getId().getId() + "&fromType=DEVICE&relationType=Contains&toId=" + savedAnotherDevice.getId().getId() + "&toType=DEVICE", EntityRelation.class, status().isNotFound());
doGet("/api/relation?fromId=" + savedDevice.getId().getId() + "&fromType=DEVICE&relationType=Contains&toId="
+ savedAnotherDevice.getId().getId() + "&toType=DEVICE")
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Device", savedAnotherDevice.getId().getId().toString()))));
loginSysAdmin();
doDelete("/api/tenant/" + savedDifferentTenant.getId().getId())
@ -869,17 +1083,32 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
doPost("/api/edge/" + savedEdge.getId().getId()
+ "/device/" + savedDevice.getId().getId(), Device.class);
testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ASSIGNED_TO_EDGE,
savedDevice.getId().getId().toString(), savedEdge.getId().getId().toString(), savedEdge.getName());
testNotificationUpdateGatewayNever();
pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/devices?",
PAGE_DATA_DEVICE_TYPE_REF, new PageLink(100));
Assert.assertEquals(1, pageData.getData().size());
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
doDelete("/api/edge/" + savedEdge.getId().getId()
+ "/device/" + savedDevice.getId().getId(), Device.class);
testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.UNASSIGNED_FROM_EDGE, savedDevice.getId().getId().toString(), savedEdge.getId().getId().toString(), savedEdge.getName());
testNotificationUpdateGatewayNever();
pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/devices?",
PAGE_DATA_DEVICE_TYPE_REF, new PageLink(100));

6
common/data/src/main/java/org/thingsboard/server/common/data/queue/Queue.java

@ -22,11 +22,17 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@Data
public class Queue extends SearchTextBasedWithAdditionalInfo<QueueId> implements HasName, HasTenantId {
private TenantId tenantId;
@NoXss
@Length(fieldName = "name")
private String name;
@NoXss
@Length(fieldName = "topic")
private String topic;
private int pollInterval;
private int partitions;

2
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/BulkImportColumnType.java → common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.sync.ie.importing.csv;
package org.thingsboard.server.common.data.sync.ie.importing.csv;
import lombok.Getter;
import org.thingsboard.server.common.data.DataConstants;

2
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/BulkImportRequest.java → common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportRequest.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.sync.ie.importing.csv;
package org.thingsboard.server.common.data.sync.ie.importing.csv;
import lombok.Data;

2
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/BulkImportResult.java → common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportResult.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.sync.ie.importing.csv;
package org.thingsboard.server.common.data.sync.ie.importing.csv;
import lombok.Data;

3
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/BranchInfo.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.sync.vc;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.Objects;
@ -22,6 +23,8 @@ import java.util.Objects;
@Data
public class BranchInfo {
private final String name;
@JsonProperty("default")
private final boolean isDefault;
@Override

1
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/VersionLoadRequest.java

@ -29,7 +29,6 @@ import static com.fasterxml.jackson.annotation.JsonSubTypes.Type;
@Data
public abstract class VersionLoadRequest {
private String branch;
private String versionId;
public abstract VersionLoadRequestType getType();

6
common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java

@ -354,7 +354,11 @@ public class GitRepository {
}
private ObjectId resolve(String rev) throws IOException {
return git.getRepository().resolve(rev);
ObjectId result = git.getRepository().resolve(rev);
if (result == null) {
throw new IllegalArgumentException("Failed to parse git revision string: \"" + rev + "\"");
}
return result;
}
private <C extends GitCommand<T>, T> T execute(C command) throws GitAPIException {

24
dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
@ -36,6 +37,11 @@ public abstract class DataValidator<D extends BaseData<?>> {
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE);
private static final Pattern QUEUE_PATTERN = Pattern.compile("^[a-zA-Z0-9_.\\-]+$");
private static final String NAME = "name";
private static final String TOPIC = "topic";
// Returns old instance of the same object that is fetched during validation.
public D validate(D data, Function<D, TenantId> tenantIdFunction) {
try {
@ -134,4 +140,22 @@ public abstract class DataValidator<D extends BaseData<?>> {
}
}
protected static void validateQueueName(String name) {
validateQueueNameOrTopic(name, NAME);
}
protected static void validateQueueTopic(String topic) {
validateQueueNameOrTopic(topic, TOPIC);
}
private static void validateQueueNameOrTopic(String value, String fieldName) {
if (StringUtils.isEmpty(value)) {
throw new DataValidationException(String.format("Queue %s should be specified!", fieldName));
}
if (!QUEUE_PATTERN.matcher(value).matches()) {
throw new DataValidationException(
String.format("Queue %s contains a character other than ASCII alphanumerics, '.', '_' and '-'!", fieldName));
}
}
}

10
dao/src/main/java/org/thingsboard/server/dao/service/validator/QueueValidator.java

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.dao.service.validator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.TenantProfile;
@ -73,12 +72,9 @@ public class QueueValidator extends DataValidator<Queue> {
}
}
if (StringUtils.isEmpty(queue.getName())) {
throw new DataValidationException("Queue name should be specified!");
}
if (StringUtils.isBlank(queue.getTopic())) {
throw new DataValidationException("Queue topic should be non empty and without spaces!");
}
validateQueueName(queue.getName());
validateQueueTopic(queue.getTopic());
if (queue.getPollInterval() < 1) {
throw new DataValidationException("Queue poll interval should be more then 0!");
}

9
dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java

@ -107,12 +107,9 @@ public class TenantProfileDataValidator extends DataValidator<TenantProfile> {
}
private void validateQueueConfiguration(TenantProfileQueueConfiguration queue) {
if (StringUtils.isEmpty(queue.getName())) {
throw new DataValidationException("Queue name should be specified!");
}
if (StringUtils.isBlank(queue.getTopic())) {
throw new DataValidationException("Queue topic should be non empty and without spaces!");
}
validateQueueName(queue.getName());
validateQueueTopic(queue.getTopic());
if (queue.getPollInterval() < 1) {
throw new DataValidationException("Queue poll interval should be more then 0!");
}

28
dao/src/test/java/org/thingsboard/server/dao/service/BaseQueueServiceTest.java

@ -152,6 +152,20 @@ public abstract class BaseQueueServiceTest extends AbstractServiceTest {
queueService.saveQueue(queue);
}
@Test(expected = DataValidationException.class)
public void testSaveQueueWithInvalidName() {
Queue queue = new Queue();
queue.setTenantId(tenantId);
queue.setName("Test 1");
queue.setTopic("tb_rule_engine.test");
queue.setPollInterval(25);
queue.setPartitions(1);
queue.setPackProcessingTimeout(2000);
queue.setSubmitStrategy(createTestSubmitStrategy());
queue.setProcessingStrategy(createTestProcessingStrategy());
queueService.saveQueue(queue);
}
@Test(expected = DataValidationException.class)
public void testSaveQueueWithEmptyTopic() {
Queue queue = new Queue();
@ -165,6 +179,20 @@ public abstract class BaseQueueServiceTest extends AbstractServiceTest {
queueService.saveQueue(queue);
}
@Test(expected = DataValidationException.class)
public void testSaveQueueWithInvalidTopic() {
Queue queue = new Queue();
queue.setTenantId(tenantId);
queue.setName("Test");
queue.setTopic("tb rule engine test");
queue.setPollInterval(25);
queue.setPartitions(1);
queue.setPackProcessingTimeout(2000);
queue.setSubmitStrategy(createTestSubmitStrategy());
queue.setProcessingStrategy(createTestProcessingStrategy());
queueService.saveQueue(queue);
}
@Test(expected = DataValidationException.class)
public void testSaveQueueWithEmptyPollInterval() {
Queue queue = new Queue();

74
docker/compose-utils.sh

@ -121,3 +121,77 @@ function additionalStartupServices() {
echo $ADDITIONAL_STARTUP_SERVICES
}
function permissionList() {
PERMISSION_LIST="
799 799 tb-node/log
799 799 tb-transports/coap/log
799 799 tb-transports/lwm2m/log
799 799 tb-transports/http/log
799 799 tb-transports/mqtt/log
799 799 tb-transports/snmp/log
799 799 tb-transports/coap/log
799 799 tb-vc-executor/log
999 999 tb-node/postgres
"
source .env
if [ "$DATABASE" = "hybrid" ]; then
PERMISSION_LIST="$PERMISSION_LIST
999 999 tb-node/cassandra
"
fi
CACHE="${CACHE:-redis}"
case $CACHE in
redis)
PERMISSION_LIST="$PERMISSION_LIST
1001 1001 tb-node/redis-data
"
;;
redis-cluster)
PERMISSION_LIST="$PERMISSION_LIST
1001 1001 tb-node/redis-cluster-data-0
1001 1001 tb-node/redis-cluster-data-1
1001 1001 tb-node/redis-cluster-data-2
1001 1001 tb-node/redis-cluster-data-3
1001 1001 tb-node/redis-cluster-data-4
1001 1001 tb-node/redis-cluster-data-5
"
;;
*)
echo "Unknown CACHE value specified in the .env file: '${CACHE}'. Should be either 'redis' or 'redis-cluster'." >&2
exit 1
esac
echo "$PERMISSION_LIST"
}
function checkFolders() {
EXIT_CODE=0
PERMISSION_LIST=$(permissionList) || exit $?
set -e
while read -r USR GRP DIR
do
if [ -z "$DIR" ]; then # skip empty lines
continue
fi
MESSAGE="Checking user ${USR} group ${GRP} dir ${DIR}"
if [[ -d "$DIR" ]] &&
[[ $(ls -ldn "$DIR" | awk '{print $3}') -eq "$USR" ]] &&
[[ $(ls -ldn "$DIR" | awk '{print $4}') -eq "$GRP" ]]
then
MESSAGE="$MESSAGE OK"
else
if [ "$1" = "--create" ]; then
echo "Create and chown: user ${USR} group ${GRP} dir ${DIR}"
mkdir -p "$DIR" && sudo chown -R "$USR":"$GRP" "$DIR"
else
echo "$MESSAGE FAILED"
EXIT_CODE=1
fi
fi
done < <(echo "$PERMISSION_LIST")
return $EXIT_CODE
}

21
docker/docker-check-log-folders.sh

@ -0,0 +1,21 @@
#!/bin/bash
#
# Copyright © 2016-2022 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.
#
set -e
source compose-utils.sh
checkFolders || exit $?
echo "OK"

39
docker/docker-create-log-folders.sh

@ -15,39 +15,6 @@
# limitations under the License.
#
mkdir -p tb-node/log && sudo chown -R 799:799 tb-node/log
mkdir -p tb-transports/coap/log && sudo chown -R 799:799 tb-transports/coap/log
mkdir -p tb-transports/lwm2m/log && sudo chown -R 799:799 tb-transports/lwm2m/log
mkdir -p tb-transports/http/log && sudo chown -R 799:799 tb-transports/http/log
mkdir -p tb-transports/mqtt/log && sudo chown -R 799:799 tb-transports/mqtt/log
mkdir -p tb-transports/snmp/log && sudo chown -R 799:799 tb-transports/snmp/log
mkdir -p tb-vc-executor/log && sudo chown -R 799:799 tb-vc-executor/log
mkdir -p tb-node/postgres && sudo chown -R 999:999 tb-node/postgres
mkdir -p tb-node/cassandra && sudo chown -R 999:999 tb-node/cassandra
source .env
CACHE="${CACHE:-redis}"
case $CACHE in
redis)
mkdir -p tb-node/redis-data && sudo chown -R 1001:1001 tb-node/redis-data
;;
redis-cluster)
mkdir -p tb-node/redis-cluster-data-0 && sudo chown -R 1001:1001 tb-node/redis-cluster-data-0
mkdir -p tb-node/redis-cluster-data-1 && sudo chown -R 1001:1001 tb-node/redis-cluster-data-1
mkdir -p tb-node/redis-cluster-data-2 && sudo chown -R 1001:1001 tb-node/redis-cluster-data-2
mkdir -p tb-node/redis-cluster-data-3 && sudo chown -R 1001:1001 tb-node/redis-cluster-data-3
mkdir -p tb-node/redis-cluster-data-4 && sudo chown -R 1001:1001 tb-node/redis-cluster-data-4
mkdir -p tb-node/redis-cluster-data-5 && sudo chown -R 1001:1001 tb-node/redis-cluster-data-5
;;
*)
echo "Unknown CACHE value specified in the .env file: '${CACHE}'. Should be either 'redis' or 'redis-cluster'." >&2
exit 1
esac
set -e
source compose-utils.sh
checkFolders --create

2
docker/docker-install-tb.sh

@ -49,6 +49,8 @@ ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs) || exit $?
ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $?
checkFolders --create || exit $?
if [ ! -z "${ADDITIONAL_STARTUP_SERVICES// }" ]; then
docker-compose \
-f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \

2
docker/docker-start-services.sh

@ -27,6 +27,8 @@ ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs) || exit $?
ADDITIONAL_COMPOSE_MONITORING_ARGS=$(additionalComposeMonitoringArgs) || exit $?
checkFolders --create || exit $?
docker-compose \
-f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS \
up -d

2
docker/docker-upgrade-tb.sh

@ -48,6 +48,8 @@ ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs) || exit $?
ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $?
checkFolders --create || exit $?
docker-compose \
-f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \
pull \

266
rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java

@ -51,6 +51,7 @@ import org.thingsboard.server.common.data.DeviceProfileInfo;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo;
import org.thingsboard.server.common.data.Event;
@ -77,6 +78,7 @@ import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeInfo;
import org.thingsboard.server.common.data.edge.EdgeSearchQuery;
import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
import org.thingsboard.server.common.data.id.AlarmId;
@ -134,6 +136,19 @@ import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.data.security.model.SecuritySettings;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings;
import org.thingsboard.server.common.data.sync.vc.BranchInfo;
import org.thingsboard.server.common.data.sync.vc.EntityDataDiff;
import org.thingsboard.server.common.data.sync.vc.EntityDataInfo;
import org.thingsboard.server.common.data.sync.vc.EntityVersion;
import org.thingsboard.server.common.data.sync.vc.RepositorySettings;
import org.thingsboard.server.common.data.sync.vc.VersionCreationResult;
import org.thingsboard.server.common.data.sync.vc.VersionLoadResult;
import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo;
import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest;
import org.thingsboard.server.common.data.sync.vc.request.load.VersionLoadRequest;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
@ -147,11 +162,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import static org.springframework.util.StringUtils.isEmpty;
import static org.thingsboard.server.common.data.StringUtils.isEmpty;
/**
* @author Andrew Shvayka
@ -267,6 +283,60 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
return restTemplate.postForEntity(baseURL + "/api/admin/securitySettings", securitySettings, SecuritySettings.class).getBody();
}
public Optional<RepositorySettings> getRepositorySettings() {
try {
ResponseEntity<RepositorySettings> repositorySettings = restTemplate.getForEntity(baseURL + "/api/admin/repositorySettings", RepositorySettings.class);
return Optional.ofNullable(repositorySettings.getBody());
} catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
} else {
throw exception;
}
}
}
public Boolean repositorySettingsExists() {
return restTemplate.getForEntity(baseURL + "/api/admin/repositorySettings/exists", Boolean.class).getBody();
}
public RepositorySettings saveRepositorySettings(RepositorySettings repositorySettings) {
return restTemplate.postForEntity(baseURL + "/api/admin/repositorySettings", repositorySettings, RepositorySettings.class).getBody();
}
public void deleteRepositorySettings() {
restTemplate.delete(baseURL + "/api/admin/repositorySettings");
}
public void checkRepositoryAccess(RepositorySettings repositorySettings) {
restTemplate.postForLocation(baseURL + "/api/admin/repositorySettings/checkAccess", repositorySettings);
}
public Optional<AutoCommitSettings> getAutoCommitSettings() {
try {
ResponseEntity<AutoCommitSettings> autoCommitSettings = restTemplate.getForEntity(baseURL + "/api/admin/autoCommitSettings", AutoCommitSettings.class);
return Optional.ofNullable(autoCommitSettings.getBody());
} catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
} else {
throw exception;
}
}
}
public Boolean autoCommitSettingsExists() {
return restTemplate.getForEntity(baseURL + "/api/admin/autoCommitSettings/exists", Boolean.class).getBody();
}
public AutoCommitSettings saveAutoCommitSettings(AutoCommitSettings autoCommitSettings) {
return restTemplate.postForEntity(baseURL + "/api/admin/autoCommitSettings", autoCommitSettings, AutoCommitSettings.class).getBody();
}
public void deleteAutoCommitSettings() {
restTemplate.delete(baseURL + "/api/admin/autoCommitSettings");
}
public Optional<UpdateMessage> checkUpdates() {
try {
ResponseEntity<UpdateMessage> updateMsg = restTemplate.getForEntity(baseURL + "/api/admin/updates", UpdateMessage.class);
@ -550,6 +620,15 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
}).getBody();
}
public BulkImportResult<Asset> processAssetsBulkImport(BulkImportRequest request) {
return restTemplate.exchange(
baseURL + "/api/asset/bulk_import",
HttpMethod.POST,
new HttpEntity<>(request),
new ParameterizedTypeReference<BulkImportResult<Asset>>() {
}).getBody();
}
@Deprecated
public Optional<Asset> findAsset(String name) {
Map<String, String> params = new HashMap<String, String>();
@ -1251,7 +1330,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
HttpEntity.EMPTY, Device.class, tenantId, deviceId).getBody();
}
public Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(OtaPackageType otaPackageType, DeviceProfileId deviceProfileId) {
public Long countByDeviceProfileAndEmptyOtaPackage(OtaPackageType otaPackageType, DeviceProfileId deviceProfileId) {
Map<String, String> params = new HashMap<>();
params.put("otaPackageType", otaPackageType.name());
params.put("deviceProfileId", deviceProfileId.getId().toString());
@ -1266,6 +1345,15 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
).getBody();
}
public BulkImportResult<Device> processDevicesBulkImport(BulkImportRequest request) {
return restTemplate.exchange(
baseURL + "/api/device/bulk_import",
HttpMethod.POST,
new HttpEntity<>(request),
new ParameterizedTypeReference<BulkImportResult<Device>>() {
}).getBody();
}
@Deprecated
public Device createDevice(String name, String type) {
Device device = new Device();
@ -2499,6 +2587,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
}
}
public Boolean isEdgesSupportEnabled() {
return restTemplate.getForEntity(baseURL + "/api/edges/enabled", Boolean.class).getBody();
}
public Edge saveEdge(Edge edge) {
return restTemplate.postForEntity(baseURL + "/api/edge", edge, Edge.class).getBody();
}
@ -2520,6 +2612,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
}
}
public Optional<EdgeInfo> getEdgeInfoById(EdgeId edgeId) {
try {
ResponseEntity<EdgeInfo> edge = restTemplate.getForEntity(baseURL + "/api/edge/info/{edgeId}", EdgeInfo.class, edgeId.getId());
return Optional.ofNullable(edge.getBody());
} catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
} else {
throw exception;
}
}
}
public Optional<Edge> assignEdgeToCustomer(CustomerId customerId, EdgeId edgeId) {
try {
ResponseEntity<Edge> edge = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/edge/{edgeId}", null, Edge.class, customerId.getId(), edgeId.getId());
@ -2546,7 +2651,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
}
}
public Optional<Edge> setRootRuleChain(EdgeId edgeId, RuleChainId ruleChainId) {
public Optional<Edge> setEdgeRootRuleChain(EdgeId edgeId, RuleChainId ruleChainId) {
try {
ResponseEntity<Edge> ruleChain = restTemplate.postForEntity(baseURL + "/api/edge/{edgeId}/{ruleChainId}/root", null, Edge.class, edgeId.getId(), ruleChainId.getId());
return Optional.ofNullable(ruleChain.getBody());
@ -2827,6 +2932,17 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
}, params).getBody();
}
public PageData<EdgeInfo> getTenantEdgeInfos(String type, PageLink pageLink) {
Map<String, String> params = new HashMap<>();
params.put("type", type);
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/tenant/edgeInfos?type={type}&" + getUrlParams(pageLink),
HttpMethod.GET, HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<EdgeInfo>>() {
}, params).getBody();
}
public Optional<Edge> getTenantEdge(String edgeName) {
try {
ResponseEntity<Edge> edge = restTemplate.getForEntity(baseURL + "/api/tenant/edges?edgeName={edgeName}", Edge.class, edgeName);
@ -2852,6 +2968,18 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
}, params).getBody();
}
public PageData<EdgeInfo> getCustomerEdgeInfos(CustomerId customerId, PageLink pageLink, String edgeType) {
Map<String, String> params = new HashMap<>();
params.put("customerId", customerId.getId().toString());
params.put("type", edgeType);
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/customer/{customerId}/edgeInfos?type={type}&" + getUrlParams(pageLink),
HttpMethod.GET, HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<EdgeInfo>>() {
}, params).getBody();
}
public List<Edge> getEdgesByIds(List<EdgeId> edgeIds) {
return restTemplate.exchange(baseURL + "/api/edges?edgeIds={edgeIds}",
HttpMethod.GET,
@ -2896,6 +3024,138 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
restTemplate.postForEntity(baseURL + "/api/edge/sync/{edgeId}", null, EdgeId.class, params);
}
public String findMissingToRelatedRuleChains(EdgeId edgeId) {
return restTemplate.getForEntity(baseURL + "/api/edge/missingToRelatedRuleChains/{edgeId}", String.class, edgeId.getId()).getBody();
}
public BulkImportResult<Edge> processEdgesBulkImport(BulkImportRequest request) {
return restTemplate.exchange(
baseURL + "/api/edge/bulk_import",
HttpMethod.POST,
new HttpEntity<>(request),
new ParameterizedTypeReference<BulkImportResult<Edge>>() {
}).getBody();
}
public UUID saveEntitiesVersion(VersionCreateRequest request) {
return restTemplate.postForEntity(baseURL + "/api/entities/vc/version", request, UUID.class).getBody();
}
public Optional<VersionCreationResult> getVersionCreateRequestStatus(UUID requestId) {
try {
ResponseEntity<VersionCreationResult> versionCreateResult = restTemplate.getForEntity(baseURL + "/api/entities/vc/version/{requestId}/status", VersionCreationResult.class, requestId);
return Optional.ofNullable(versionCreateResult.getBody());
} catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
} else {
throw exception;
}
}
}
public PageData<EntityVersion> listEntityVersions(EntityId externalEntityId, String branch, PageLink pageLink) {
Map<String, String> params = new HashMap<>();
params.put("entityType", externalEntityId.getEntityType().name());
params.put("externalEntityUuid", externalEntityId.getId().toString());
params.put("branch", branch);
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/entities/vc/version/{entityType}/{externalEntityUuid}?branch={branch}&" + getUrlParams(pageLink),
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<EntityVersion>>() {
},
params).getBody();
}
public PageData<EntityVersion> listEntityTypeVersions(EntityType entityType, String branch, PageLink pageLink) {
Map<String, String> params = new HashMap<>();
params.put("entityType", entityType.name());
params.put("branch", branch);
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/entities/vc/version/{entityType}?branch={branch}&" + getUrlParams(pageLink),
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<EntityVersion>>() {
},
params).getBody();
}
public PageData<EntityVersion> listVersions(String branch, PageLink pageLink) {
Map<String, String> params = new HashMap<>();
params.put("branch", branch);
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/entities/vc/version?branch={branch}&" + getUrlParams(pageLink),
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<EntityVersion>>() {
},
params).getBody();
}
public List<VersionedEntityInfo> listEntitiesAtVersion(EntityType entityType, String versionId) {
Map<String, String> params = new HashMap<>();
params.put("entityType", entityType.name());
params.put("versionId", versionId);
return restTemplate.exchange(
baseURL + "/api/entities/vc/entity/{entityType}/{versionId}",
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<List<VersionedEntityInfo>>() {
},
params).getBody();
}
public List<VersionedEntityInfo> listAllEntitiesAtVersion(String versionId) {
Map<String, String> params = new HashMap<>();
params.put("versionId", versionId);
return restTemplate.exchange(
baseURL + "/api/entities/vc/entity/{versionId}",
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<List<VersionedEntityInfo>>() {
},
params).getBody();
}
public EntityDataInfo getEntityDataInfo(EntityId externalEntityId, String versionId) {
return restTemplate.getForEntity(baseURL + "/api/entities/vc/info/{versionId}/{entityType}/{externalEntityUuid}",
EntityDataInfo.class, versionId, externalEntityId.getEntityType(), externalEntityId.getId()).getBody();
}
public EntityDataDiff compareEntityDataToVersion(EntityId internalEntityId, String versionId) {
return restTemplate.getForEntity(baseURL + "/api/entities/vc/diff/{entityType}/{internalEntityUuid}?versionId={versionId}",
EntityDataDiff.class, internalEntityId.getEntityType(), internalEntityId.getId(), versionId).getBody();
}
public UUID loadEntitiesVersion(VersionLoadRequest request) {
return restTemplate.postForEntity(baseURL + "/api/entities/vc/entity", request, UUID.class).getBody();
}
public Optional<VersionLoadResult> getVersionLoadRequestStatus(UUID requestId) {
try {
ResponseEntity<VersionLoadResult> versionLoadResult = restTemplate.getForEntity(baseURL + "/api/entities/vc/entity/{requestId}/status", VersionLoadResult.class, requestId);
return Optional.ofNullable(versionLoadResult.getBody());
} catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
} else {
throw exception;
}
}
}
public List<BranchInfo> listBranches() {
return restTemplate.exchange(
baseURL + "/api/entities/vc/branches",
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<List<BranchInfo>>() {
}).getBody();
}
public ResponseEntity<Resource> downloadResource(TbResourceId resourceId) {
Map<String, String> params = new HashMap<>();
params.put("resourceId", resourceId.getId().toString());

6
ui-ngx/src/app/core/http/entities-version-control.service.ts

@ -158,12 +158,10 @@ export class EntitiesVersionControlService {
defaultHttpOptionsFromConfig({...config, ...{ignoreLoading: true}}));
}
public compareEntityDataToVersion(branch: string,
entityId: EntityId,
public compareEntityDataToVersion(entityId: EntityId,
versionId: string,
config?: RequestConfig): Observable<EntityDataDiff> {
const encodedBranch = encodeURIComponent(branch);
return this.http.get<EntityDataDiff>(`/api/entities/vc/diff/${entityId.entityType}/${entityId.id}?branch=${encodedBranch}&versionId=${versionId}`,
return this.http.get<EntityDataDiff>(`/api/entities/vc/diff/${entityId.entityType}/${entityId.id}?versionId=${versionId}`,
defaultHttpOptionsFromConfig(config));
}

3
ui-ngx/src/app/modules/home/components/queue/queue-form.component.html

@ -26,6 +26,9 @@
<mat-error *ngIf="queueFormGroup.get('name').hasError('unique')">
{{ 'queue.name-unique' | translate }}
</mat-error>
<mat-error *ngIf="queueFormGroup.get('name').hasError('pattern')">
{{ 'queue.name-pattern' | translate }}
</mat-error>
</mat-form-field>
<mat-accordion class="queue-strategy" [multi]="true">
<mat-expansion-panel [expanded]="true">

4
ui-ngx/src/app/modules/home/components/queue/queue-form.component.ts

@ -99,7 +99,7 @@ export class QueueFormComponent implements ControlValueAccessor, OnInit, OnDestr
ngOnInit() {
this.queueFormGroup = this.fb.group(
{
name: ['', [Validators.required]],
name: ['', [Validators.required, Validators.pattern(/^[a-zA-Z0-9_.\-]+$/)]],
pollInterval: [25, [Validators.min(1), Validators.required]],
partitions: [10, [Validators.min(1), Validators.required]],
consumerPerPartition: [false, []],
@ -205,10 +205,12 @@ export class QueueFormComponent implements ControlValueAccessor, OnInit, OnDestr
if (type === QueueSubmitStrategyTypes.BATCH) {
batchSizeField.patchValue(1000, {emitEvent: false});
batchSizeField.setValidators([Validators.min(1), Validators.required]);
batchSizeField.updateValueAndValidity({emitEvent: false});
this.hideBatchSize = true;
} else {
batchSizeField.patchValue(null, {emitEvent: false});
batchSizeField.clearValidators();
batchSizeField.updateValueAndValidity({emitEvent: false});
this.hideBatchSize = false;
}
}

4
ui-ngx/src/app/modules/home/components/vc/complex-version-load.component.ts

@ -41,9 +41,6 @@ import { parseHttpErrorMessage } from '@core/utils';
})
export class ComplexVersionLoadComponent extends PageComponent implements OnInit, OnDestroy {
@Input()
branch: string;
@Input()
versionName: string;
@ -117,7 +114,6 @@ export class ComplexVersionLoadComponent extends PageComponent implements OnInit
restore(): void {
const request: EntityTypeVersionLoadRequest = {
branch: this.branch,
versionId: this.versionId,
entityTypes: this.loadVersionFormGroup.get('entityTypes').value,
type: VersionLoadRequestType.ENTITY_TYPE

6
ui-ngx/src/app/modules/home/components/vc/entity-version-diff.component.ts

@ -59,9 +59,6 @@ export class EntityVersionDiffComponent extends PageComponent implements OnInit,
@ViewChild('diffViewer', {static: true})
diffViewerElmRef: ElementRef<HTMLElement>;
@Input()
branch: string;
@Input()
versionName: string;
@ -108,7 +105,7 @@ export class EntityVersionDiffComponent extends PageComponent implements OnInit,
ngOnInit(): void {
this.entitiesVersionControlService
.compareEntityDataToVersion(this.branch, this.entityId, this.versionId).subscribe((diffData) => {
.compareEntityDataToVersion(this.entityId, this.versionId).subscribe((diffData) => {
const leftContent = entityExportDataToJsonString(diffData.currentVersion);
const rightContent = entityExportDataToJsonString(diffData.otherVersion);
const leftLines = leftContent.split('\n').length;
@ -310,7 +307,6 @@ export class EntityVersionDiffComponent extends PageComponent implements OnInit,
const restoreVersionPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, EntityVersionRestoreComponent, 'leftTop', true, null,
{
branch: this.branch,
versionName: this.versionName,
versionId: this.versionId,
externalEntityId: this.externalEntityId,

4
ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.ts

@ -41,9 +41,6 @@ import { parseHttpErrorMessage } from '@core/utils';
})
export class EntityVersionRestoreComponent extends PageComponent implements OnInit, OnDestroy {
@Input()
branch: string;
@Input()
versionName: string;
@ -108,7 +105,6 @@ export class EntityVersionRestoreComponent extends PageComponent implements OnIn
restore(): void {
const request: SingleEntityVersionLoadRequest = {
branch: this.branch,
versionId: this.versionId,
externalEntityId: this.externalEntityId,
config: {

3
ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts

@ -260,7 +260,6 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
const diffVersionPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, EntityVersionDiffComponent, 'leftTop', true, null,
{
branch: this.branch,
versionName: entityVersion.name,
versionId: entityVersion.id,
entityId: this.entityId,
@ -284,7 +283,6 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
const restoreVersionPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, EntityVersionRestoreComponent, 'leftTop', true, null,
{
branch: this.branch,
versionName: entityVersion.name,
versionId: entityVersion.id,
externalEntityId: this.externalEntityIdValue,
@ -310,7 +308,6 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
const restoreEntitiesVersionPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, ComplexVersionLoadComponent, 'leftTop', true, null,
{
branch: this.branch,
versionName: entityVersion.name,
versionId: entityVersion.id,
onClose: (result: VersionLoadResult | null) => {

1
ui-ngx/src/app/shared/models/vc.models.ts

@ -113,7 +113,6 @@ export enum VersionLoadRequestType {
}
export interface VersionLoadRequest {
branch: string;
versionId: string;
type: VersionLoadRequestType;
}

1
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2928,6 +2928,7 @@
"name": "Name",
"name-required": "Queue name is required!",
"name-unique": "Queue name is not unique!",
"name-pattern": "Queue name contains a character other than ASCII alphanumerics, '.', '_' and '-'!",
"queue-required": "Queue is required!",
"topic-required": "Queue topic is required!",
"poll-interval-required": "Poll interval is required!",

Loading…
Cancel
Save