committed by
GitHub
420 changed files with 10187 additions and 1358 deletions
@ -0,0 +1,259 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.controller; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import io.swagger.v3.oas.annotations.Parameter; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.RequiredArgsConstructor; |
|||
import org.springframework.http.HttpStatus; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.web.bind.annotation.DeleteMapping; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.PathVariable; |
|||
import org.springframework.web.bind.annotation.PostMapping; |
|||
import org.springframework.web.bind.annotation.RequestBody; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
import org.springframework.web.bind.annotation.ResponseStatus; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.EventInfo; |
|||
import org.thingsboard.server.common.data.cf.AlarmRuleDefinition; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
|||
import org.thingsboard.server.common.data.cf.AlarmRuleDefinitionInfo; |
|||
import org.thingsboard.server.common.data.cf.CalculatedField; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldFilter; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldInfo; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldType; |
|||
import org.thingsboard.server.common.data.event.EventType; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.CalculatedFieldId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.EntityIdFactory; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.config.annotations.ApiOperation; |
|||
import org.thingsboard.server.dao.event.EventService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.entitiy.cf.TbCalculatedFieldService; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.security.permission.Operation; |
|||
|
|||
import java.util.EnumSet; |
|||
import java.util.Map; |
|||
import java.util.Optional; |
|||
import java.util.Set; |
|||
import java.util.UUID; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.thingsboard.server.controller.ControllerConstants.CF_TEXT_SEARCH_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_PARAM_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END; |
|||
import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_START; |
|||
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; |
|||
import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; |
|||
|
|||
@RestController |
|||
@TbCoreComponent |
|||
@RequestMapping("/api") |
|||
@RequiredArgsConstructor |
|||
public class AlarmRuleController extends BaseController { |
|||
|
|||
private final TbCalculatedFieldService tbCalculatedFieldService; |
|||
private final EventService eventService; |
|||
|
|||
public static final String ALARM_RULE_ID = "alarmRuleId"; |
|||
|
|||
private static final String TEST_SCRIPT_EXPRESSION = |
|||
"Execute the alarm rule TBEL condition expression and return the result. " + |
|||
"Alarm rule expressions must return a boolean value. The format of request: \n\n" |
|||
+ MARKDOWN_CODE_BLOCK_START |
|||
+ "{\n" + |
|||
" \"expression\": \"return temperature > 50;\",\n" + |
|||
" \"arguments\": {\n" + |
|||
" \"temperature\": { \"type\": \"SINGLE_VALUE\", \"ts\": 1739776478057, \"value\": 55 }\n" + |
|||
" }\n" + |
|||
"}" |
|||
+ MARKDOWN_CODE_BLOCK_END |
|||
+ "\n\n Expected result JSON contains \"output\" and \"error\"."; |
|||
|
|||
@ApiOperation(value = "Create Or Update Alarm Rule (saveAlarmRule)", |
|||
notes = "Creates or Updates the Alarm Rule. When creating alarm rule, platform generates Alarm Rule Id as " + UUID_WIKI_LINK + |
|||
"The newly created Alarm Rule Id will be present in the response. " + |
|||
"Specify existing Alarm Rule Id to update the alarm rule. " + |
|||
"Referencing non-existing Alarm Rule Id will cause 'Not Found' error. " + |
|||
"Remove 'id', 'tenantId' from the request body example (below) to create new Alarm Rule entity. " |
|||
+ TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@PostMapping("/alarm/rule") |
|||
public AlarmRuleDefinition saveAlarmRule(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the alarm rule.") |
|||
@RequestBody AlarmRuleDefinition alarmRuleDefinition) throws Exception { |
|||
alarmRuleDefinition.setTenantId(getTenantId()); |
|||
checkEntityId(alarmRuleDefinition.getEntityId(), Operation.WRITE_CALCULATED_FIELD); |
|||
if (alarmRuleDefinition.getId() != null) { |
|||
checkAlarmRule(alarmRuleDefinition.getId()); |
|||
} |
|||
CalculatedField calculatedField = alarmRuleDefinition.toCalculatedField(); |
|||
checkReferencedEntities(calculatedField.getConfiguration()); |
|||
CalculatedField saved = tbCalculatedFieldService.save(calculatedField, getCurrentUser()); |
|||
return AlarmRuleDefinition.fromCalculatedField(saved); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get Alarm Rule (getAlarmRuleById)", |
|||
notes = "Fetch the Alarm Rule object based on the provided Alarm Rule Id." + TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@GetMapping("/alarm/rule/{alarmRuleId}") |
|||
public AlarmRuleDefinition getAlarmRuleById(@Parameter @PathVariable(ALARM_RULE_ID) String strAlarmRuleId) throws ThingsboardException { |
|||
checkParameter(ALARM_RULE_ID, strAlarmRuleId); |
|||
CalculatedFieldId calculatedFieldId = new CalculatedFieldId(toUUID(strAlarmRuleId)); |
|||
CalculatedField calculatedField = checkAlarmRule(calculatedFieldId); |
|||
checkEntityId(calculatedField.getEntityId(), Operation.READ_CALCULATED_FIELD); |
|||
return AlarmRuleDefinition.fromCalculatedField(calculatedField); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get Alarm Rules by Entity Id (getAlarmRulesByEntityId)", |
|||
notes = "Fetch the Alarm Rules based on the provided Entity Id." + TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@GetMapping(value = "/alarm/rules/{entityType}/{entityId}") |
|||
public PageData<AlarmRuleDefinition> getAlarmRulesByEntityId( |
|||
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, |
|||
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, |
|||
@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, |
|||
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, |
|||
@Parameter(description = CF_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, |
|||
@Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name"})) @RequestParam(required = false) String sortProperty, |
|||
@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
|||
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
|||
checkParameter("entityId", entityIdStr); |
|||
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, entityIdStr); |
|||
checkEntityId(entityId, Operation.READ_CALCULATED_FIELD); |
|||
PageData<CalculatedField> result = checkNotNull(tbCalculatedFieldService.findByTenantIdAndEntityId(getTenantId(), entityId, CalculatedFieldType.ALARM, pageLink)); |
|||
return result.mapData(AlarmRuleDefinition::fromCalculatedField); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get alarm rules (getAlarmRules)", |
|||
notes = "Fetch tenant alarm rules based on the filter." + TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@GetMapping(value = "/alarm/rules") |
|||
public PageData<AlarmRuleDefinitionInfo> getAlarmRules(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) |
|||
@RequestParam int pageSize, |
|||
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) |
|||
@RequestParam int page, |
|||
@Parameter(description = "Entity type filter. If not specified, alarm rules for all supported entity types will be returned.") |
|||
@RequestParam(required = false) EntityType entityType, |
|||
@Parameter(description = "Entities filter. If not specified, alarm rules for entity type filter will be returned.") |
|||
@RequestParam(required = false) Set<UUID> entities, |
|||
@Parameter(description = CF_TEXT_SEARCH_DESCRIPTION) |
|||
@RequestParam(required = false) String textSearch, |
|||
@Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name"})) |
|||
@RequestParam(required = false) String sortProperty, |
|||
@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) |
|||
@RequestParam(required = false) String sortOrder) throws ThingsboardException { |
|||
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
|||
SecurityUser user = getCurrentUser(); |
|||
|
|||
Set<EntityType> entityTypes; |
|||
if (entityType == null) { |
|||
entityTypes = CalculatedField.SUPPORTED_ENTITIES.entrySet().stream() |
|||
.filter(entry -> entry.getValue().contains(CalculatedFieldType.ALARM)) |
|||
.map(Map.Entry::getKey) |
|||
.collect(Collectors.toSet()); |
|||
} else { |
|||
entityTypes = EnumSet.of(entityType); |
|||
} |
|||
|
|||
CalculatedFieldFilter filter = CalculatedFieldFilter.builder() |
|||
.types(EnumSet.of(CalculatedFieldType.ALARM)) |
|||
.entityTypes(entityTypes) |
|||
.entityIds(entities) |
|||
.build(); |
|||
PageData<CalculatedFieldInfo> result = calculatedFieldService.findCalculatedFieldsByTenantIdAndFilter(user.getTenantId(), filter, pageLink); |
|||
return result.mapData(AlarmRuleDefinitionInfo::fromCalculatedFieldInfo); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get alarm rule names (getAlarmRuleNames)", |
|||
notes = "Fetch the list of alarm rule names." + TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@GetMapping(value = "/alarm/rules/names") |
|||
public PageData<String> getAlarmRuleNames(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) |
|||
@RequestParam int pageSize, |
|||
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) |
|||
@RequestParam int page, |
|||
@Parameter(description = CF_TEXT_SEARCH_DESCRIPTION) |
|||
@RequestParam(required = false) String textSearch, |
|||
@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) |
|||
@RequestParam(required = false) String sortOrder) throws ThingsboardException { |
|||
PageLink pageLink = createPageLink(pageSize, page, textSearch, "name", sortOrder); |
|||
return calculatedFieldService.findCalculatedFieldNamesByTenantIdAndType(getTenantId(), CalculatedFieldType.ALARM, pageLink); |
|||
} |
|||
|
|||
@ApiOperation(value = "Delete Alarm Rule (deleteAlarmRule)", |
|||
notes = "Deletes the alarm rule. Referencing non-existing Alarm Rule Id will cause an error." + TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@DeleteMapping("/alarm/rule/{alarmRuleId}") |
|||
@ResponseStatus(HttpStatus.OK) |
|||
public void deleteAlarmRule(@PathVariable(ALARM_RULE_ID) String strAlarmRuleId) throws Exception { |
|||
checkParameter(ALARM_RULE_ID, strAlarmRuleId); |
|||
CalculatedFieldId calculatedFieldId = new CalculatedFieldId(toUUID(strAlarmRuleId)); |
|||
CalculatedField calculatedField = checkAlarmRule(calculatedFieldId); |
|||
checkEntityId(calculatedField.getEntityId(), Operation.WRITE_CALCULATED_FIELD); |
|||
tbCalculatedFieldService.delete(calculatedField, getCurrentUser()); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get latest alarm rule debug event (getLatestAlarmRuleDebugEvent)", |
|||
notes = "Gets latest alarm rule debug event for specified alarm rule id. " + |
|||
"Referencing non-existing alarm rule id will cause an error. " + TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@GetMapping("/alarm/rule/{alarmRuleId}/debug") |
|||
public JsonNode getLatestAlarmRuleDebugEvent(@Parameter @PathVariable(ALARM_RULE_ID) String strAlarmRuleId) throws ThingsboardException { |
|||
checkParameter(ALARM_RULE_ID, strAlarmRuleId); |
|||
CalculatedFieldId calculatedFieldId = new CalculatedFieldId(toUUID(strAlarmRuleId)); |
|||
CalculatedField calculatedField = checkAlarmRule(calculatedFieldId); |
|||
checkEntityId(calculatedField.getEntityId(), Operation.READ_CALCULATED_FIELD); |
|||
TenantId tenantId = getCurrentUser().getTenantId(); |
|||
return Optional.ofNullable(eventService.findLatestEvents(tenantId, calculatedFieldId, EventType.DEBUG_CALCULATED_FIELD, 1)) |
|||
.flatMap(events -> events.stream().map(EventInfo::getBody).findFirst()) |
|||
.orElse(null); |
|||
} |
|||
|
|||
@ApiOperation(value = "Test alarm rule TBEL expression (testAlarmRuleScript)", |
|||
notes = TEST_SCRIPT_EXPRESSION + TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@PostMapping("/alarm/rule/testScript") |
|||
public JsonNode testAlarmRuleScript( |
|||
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Test alarm rule TBEL condition expression. The expression must return a boolean value.") |
|||
@RequestBody JsonNode inputParams) throws ThingsboardException { |
|||
checkParameter("expression", inputParams.has("expression") ? inputParams.get("expression").asText() : null); |
|||
return tbCalculatedFieldService.executeTestScript(getTenantId(), inputParams); |
|||
} |
|||
|
|||
private CalculatedField checkAlarmRule(CalculatedFieldId calculatedFieldId) throws ThingsboardException { |
|||
CalculatedField calculatedField = tbCalculatedFieldService.findById(calculatedFieldId, getCurrentUser()); |
|||
checkNotNull(calculatedField); |
|||
if (calculatedField.getType() != CalculatedFieldType.ALARM) { |
|||
throw new ThingsboardException("Alarm rule not found", ThingsboardErrorCode.ITEM_NOT_FOUND); |
|||
} |
|||
return calculatedField; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
# |
|||
# Copyright © 2016-2026 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. |
|||
# |
|||
|
|||
# Lightweight startup for OpenAPI spec generation. |
|||
# Activated only via: --spring.profiles.active=openapi |
|||
|
|||
spring.main.banner-mode=off |
|||
|
|||
# Testcontainers PostgreSQL |
|||
database.ts.type=sql |
|||
database.ts_latest.type=sql |
|||
spring.datasource.url=jdbc:tc:postgresql:16.6:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.PostgreSqlInitializer::initDb |
|||
spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver |
|||
spring.datasource.username=postgres |
|||
spring.datasource.password=postgres |
|||
spring.datasource.hikari.maximumPoolSize=5 |
|||
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true |
|||
spring.jpa.properties.hibernate.order_by.default_null_ordering=last |
|||
spring.jpa.show-sql=false |
|||
spring.jpa.hibernate.ddl-auto=none |
|||
|
|||
# Disable transports |
|||
transport.http.enabled=false |
|||
transport.mqtt.enabled=false |
|||
transport.coap.enabled=false |
|||
transport.lwm2m.enabled=false |
|||
transport.snmp.enabled=false |
|||
coap.server.enabled=false |
|||
|
|||
# Disable edges, integrations, EDQS |
|||
edges.enabled=false |
|||
integrations.rpc.enabled=false |
|||
service.integrations.supported=NONE |
|||
transport.gateway.dashboard.sync.enabled=false |
|||
queue.edqs.sync.enabled=false |
|||
queue.edqs.api.supported=false |
|||
usage.stats.report.enabled=false |
|||
service.type=monolith |
|||
@ -0,0 +1,168 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.AiModel; |
|||
import org.thingsboard.client.model.OpenAiChatModelConfig; |
|||
import org.thingsboard.client.model.OpenAiProviderConfig; |
|||
import org.thingsboard.client.model.PageDataAiModel; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertFalse; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class AIModelApiClientTest extends AbstractApiClientTest { |
|||
|
|||
private static final String AI_PREFIX = "AiTest_"; |
|||
|
|||
@Test |
|||
public void testSaveAndGetAiModel() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
String name = AI_PREFIX + "save_" + ts; |
|||
|
|||
AiModel model = buildAiModel(name, "gpt-4o", 0.7); |
|||
AiModel saved = client.saveAiModel(model); |
|||
assertNotNull(saved); |
|||
assertNotNull(saved.getId()); |
|||
assertEquals(name, saved.getName()); |
|||
assertNotNull(saved.getConfiguration()); |
|||
|
|||
// get by id
|
|||
AiModel fetched = client.getAiModelById(saved.getId().getId()); |
|||
assertNotNull(fetched); |
|||
assertEquals(name, fetched.getName()); |
|||
assertEquals(saved.getId().getId(), fetched.getId().getId()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAiModelById() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
AiModel saved = createAiModel("getbyid_" + ts); |
|||
|
|||
AiModel fetched = client.getAiModelById(saved.getId().getId()); |
|||
assertNotNull(fetched); |
|||
assertEquals(saved.getName(), fetched.getName()); |
|||
assertEquals(saved.getId().getId(), fetched.getId().getId()); |
|||
} |
|||
|
|||
@Test |
|||
public void testUpdateAiModel() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
AiModel saved = createAiModel("update_" + ts); |
|||
|
|||
saved.setName(AI_PREFIX + "updated_" + ts); |
|||
OpenAiChatModelConfig updatedConfig = new OpenAiChatModelConfig(); |
|||
updatedConfig.setModelId("gpt-4o-mini"); |
|||
updatedConfig.setTemperature(0.3); |
|||
updatedConfig.setMaxOutputTokens(2048); |
|||
updatedConfig.setMaxRetries(50); |
|||
OpenAiProviderConfig providerConfig = new OpenAiProviderConfig(); |
|||
providerConfig.setApiKey("test-api-key"); |
|||
providerConfig.setBaseUrl("https://api.openai.com/v1"); |
|||
updatedConfig.setProviderConfig(providerConfig); |
|||
updatedConfig.setProvider("OPENAI"); |
|||
saved.setConfiguration(updatedConfig); |
|||
|
|||
AiModel updated = client.saveAiModel(saved); |
|||
assertNotNull(updated); |
|||
assertEquals(saved.getId().getId(), updated.getId().getId()); |
|||
assertEquals(AI_PREFIX + "updated_" + ts, updated.getName()); |
|||
} |
|||
|
|||
@Test |
|||
public void testDeleteAiModel() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
AiModel saved = createAiModel("delete_" + ts); |
|||
|
|||
UUID modelId = saved.getId().getId(); |
|||
client.getAiModelById(modelId); |
|||
|
|||
Boolean deleted = client.deleteAiModelById(modelId); |
|||
assertTrue(deleted); |
|||
|
|||
assertReturns404(() -> client.getAiModelById(modelId)); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAiModels() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
|
|||
for (int i = 0; i < 3; i++) { |
|||
createAiModel("list_" + ts + "_" + i); |
|||
} |
|||
|
|||
PageDataAiModel page = client.getAiModels(100, 0, AI_PREFIX + "list_" + ts, null, null); |
|||
assertNotNull(page); |
|||
assertEquals(3, page.getTotalElements().intValue()); |
|||
for (AiModel m : page.getData()) { |
|||
assertTrue(m.getName().startsWith(AI_PREFIX + "list_" + ts)); |
|||
} |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAiModelById_notFound() { |
|||
UUID nonExistentId = UUID.randomUUID(); |
|||
assertReturns404(() -> client.getAiModelById(nonExistentId)); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAiModelsPagination() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
|
|||
for (int i = 0; i < 5; i++) { |
|||
createAiModel("paged_" + ts + "_" + i); |
|||
} |
|||
|
|||
PageDataAiModel page1 = client.getAiModels(2, 0, AI_PREFIX + "paged_" + ts, null, null); |
|||
assertNotNull(page1); |
|||
assertEquals(5, page1.getTotalElements().intValue()); |
|||
assertEquals(3, page1.getTotalPages().intValue()); |
|||
assertEquals(2, page1.getData().size()); |
|||
assertTrue(page1.getHasNext()); |
|||
|
|||
PageDataAiModel lastPage = client.getAiModels(2, 2, AI_PREFIX + "paged_" + ts, null, null); |
|||
assertEquals(1, lastPage.getData().size()); |
|||
assertFalse(lastPage.getHasNext()); |
|||
} |
|||
|
|||
private AiModel buildAiModel(String name, String modelId, double temperature) { |
|||
OpenAiChatModelConfig config = new OpenAiChatModelConfig(); |
|||
config.setModelId(modelId); |
|||
config.setTemperature(temperature); |
|||
config.setMaxRetries(50); |
|||
OpenAiProviderConfig openAiProviderConfig = new OpenAiProviderConfig(); |
|||
openAiProviderConfig.setApiKey("test-api-key"); |
|||
openAiProviderConfig.setBaseUrl("https://api.openai.com/v1"); |
|||
config.setProviderConfig(openAiProviderConfig); |
|||
config.setProvider("OPENAI"); |
|||
|
|||
AiModel model = new AiModel(); |
|||
model.setName(name); |
|||
model.setConfiguration(config); |
|||
return model; |
|||
} |
|||
|
|||
private AiModel createAiModel(String suffix) throws Exception { |
|||
return client.saveAiModel(buildAiModel(AI_PREFIX + suffix, "gpt-4o", 0.7)); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,127 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.junit.After; |
|||
import org.junit.Before; |
|||
import org.thingsboard.client.ApiException; |
|||
import org.thingsboard.client.ThingsboardClient; |
|||
import org.thingsboard.client.model.ActivateUserRequest; |
|||
import org.thingsboard.client.model.Authority; |
|||
import org.thingsboard.client.model.JwtPair; |
|||
import org.thingsboard.client.model.User; |
|||
import org.thingsboard.client.model.UserId; |
|||
import org.thingsboard.server.common.data.util.ThrowingRunnable; |
|||
import org.thingsboard.server.controller.AbstractControllerTest; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.fail; |
|||
|
|||
@Slf4j |
|||
public abstract class AbstractApiClientTest extends AbstractControllerTest { |
|||
|
|||
protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); |
|||
protected static final ObjectMapper MAPPER = new ObjectMapper(); |
|||
|
|||
protected static final String TEST_PREFIX = "ApiClientTestDevice_"; |
|||
protected static final String TEST_PREFIX_2 = "ApiClientTestDevice2_"; |
|||
protected static final String CUSTOMER_USERNAME = "javaClientCustomer@thingsboard.org"; |
|||
protected static final String TENANT_ADMIN_USERNAME = "javaClientTenant@thingsboard.org"; |
|||
protected static final String TEST_PASSWORD = "password123"; |
|||
|
|||
protected ThingsboardClient client; |
|||
|
|||
// FQN for Tenant/Customer to avoid collision with AbstractWebTest fields
|
|||
protected org.thingsboard.client.model.Tenant savedClientTenant; |
|||
protected User clientTenantAdmin; |
|||
protected org.thingsboard.client.model.Customer savedClientCustomer; |
|||
protected User savedClientCustomerUser; |
|||
|
|||
@Before |
|||
public void setUpJavaClient() throws Exception { |
|||
client = ThingsboardClient.builder() |
|||
.url("http://localhost:" + wsPort) |
|||
.build(); |
|||
client.login("sysadmin@thingsboard.org", "sysadmin"); |
|||
|
|||
org.thingsboard.client.model.Tenant tenant = new org.thingsboard.client.model.Tenant(); |
|||
tenant.setTitle("Java client test tenant"); |
|||
savedClientTenant = client.saveTenant(tenant); |
|||
|
|||
clientTenantAdmin = new User(); |
|||
clientTenantAdmin.setAuthority(Authority.TENANT_ADMIN); |
|||
clientTenantAdmin.setTenantId(savedClientTenant.getId()); |
|||
clientTenantAdmin.setEmail(TENANT_ADMIN_USERNAME); |
|||
clientTenantAdmin = client.saveUser(clientTenantAdmin, "false"); |
|||
activateUserAndAuthorize(clientTenantAdmin); |
|||
|
|||
org.thingsboard.client.model.Customer customer = new org.thingsboard.client.model.Customer(); |
|||
customer.setTitle("Java client test customer"); |
|||
customer.setTenantId(savedClientTenant.getId()); |
|||
savedClientCustomer = client.saveCustomer(customer, null, null, null); |
|||
|
|||
User customerUser = new User(); |
|||
customerUser.setAuthority(Authority.CUSTOMER_USER); |
|||
customerUser.setTenantId(savedClientTenant.getId()); |
|||
customerUser.setCustomerId(savedClientCustomer.getId()); |
|||
customerUser.setEmail(CUSTOMER_USERNAME); |
|||
savedClientCustomerUser = client.saveUser(customerUser, "false"); |
|||
activateUser(savedClientCustomerUser.getId(), "password123", false); |
|||
} |
|||
|
|||
@After |
|||
public void tearDownJavaClient() { |
|||
client.login("sysadmin@thingsboard.org", "sysadmin"); |
|||
client.deleteTenant(savedClientTenant.getId().getId().toString()); |
|||
} |
|||
|
|||
protected String getBaseUrl() { |
|||
return "http://localhost:" + wsPort; |
|||
} |
|||
|
|||
protected void activateUserAndAuthorize(User user) throws ApiException { |
|||
JwtPair jwtPair = activateUser(user.getId(), TEST_PASSWORD, false); |
|||
client.setToken(jwtPair.getToken()); |
|||
} |
|||
|
|||
protected JwtPair activateUser(UserId userId, String password, boolean sendActivationMail) throws ApiException { |
|||
ActivateUserRequest activateRequest = new ActivateUserRequest(); |
|||
activateRequest.setActivateToken(getActivateToken(userId)); |
|||
activateRequest.setPassword(password); |
|||
return client.activateUser(activateRequest, sendActivationMail); |
|||
} |
|||
|
|||
protected String getActivateToken(UserId userId) throws ApiException { |
|||
String activateTokenRegex = "/api/noauth/activate?activateToken="; |
|||
String activationLink = client.getActivationLink(userId.getId().toString()); |
|||
return activationLink.substring(activationLink.lastIndexOf(activateTokenRegex) + activateTokenRegex.length()); |
|||
} |
|||
|
|||
protected void assertReturns404(ThrowingRunnable operation) { |
|||
try { |
|||
operation.run(); |
|||
fail("Expected ApiException with 404 status code"); |
|||
} catch (ApiException exception) { |
|||
assertEquals("Expected 404 status code but got " + exception.getCode(), |
|||
404, exception.getCode()); |
|||
} catch (Exception e) { |
|||
fail("Expected ApiException but got " + e.getClass().getName() + ": " + e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,99 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.AdminSettings; |
|||
import org.thingsboard.client.model.FeaturesInfo; |
|||
import org.thingsboard.client.model.JwtSettings; |
|||
import org.thingsboard.client.model.SecuritySettings; |
|||
import org.thingsboard.client.model.SystemInfo; |
|||
import org.thingsboard.client.model.UpdateMessage; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertFalse; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class AdminApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testAdminSettingsLifecycle() throws Exception { |
|||
// authenticate as sysadmin for admin settings management
|
|||
client.login("sysadmin@thingsboard.org", "sysadmin"); |
|||
|
|||
// get mail settings
|
|||
AdminSettings mailSettings = client.getAdminSettings("mail"); |
|||
assertNotNull(mailSettings); |
|||
assertNotNull(mailSettings.getKey()); |
|||
assertEquals("mail", mailSettings.getKey()); |
|||
assertNotNull(mailSettings.getJsonValue()); |
|||
|
|||
// get general settings
|
|||
AdminSettings generalSettings = client.getAdminSettings("general"); |
|||
assertNotNull(generalSettings); |
|||
assertEquals("general", generalSettings.getKey()); |
|||
assertNotNull(generalSettings.getJsonValue()); |
|||
assertNotNull(generalSettings.getJsonValue().get("baseUrl").asText()); |
|||
|
|||
// update general settings and restore
|
|||
((ObjectNode) generalSettings.getJsonValue()).put("prohibitDifferentUrl", true); |
|||
AdminSettings updatedGeneralSettings = client.saveAdminSettings(generalSettings); |
|||
assertTrue(updatedGeneralSettings.getJsonValue().get("prohibitDifferentUrl").asBoolean()); |
|||
|
|||
// get security settings
|
|||
SecuritySettings securitySettings = client.getSecuritySettings(); |
|||
assertNotNull(securitySettings); |
|||
assertNotNull(securitySettings.getPasswordPolicy()); |
|||
Integer originalMaxAttempts = securitySettings.getMaxFailedLoginAttempts(); |
|||
|
|||
// update security settings
|
|||
securitySettings.setMaxFailedLoginAttempts(10); |
|||
SecuritySettings updatedSecurity = client.saveSecuritySettings(securitySettings); |
|||
assertNotNull(updatedSecurity); |
|||
assertEquals(10, updatedSecurity.getMaxFailedLoginAttempts().intValue()); |
|||
|
|||
// restore original security settings
|
|||
updatedSecurity.setMaxFailedLoginAttempts(originalMaxAttempts); |
|||
client.saveSecuritySettings(updatedSecurity); |
|||
|
|||
// get JWT settings
|
|||
JwtSettings jwtSettings = client.getJwtSettings(); |
|||
assertNotNull(jwtSettings); |
|||
assertNotNull(jwtSettings.getTokenExpirationTime()); |
|||
assertNotNull(jwtSettings.getRefreshTokenExpTime()); |
|||
assertEquals("thingsboard.io", jwtSettings.getTokenIssuer()); |
|||
assertNotNull(jwtSettings.getTokenSigningKey()); |
|||
|
|||
// get system info
|
|||
SystemInfo systemInfo = client.getSystemInfo(); |
|||
assertNotNull(systemInfo); |
|||
|
|||
// get features info
|
|||
FeaturesInfo featuresInfo = client.getFeaturesInfo(); |
|||
assertNotNull(featuresInfo); |
|||
assertFalse(featuresInfo.getSmsEnabled()); |
|||
assertFalse(featuresInfo.getOauthEnabled()); |
|||
|
|||
// check updates
|
|||
UpdateMessage updateMessage = client.checkUpdates(); |
|||
assertNotNull(updateMessage); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,163 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.Alarm; |
|||
import org.thingsboard.client.model.AlarmInfo; |
|||
import org.thingsboard.client.model.AlarmSeverity; |
|||
import org.thingsboard.client.model.AlarmStatus; |
|||
import org.thingsboard.client.model.Device; |
|||
import org.thingsboard.client.model.EntitySubtype; |
|||
import org.thingsboard.client.model.EntityType; |
|||
import org.thingsboard.client.model.PageDataAlarmInfo; |
|||
import org.thingsboard.client.model.PageDataEntitySubtype; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class AlarmApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testAlarmLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<Alarm> createdAlarms = new ArrayList<>(); |
|||
|
|||
// First, create devices to attach alarms to
|
|||
Device device1 = new Device(); |
|||
device1.setName("Device_For_Alarm_" + timestamp + "_1"); |
|||
device1.setType("default"); |
|||
Device createdDevice1 = client.saveDevice(device1, null, null, null, null); |
|||
|
|||
Device device2 = new Device(); |
|||
device2.setName("Device_For_Alarm_" + timestamp + "_2"); |
|||
device2.setType("thermostat"); |
|||
Device createdDevice2 = client.saveDevice(device2, null, null, null, null); |
|||
|
|||
// Create 2 alarms (1 for each device)
|
|||
for (int i = 0; i < 2; i++) { |
|||
Alarm alarm = new Alarm(); |
|||
alarm.setType(((i % 2 == 0) ? "Temperature Alarm" : "Connection Alarm")); |
|||
alarm.setSeverity(((i % 2 == 0) ? AlarmSeverity.CRITICAL : AlarmSeverity.WARNING)); |
|||
alarm.setOriginator((i % 2 == 0) ? createdDevice1.getId() : createdDevice2.getId()); |
|||
|
|||
Alarm createdAlarm = client.saveAlarm(alarm); |
|||
assertNotNull(createdAlarm); |
|||
assertNotNull(createdAlarm.getId()); |
|||
assertEquals(alarm.getType(), createdAlarm.getType()); |
|||
assertEquals(alarm.getSeverity(), createdAlarm.getSeverity()); |
|||
|
|||
createdAlarms.add(createdAlarm); |
|||
} |
|||
|
|||
// Get all alarms
|
|||
PageDataAlarmInfo allAlarms = client.getAllAlarms(100, 0, null, null, null, null, null, null, null, null, null); |
|||
|
|||
assertNotNull(allAlarms); |
|||
assertNotNull(allAlarms.getData()); |
|||
int initialSize = allAlarms.getData().size(); |
|||
assertEquals("Expected at least 2 alarms, but got " + initialSize, 2, initialSize); |
|||
|
|||
// Get alarms by entity (device1)
|
|||
PageDataAlarmInfo device1Alarms = client.getAlarmsV2(EntityType.DEVICE.toString(), createdDevice1.getId().getId().toString(), 100, 0, null, null, null, null, null, null, null, null, null); |
|||
assertNotNull(device1Alarms); |
|||
assertEquals("Expected 1 alarms for device1", 1, device1Alarms.getData().size()); |
|||
|
|||
// Get alarm by id
|
|||
Alarm searchAlarm = createdAlarms.get(0); |
|||
Alarm fetchedAlarm = client.getAlarmById(searchAlarm.getId().getId().toString()); |
|||
assertEquals(searchAlarm.getType(), fetchedAlarm.getType()); |
|||
assertEquals(searchAlarm.getSeverity(), fetchedAlarm.getSeverity()); |
|||
|
|||
// Get alarm info
|
|||
AlarmInfo alarmInfo = client.getAlarmInfoById(searchAlarm.getId().getId().toString()); |
|||
assertNotNull(alarmInfo); |
|||
assertEquals(searchAlarm.getId().getId(), alarmInfo.getId().getId()); |
|||
|
|||
// Acknowledge alarm
|
|||
client.ackAlarm(searchAlarm.getId().getId().toString()); |
|||
|
|||
// Verify alarm is acknowledged
|
|||
Alarm ackedAlarm = client.getAlarmById(searchAlarm.getId().getId().toString()); |
|||
assertEquals(AlarmStatus.ACTIVE_ACK, ackedAlarm.getStatus()); |
|||
|
|||
// Clear alarm
|
|||
client.clearAlarm(searchAlarm.getId().getId().toString()); |
|||
|
|||
// Verify alarm is cleared
|
|||
Alarm clearedAlarm = client.getAlarmById(searchAlarm.getId().getId().toString()); |
|||
assertEquals(AlarmStatus.CLEARED_ACK, clearedAlarm.getStatus()); |
|||
|
|||
// Get highest severity alarm for device
|
|||
AlarmSeverity highestSeverity = client.getHighestAlarmSeverity(EntityType.DEVICE.toString(), createdDevice1.getId().getId().toString(), null, null, null); |
|||
assertNotNull(highestSeverity); |
|||
assertEquals(AlarmSeverity.CRITICAL, highestSeverity); |
|||
|
|||
// Assign alarm to customer
|
|||
client.assignAlarm(createdAlarms.get(0).getId().getId().toString(), clientTenantAdmin.getId().getId().toString()); |
|||
|
|||
// Verify assignment
|
|||
Alarm assignedAlarm = client.getAlarmById(createdAlarms.get(0).getId().getId().toString()); |
|||
assertEquals(clientTenantAdmin.getId().getId(), assignedAlarm.getAssigneeId().getId()); |
|||
|
|||
// Unassign alarm
|
|||
client.unassignAlarm(createdAlarms.get(0).getId().getId().toString()); |
|||
|
|||
// Verify unassignment
|
|||
Alarm unassignedAlarm = client.getAlarmById(createdAlarms.get(0).getId().getId().toString()); |
|||
assertNull(unassignedAlarm.getAssigneeId()); |
|||
|
|||
// Get alarm types
|
|||
PageDataEntitySubtype pageDataEntitySubtype = client.getAlarmTypes(100, 0, null, null); |
|||
assertEquals(2, pageDataEntitySubtype.getData().size()); |
|||
List<String> alarmTypes = pageDataEntitySubtype.getData().stream() |
|||
.map(EntitySubtype::getType) |
|||
.collect(Collectors.toList()); |
|||
assertTrue(alarmTypes.containsAll(List.of("Temperature Alarm", "Connection Alarm"))); |
|||
|
|||
// Get alarms V2 (alternative endpoint)
|
|||
PageDataAlarmInfo alarmsV2 = client.getAlarmsV2(EntityType.DEVICE.toString(), createdDevice1.getId().getId().toString(), 100, 0, null, null, null, null, null, null, null, null, null); |
|||
assertNotNull(alarmsV2); |
|||
assertEquals(1, alarmsV2.getData().size()); |
|||
|
|||
// Get all alarms V2
|
|||
PageDataAlarmInfo allAlarmsV2 = client.getAllAlarmsV2(100, 0, null, null, null, null, null, null, null, null, null); |
|||
assertEquals(2, allAlarmsV2.getData().size()); |
|||
|
|||
// Delete alarm
|
|||
UUID alarmToDeleteId = createdAlarms.get(0).getId().getId(); |
|||
client.deleteAlarm(alarmToDeleteId.toString()); |
|||
|
|||
// Verify the alarm is deleted (should return 404)
|
|||
assertReturns404(() -> |
|||
client.getAlarmById(alarmToDeleteId.toString()) |
|||
); |
|||
|
|||
// Verify count after deletion
|
|||
PageDataAlarmInfo alarmsAfterDelete = client.getAllAlarms(100, 0, null, null, null, null, null, null, null, null, null); |
|||
assertEquals(initialSize - 1, alarmsAfterDelete.getData().size()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,106 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.Alarm; |
|||
import org.thingsboard.client.model.AlarmComment; |
|||
import org.thingsboard.client.model.AlarmCommentInfo; |
|||
import org.thingsboard.client.model.AlarmSeverity; |
|||
import org.thingsboard.client.model.Device; |
|||
import org.thingsboard.client.model.PageDataAlarmCommentInfo; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
|
|||
@DaoSqlTest |
|||
public class AlarmCommentApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testAlarmComments() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
|
|||
// Create device for alarm
|
|||
Device device = new Device(); |
|||
device.setName("Device_For_Comments_" + timestamp); |
|||
device.setType("default"); |
|||
Device createdDevice = client.saveDevice(device, null, null, null, null); |
|||
|
|||
// Create alarm
|
|||
Alarm alarm = new Alarm(); |
|||
alarm.setType("Temperature Alarm"); |
|||
alarm.setSeverity(AlarmSeverity.CRITICAL); |
|||
alarm.setOriginator(createdDevice.getId()); |
|||
|
|||
Alarm createdAlarm = client.saveAlarm(alarm); |
|||
String alarmId = createdAlarm.getId().getId().toString(); |
|||
|
|||
List<AlarmComment> createdComments = new ArrayList<>(); |
|||
|
|||
// Create multiple comments
|
|||
for (int i = 0; i < 5; i++) { |
|||
AlarmComment alarmComment = new AlarmComment(); |
|||
String message = "Test comment #" + i + " at " + timestamp; |
|||
ObjectNode comment = OBJECT_MAPPER.createObjectNode().put("message", message); |
|||
alarmComment.setComment(comment); |
|||
|
|||
AlarmComment commentInfo = client.saveAlarmComment(alarmId, alarmComment); |
|||
|
|||
assertNotNull(commentInfo); |
|||
assertNotNull(commentInfo.getId()); |
|||
JsonNode commentValue = commentInfo.getComment(); |
|||
assertEquals(message, commentValue.get("message").asText()); |
|||
assertNotNull(commentInfo.getCreatedTime()); |
|||
|
|||
createdComments.add(commentInfo); |
|||
} |
|||
|
|||
// Get all comments for the alarm
|
|||
PageDataAlarmCommentInfo allComments = client.getAlarmComments(alarmId, 100, 0, null, null); |
|||
assertEquals("Expected 5 comments", 5, allComments.getData().size()); |
|||
|
|||
// Update a comment
|
|||
AlarmComment commentToUpdate = createdComments.get(2); |
|||
JsonNode comment = commentToUpdate.getComment(); |
|||
((ObjectNode) comment).put("message", "New comment"); |
|||
commentToUpdate.setComment(comment); |
|||
|
|||
AlarmComment updatedComment = client.saveAlarmComment(alarmId, commentToUpdate); |
|||
assertEquals("New comment", updatedComment.getComment().get("message").asText()); |
|||
|
|||
// Delete a comment
|
|||
UUID commentToDeleteId = createdComments.get(0).getId().getId(); |
|||
|
|||
client.deleteAlarmComment(alarmId, commentToDeleteId.toString()); |
|||
|
|||
// Verify comment was updated to "deleted"
|
|||
PageDataAlarmCommentInfo commentsAfterDelete = client.getAlarmComments(alarmId, 100, 0, null, null); |
|||
List<AlarmCommentInfo> data = commentsAfterDelete.getData(); |
|||
AlarmCommentInfo deletedComment = data.stream() |
|||
.filter(alarmCommentInfo -> alarmCommentInfo.getId().getId().equals(commentToDeleteId)) |
|||
.findFirst() |
|||
.get(); |
|||
assertEquals("User " + clientTenantAdmin.getEmail() + " deleted his comment", deletedComment.getComment().get("text").asText()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.ApiKey; |
|||
import org.thingsboard.client.model.ApiKeyInfo; |
|||
import org.thingsboard.client.model.PageDataApiKeyInfo; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertFalse; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class ApiKeyApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testApiKeyLifecycle() throws Exception { |
|||
String userId = clientTenantAdmin.getId().getId().toString(); |
|||
|
|||
ApiKeyInfo request = new ApiKeyInfo(); |
|||
request.setDescription("Test API key"); |
|||
request.setUserId(clientTenantAdmin.getId()); |
|||
request.setEnabled(true); |
|||
ApiKey created = client.saveApiKey(request); |
|||
|
|||
assertNotNull(created); |
|||
assertNotNull(created.getId()); |
|||
assertNotNull(created.getValue()); |
|||
assertFalse(created.getValue().isBlank()); |
|||
assertEquals("Test API key", created.getDescription()); |
|||
|
|||
UUID keyId = created.getId().getId(); |
|||
|
|||
PageDataApiKeyInfo keysPage = client.getUserApiKeys(userId, 100, 0, null, null, null); |
|||
assertNotNull(keysPage); |
|||
assertNotNull(keysPage.getData()); |
|||
assertTrue("Newly created API key should appear in user's key list", |
|||
keysPage.getData().stream() |
|||
.anyMatch(k -> k.getId().getId().equals(keyId))); |
|||
|
|||
client.deleteApiKey(keyId); |
|||
|
|||
PageDataApiKeyInfo keysAfterDelete = client.getUserApiKeys(userId, 100, 0, null, null, null); |
|||
assertTrue("Deleted API key should not appear in user's key list", |
|||
keysAfterDelete.getData().stream() |
|||
.noneMatch(k -> k.getId().getId().equals(keyId))); |
|||
} |
|||
|
|||
@Test |
|||
public void testEnableDisableApiKey() throws Exception { |
|||
ApiKeyInfo request = new ApiKeyInfo(); |
|||
request.setDescription("Enable/disable test key"); |
|||
request.setUserId(clientTenantAdmin.getId()); |
|||
request.setEnabled(true); |
|||
ApiKey created = client.saveApiKey(request); |
|||
assertNotNull(created); |
|||
|
|||
UUID keyId = created.getId().getId(); |
|||
|
|||
ApiKeyInfo disabled = client.enableApiKey(keyId, false); |
|||
assertNotNull(disabled); |
|||
assertEquals(Boolean.FALSE, disabled.getEnabled()); |
|||
|
|||
ApiKeyInfo enabled = client.enableApiKey(keyId, true); |
|||
assertNotNull(enabled); |
|||
assertEquals(Boolean.TRUE, enabled.getEnabled()); |
|||
|
|||
client.deleteApiKey(keyId); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetUserApiKeys() throws Exception { |
|||
String userId = clientTenantAdmin.getId().getId().toString(); |
|||
|
|||
int initialCount = client.getUserApiKeys(userId, 100, 0, null, null, null) |
|||
.getData().size(); |
|||
|
|||
UUID[] createdIds = new UUID[3]; |
|||
for (int i = 0; i < 3; i++) { |
|||
ApiKeyInfo request = new ApiKeyInfo(); |
|||
request.setDescription("Paging test key " + i); |
|||
request.setUserId(clientTenantAdmin.getId()); |
|||
createdIds[i] = client.saveApiKey(request).getId().getId(); |
|||
} |
|||
|
|||
PageDataApiKeyInfo afterCreate = client.getUserApiKeys(userId, 100, 0, null, null, null); |
|||
assertEquals(initialCount + 3, afterCreate.getData().size()); |
|||
assertEquals(Long.valueOf(initialCount + 3), afterCreate.getTotalElements()); |
|||
|
|||
PageDataApiKeyInfo page1 = client.getUserApiKeys(userId, 2, 0, null, null, null); |
|||
assertEquals(2, page1.getData().size()); |
|||
assertTrue(page1.getHasNext()); |
|||
|
|||
for (UUID id : createdIds) { |
|||
client.deleteApiKey(id); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.Asset; |
|||
import org.thingsboard.client.model.PageDataAsset; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
|
|||
@DaoSqlTest |
|||
public class AssetApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testAssetLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<Asset> createdAssets = new ArrayList<>(); |
|||
|
|||
// create 20 assets
|
|||
for (int i = 0; i < 20; i++) { |
|||
Asset asset = new Asset(); |
|||
String assetName = ((i % 2 == 0) ? TEST_PREFIX : TEST_PREFIX_2) + timestamp + "_" + i; |
|||
asset.setName(assetName); |
|||
asset.setLabel("Test Asset " + i); |
|||
asset.setType(((i % 2 == 0) ? "default" : "building")); |
|||
|
|||
Asset createdAsset = client.saveAsset(asset, null, null, null); |
|||
assertNotNull(createdAsset); |
|||
assertNotNull(createdAsset.getId()); |
|||
assertEquals(assetName, createdAsset.getName()); |
|||
|
|||
createdAssets.add(createdAsset); |
|||
} |
|||
|
|||
// find all, check count
|
|||
PageDataAsset allAssets = client.getTenantAssets(100, 0, null, null, null, null); |
|||
|
|||
assertNotNull(allAssets); |
|||
assertNotNull(allAssets.getData()); |
|||
int initialSize = allAssets.getData().size(); |
|||
assertEquals("Expected at least 20 assets, but got " + allAssets.getData().size(), 20, initialSize); |
|||
|
|||
//find all with search text, check count
|
|||
PageDataAsset allAssetsBySearchText = client.getTenantAssets(100, 0, null, TEST_PREFIX_2, null, null); |
|||
assertEquals("Expected exactly 10 test assets", 10, allAssetsBySearchText.getData().size()); |
|||
|
|||
// find by id
|
|||
Asset searchAsset = createdAssets.get(10); |
|||
Asset asset = client.getAssetById(searchAsset.getId().getId().toString()); |
|||
assertEquals(searchAsset.getName(), asset.getName()); |
|||
|
|||
// delete asset
|
|||
UUID assetToDeleteId = createdAssets.get(0).getId().getId(); |
|||
client.deleteAsset(assetToDeleteId.toString()); |
|||
|
|||
// Verify the asset is deleted
|
|||
PageDataAsset assetsAfterDelete = client.getTenantAssets(100, 0, null, null, null, null); |
|||
assertEquals(initialSize - 1, assetsAfterDelete.getData().size()); |
|||
|
|||
assertReturns404(() -> |
|||
client.getAssetById(assetToDeleteId.toString()) |
|||
); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,135 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.AssetProfile; |
|||
import org.thingsboard.client.model.AssetProfileInfo; |
|||
import org.thingsboard.client.model.EntityInfo; |
|||
import org.thingsboard.client.model.PageDataAssetProfile; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertFalse; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class AssetProfileApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testAssetProfileLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<AssetProfile> createdProfiles = new ArrayList<>(); |
|||
|
|||
// Get initial count (there should be a default profile)
|
|||
PageDataAssetProfile initialProfiles = client.getAssetProfiles(100, 0, null, null, null); |
|||
assertNotNull(initialProfiles); |
|||
int initialSize = initialProfiles.getData().size(); |
|||
assertTrue("Expected at least 1 default asset profile", initialSize == 1); |
|||
|
|||
// Get default asset profile info
|
|||
AssetProfileInfo defaultProfileInfo = client.getDefaultAssetProfileInfo(); |
|||
assertNotNull(defaultProfileInfo); |
|||
assertEquals(defaultProfileInfo.getName(), "default"); |
|||
|
|||
// Create multiple asset profiles
|
|||
for (int i = 0; i < 5; i++) { |
|||
AssetProfile profile = new AssetProfile(); |
|||
profile.setName("Test Asset Profile " + timestamp + "_" + i); |
|||
profile.setDescription("Test description " + i); |
|||
|
|||
AssetProfile created = client.saveAssetProfile(profile); |
|||
assertNotNull(created); |
|||
assertNotNull(created.getId()); |
|||
assertEquals(profile.getName(), created.getName()); |
|||
assertEquals(profile.getDescription(), created.getDescription()); |
|||
assertFalse(created.getDefault()); |
|||
|
|||
createdProfiles.add(created); |
|||
} |
|||
|
|||
// Find all, check count
|
|||
PageDataAssetProfile allProfiles = client.getAssetProfiles(100, 0, null, null, null); |
|||
assertNotNull(allProfiles); |
|||
assertEquals(initialSize + 5, allProfiles.getData().size()); |
|||
|
|||
// Find all with text search
|
|||
PageDataAssetProfile filteredProfiles = client.getAssetProfiles(100, 0, "Test Asset Profile " + timestamp, null, null); |
|||
assertEquals(5, filteredProfiles.getData().size()); |
|||
|
|||
// Get by id
|
|||
AssetProfile searchProfile = createdProfiles.get(2); |
|||
AssetProfile fetchedProfile = client.getAssetProfileById(searchProfile.getId().getId().toString(), false); |
|||
assertEquals(searchProfile.getName(), fetchedProfile.getName()); |
|||
assertEquals(searchProfile.getDescription(), fetchedProfile.getDescription()); |
|||
|
|||
// Update asset profile
|
|||
fetchedProfile.setDescription("Updated description"); |
|||
AssetProfile updatedProfile = client.saveAssetProfile(fetchedProfile); |
|||
assertEquals("Updated description", updatedProfile.getDescription()); |
|||
assertEquals(fetchedProfile.getName(), updatedProfile.getName()); |
|||
|
|||
// Get asset profile info by id
|
|||
AssetProfileInfo profileInfo = client.getAssetProfileInfoById(searchProfile.getId().getId().toString()); |
|||
assertNotNull(profileInfo); |
|||
assertEquals(searchProfile.getName(), profileInfo.getName()); |
|||
|
|||
// Get asset profile infos (paginated)
|
|||
PageDataAssetProfile profileInfos = client.getAssetProfiles(100, 0, null, null, null); |
|||
assertNotNull(profileInfos); |
|||
assertEquals(initialSize + 5, profileInfos.getData().size()); |
|||
|
|||
// Set a profile as default
|
|||
AssetProfile profileToSetDefault = createdProfiles.get(1); |
|||
AssetProfile newDefault = client.setDefaultAssetProfile(profileToSetDefault.getId().getId().toString()); |
|||
assertNotNull(newDefault); |
|||
assertTrue(newDefault.getDefault()); |
|||
|
|||
// Verify default profile info now points to the new default
|
|||
AssetProfileInfo newDefaultInfo = client.getDefaultAssetProfileInfo(); |
|||
assertEquals(profileToSetDefault.getName(), newDefaultInfo.getName()); |
|||
|
|||
// Get asset profile names
|
|||
List<EntityInfo> profileNames = client.getAssetProfileNames(false); |
|||
assertNotNull(profileNames); |
|||
assertEquals(createdProfiles.size() + 1, profileNames.size()); |
|||
|
|||
// Delete asset profile (cannot delete the default one, so delete a non-default one)
|
|||
UUID profileToDeleteId = createdProfiles.get(0).getId().getId(); |
|||
client.deleteAssetProfile(profileToDeleteId.toString()); |
|||
|
|||
// Verify the profile is deleted
|
|||
assertReturns404(() -> |
|||
client.getAssetProfileById(profileToDeleteId.toString(), false)); |
|||
|
|||
// Verify count after deletion
|
|||
PageDataAssetProfile profilesAfterDelete = client.getAssetProfiles(100, 0, null, null, null); |
|||
assertEquals(initialSize + 4, profilesAfterDelete.getData().size()); |
|||
|
|||
// Restore original default profile
|
|||
AssetProfile originalDefault = initialProfiles.getData().stream() |
|||
.filter(AssetProfile::getDefault) |
|||
.findFirst() |
|||
.orElseThrow(); |
|||
client.setDefaultAssetProfile(originalDefault.getId().getId().toString()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,286 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.AlarmCalculatedFieldConfiguration; |
|||
import org.thingsboard.client.model.AlarmConditionValueAlarmSchedule; |
|||
import org.thingsboard.client.model.AlarmRule; |
|||
import org.thingsboard.client.model.AlarmSeverity; |
|||
import org.thingsboard.client.model.Argument; |
|||
import org.thingsboard.client.model.ArgumentType; |
|||
import org.thingsboard.client.model.CalculatedField; |
|||
import org.thingsboard.client.model.CalculatedFieldType; |
|||
import org.thingsboard.client.model.Device; |
|||
import org.thingsboard.client.model.EntityType; |
|||
import org.thingsboard.client.model.PageDataCalculatedField; |
|||
import org.thingsboard.client.model.ReferencedEntityKey; |
|||
import org.thingsboard.client.model.SimpleAlarmCondition; |
|||
import org.thingsboard.client.model.SimpleCalculatedFieldConfiguration; |
|||
import org.thingsboard.client.model.SpecificTimeSchedule; |
|||
import org.thingsboard.client.model.TbelAlarmConditionExpression; |
|||
import org.thingsboard.client.model.TimeSeriesOutput; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class CalculatedFieldApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testCalculatedFieldLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<CalculatedField> createdFields = new ArrayList<>(); |
|||
|
|||
// create devices to attach calculated fields to
|
|||
Device device1 = new Device(); |
|||
device1.setName("CalcFieldDevice1_" + timestamp); |
|||
device1.setType("default"); |
|||
Device createdDevice1 = client.saveDevice(device1, null, null, null, null); |
|||
|
|||
Device device2 = new Device(); |
|||
device2.setName("CalcFieldDevice2_" + timestamp); |
|||
device2.setType("default"); |
|||
Device createdDevice2 = client.saveDevice(device2, null, null, null, null); |
|||
|
|||
// create calculated fields on device1
|
|||
for (int i = 0; i < 5; i++) { |
|||
CalculatedField cf = new CalculatedField(); |
|||
cf.setName(TEST_PREFIX + "CalcField_" + timestamp + "_" + i); |
|||
cf.setType(CalculatedFieldType.SIMPLE); |
|||
|
|||
cf.setEntityId(createdDevice1.getId()); |
|||
|
|||
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration(); |
|||
|
|||
Argument arg = new Argument(); |
|||
ReferencedEntityKey refKey = new ReferencedEntityKey(); |
|||
refKey.setKey("temperature"); |
|||
refKey.setType(ArgumentType.TS_LATEST); |
|||
arg.setRefEntityKey(refKey); |
|||
config.putArgumentsItem("temp", arg); |
|||
|
|||
config.setExpression("temp * " + (i + 1)); |
|||
|
|||
TimeSeriesOutput output = new TimeSeriesOutput(); |
|||
output.setName("scaledTemp_" + i); |
|||
config.setOutput(output); |
|||
|
|||
cf.setConfiguration(config); |
|||
|
|||
CalculatedField created = client.saveCalculatedField(cf); |
|||
assertNotNull(created); |
|||
assertNotNull(created.getId()); |
|||
assertEquals(cf.getName(), created.getName()); |
|||
assertEquals(CalculatedFieldType.SIMPLE, created.getType()); |
|||
|
|||
createdFields.add(created); |
|||
} |
|||
|
|||
// create calculated fields on device2
|
|||
for (int i = 0; i < 3; i++) { |
|||
CalculatedField cf = new CalculatedField(); |
|||
cf.setName(TEST_PREFIX + "CalcField2_" + timestamp + "_" + i); |
|||
cf.setType(CalculatedFieldType.SIMPLE); |
|||
cf.setEntityId(createdDevice2.getId()); |
|||
|
|||
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration(); |
|||
|
|||
Argument arg = new Argument(); |
|||
ReferencedEntityKey refKey = new ReferencedEntityKey(); |
|||
refKey.setKey("humidity"); |
|||
refKey.setType(ArgumentType.TS_LATEST); |
|||
arg.setRefEntityKey(refKey); |
|||
config.putArgumentsItem("hum", arg); |
|||
|
|||
config.setExpression("hum + " + i); |
|||
|
|||
TimeSeriesOutput output = new TimeSeriesOutput(); |
|||
output.setName("adjustedHumidity_" + i); |
|||
config.setOutput(output); |
|||
|
|||
cf.setConfiguration(config); |
|||
|
|||
CalculatedField created = client.saveCalculatedField(cf); |
|||
assertNotNull(created); |
|||
createdFields.add(created); |
|||
} |
|||
|
|||
// get calculated fields by entity id for device1
|
|||
PageDataCalculatedField device1Fields = client.getCalculatedFieldsByEntityId( |
|||
EntityType.DEVICE.toString(), createdDevice1.getId().getId().toString(), |
|||
100, 0, CalculatedFieldType.SIMPLE, null, null, null); |
|||
assertNotNull(device1Fields); |
|||
assertEquals(5, device1Fields.getData().size()); |
|||
|
|||
// get calculated fields by entity id for device2
|
|||
PageDataCalculatedField device2Fields = client.getCalculatedFieldsByEntityId( |
|||
EntityType.DEVICE.toString(), createdDevice2.getId().getId().toString(), |
|||
100, 0, CalculatedFieldType.SIMPLE, null, null, null); |
|||
assertEquals(3, device2Fields.getData().size()); |
|||
|
|||
// get by id
|
|||
CalculatedField searchField = createdFields.get(2); |
|||
CalculatedField fetchedField = client.getCalculatedFieldById(searchField.getId().getId().toString()); |
|||
assertEquals(searchField.getName(), fetchedField.getName()); |
|||
assertEquals(searchField.getType(), fetchedField.getType()); |
|||
assertNotNull(fetchedField.getConfiguration()); |
|||
SimpleCalculatedFieldConfiguration fetchedConfig = |
|||
(SimpleCalculatedFieldConfiguration) fetchedField.getConfiguration(); |
|||
assertEquals("temp * 3", fetchedConfig.getExpression()); |
|||
|
|||
// update calculated field
|
|||
fetchedField.setName(fetchedField.getName() + "_updated"); |
|||
fetchedConfig.setExpression("temp * 100"); |
|||
CalculatedField updatedField = client.saveCalculatedField(fetchedField); |
|||
assertEquals(fetchedField.getName(), updatedField.getName()); |
|||
SimpleCalculatedFieldConfiguration updatedConfig = |
|||
(SimpleCalculatedFieldConfiguration) updatedField.getConfiguration(); |
|||
assertEquals("temp * 100", updatedConfig.getExpression()); |
|||
|
|||
// delete calculated field
|
|||
UUID fieldToDeleteId = createdFields.get(0).getId().getId(); |
|||
client.deleteCalculatedField(fieldToDeleteId.toString()); |
|||
|
|||
// verify deletion
|
|||
assertReturns404(() -> |
|||
client.getCalculatedFieldById(fieldToDeleteId.toString()) |
|||
); |
|||
|
|||
PageDataCalculatedField device1FieldsAfterDelete = client.getCalculatedFieldsByEntityId( |
|||
EntityType.DEVICE.toString(), createdDevice1.getId().getId().toString(), |
|||
100, 0, null, null, null, null); |
|||
assertEquals(4, device1FieldsAfterDelete.getData().size()); |
|||
} |
|||
|
|||
@Test |
|||
public void testAlarmCalculatedFieldLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
|
|||
// create a device to attach the alarm calculated field to
|
|||
Device device = new Device(); |
|||
device.setName("AlarmCalcFieldDevice_" + timestamp); |
|||
device.setType("default"); |
|||
Device createdDevice = client.saveDevice(device, null, null, null, null); |
|||
|
|||
// build the alarm calculated field configuration
|
|||
AlarmCalculatedFieldConfiguration config = new AlarmCalculatedFieldConfiguration(); |
|||
|
|||
// argument: temperature time-series
|
|||
Argument tempArg = new Argument(); |
|||
ReferencedEntityKey refKey = new ReferencedEntityKey(); |
|||
refKey.setKey("temperature"); |
|||
refKey.setType(ArgumentType.TS_LATEST); |
|||
tempArg.setRefEntityKey(refKey); |
|||
config.putArgumentsItem("temp", tempArg); |
|||
|
|||
// create rule: HIGH_TEMPERATURE when temp > 50 (TBEL expression)
|
|||
TbelAlarmConditionExpression createExpression = new TbelAlarmConditionExpression(); |
|||
createExpression.setExpression("return temp > 50;"); |
|||
SimpleAlarmCondition createCondition = new SimpleAlarmCondition(); |
|||
createCondition.setExpression(createExpression); |
|||
SpecificTimeSchedule specificTimeSchedule = new SpecificTimeSchedule().addDaysOfWeekItem(3); |
|||
AlarmConditionValueAlarmSchedule schedule = new AlarmConditionValueAlarmSchedule().staticValue(specificTimeSchedule); |
|||
createCondition.setSchedule(schedule); |
|||
AlarmRule createRule = new AlarmRule(); |
|||
createRule.setCondition(createCondition); |
|||
createRule.setAlarmDetails("Temperature is too high: ${temp}"); |
|||
config.setCreateRules(Map.of( |
|||
AlarmSeverity.CRITICAL.name(), createRule |
|||
)); |
|||
|
|||
// clear rule: when temp drops below 30
|
|||
TbelAlarmConditionExpression clearExpression = new TbelAlarmConditionExpression(); |
|||
clearExpression.setExpression("return temp < 30;"); |
|||
SimpleAlarmCondition clearCondition = new SimpleAlarmCondition(); |
|||
clearCondition.setExpression(clearExpression); |
|||
AlarmRule clearRule = new AlarmRule(); |
|||
clearRule.setCondition(clearCondition); |
|||
config.setClearRule(clearRule); |
|||
|
|||
config.setPropagate(true); |
|||
config.setPropagateToOwner(false); |
|||
|
|||
// create calculated field
|
|||
CalculatedField cf = new CalculatedField(); |
|||
cf.setName(TEST_PREFIX + "AlarmCalcField_" + timestamp); |
|||
cf.setType(CalculatedFieldType.ALARM); |
|||
|
|||
cf.setEntityId(createdDevice.getId()); |
|||
cf.setConfiguration(config); |
|||
|
|||
CalculatedField created = client.saveCalculatedField(cf); |
|||
assertNotNull(created); |
|||
assertNotNull(created.getId()); |
|||
assertEquals(cf.getName(), created.getName()); |
|||
assertEquals(CalculatedFieldType.ALARM, created.getType()); |
|||
AlarmCalculatedFieldConfiguration configuration = (AlarmCalculatedFieldConfiguration) created.getConfiguration(); |
|||
AlarmConditionValueAlarmSchedule createdSchedule = configuration.getCreateRules().get(AlarmSeverity.CRITICAL.name()).getCondition().getSchedule(); |
|||
SpecificTimeSchedule staticSchedule = (SpecificTimeSchedule) createdSchedule.getStaticValue(); |
|||
assertEquals(Set.of(3), staticSchedule.getDaysOfWeek()); |
|||
|
|||
// get by id and verify configuration
|
|||
CalculatedField fetched = client.getCalculatedFieldById(created.getId().getId().toString()); |
|||
assertNotNull(fetched); |
|||
assertEquals(created.getName(), fetched.getName()); |
|||
assertEquals(CalculatedFieldType.ALARM, fetched.getType()); |
|||
assertNotNull(fetched.getConfiguration()); |
|||
AlarmCalculatedFieldConfiguration fetchedConfig = |
|||
(AlarmCalculatedFieldConfiguration) fetched.getConfiguration(); |
|||
assertNotNull(fetchedConfig.getCreateRules()); |
|||
assertEquals(1, fetchedConfig.getCreateRules().size()); |
|||
assertTrue(fetchedConfig.getCreateRules().containsKey("CRITICAL")); |
|||
assertNotNull(fetchedConfig.getClearRule()); |
|||
assertEquals(Boolean.TRUE, fetchedConfig.getPropagate()); |
|||
|
|||
// update: add a second create rule for CRITICAL_TEMPERATURE
|
|||
TbelAlarmConditionExpression criticalExpression = new TbelAlarmConditionExpression(); |
|||
criticalExpression.setExpression("return temp > 80;"); |
|||
SimpleAlarmCondition criticalCondition = new SimpleAlarmCondition(); |
|||
criticalCondition.setExpression(criticalExpression); |
|||
AlarmRule criticalRule = new AlarmRule(); |
|||
criticalRule.setCondition(criticalCondition); |
|||
fetchedConfig.putCreateRulesItem(AlarmSeverity.INDETERMINATE.name(), criticalRule); |
|||
fetched.setConfiguration(fetchedConfig); |
|||
|
|||
CalculatedField updated = client.saveCalculatedField(fetched); |
|||
AlarmCalculatedFieldConfiguration updatedConfig = |
|||
(AlarmCalculatedFieldConfiguration) updated.getConfiguration(); |
|||
assertEquals(2, updatedConfig.getCreateRules().size()); |
|||
assertTrue(updatedConfig.getCreateRules().containsKey("INDETERMINATE")); |
|||
|
|||
// filter by entity and ALARM type
|
|||
PageDataCalculatedField deviceFields = client.getCalculatedFieldsByEntityId( |
|||
EntityType.DEVICE.toString(), createdDevice.getId().getId().toString(), |
|||
100, 0, CalculatedFieldType.ALARM, null, null, null); |
|||
assertNotNull(deviceFields); |
|||
assertEquals(1, deviceFields.getData().size()); |
|||
|
|||
// delete and verify
|
|||
UUID fieldId = created.getId().getId(); |
|||
client.deleteCalculatedField(fieldId.toString()); |
|||
assertReturns404(() -> client.getCalculatedFieldById(fieldId.toString())); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.Customer; |
|||
import org.thingsboard.client.model.Device; |
|||
import org.thingsboard.client.model.PageDataCustomer; |
|||
import org.thingsboard.client.model.PageDataDevice; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
|
|||
@DaoSqlTest |
|||
public class CustomerApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testCustomerLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<Customer> createdCustomers = new ArrayList<>(); |
|||
|
|||
// create 20 customers
|
|||
for (int i = 0; i < 20; i++) { |
|||
Customer customer = new Customer(); |
|||
String customerTitle = ((i % 2 == 0) ? TEST_PREFIX : TEST_PREFIX_2) + timestamp + "_" + i; |
|||
customer.setTitle(customerTitle); |
|||
customer.setEmail("customer_" + timestamp + "_" + i + "@test.com"); |
|||
|
|||
Customer createdCustomer = client.saveCustomer(customer, null, null, null); |
|||
assertNotNull(createdCustomer); |
|||
assertNotNull(createdCustomer.getId()); |
|||
assertEquals(customerTitle, createdCustomer.getTitle()); |
|||
|
|||
createdCustomers.add(createdCustomer); |
|||
} |
|||
|
|||
// find all, check count (includes savedClientCustomer from AbstractApiClientTest setup)
|
|||
PageDataCustomer allCustomers = client.getCustomers(100, 0, null, null, null); |
|||
assertNotNull(allCustomers); |
|||
assertNotNull(allCustomers.getData()); |
|||
int initialSize = allCustomers.getData().size(); |
|||
assertEquals("Expected 21 customers (20 created + 1 from setup), but got " + initialSize, 21, initialSize); |
|||
|
|||
// find all with search text, check count
|
|||
PageDataCustomer filteredCustomers = client.getCustomers(100, 0, TEST_PREFIX_2, null, null); |
|||
assertEquals("Expected exactly 10 customers matching prefix", 10, filteredCustomers.getData().size()); |
|||
|
|||
// find by id
|
|||
Customer searchCustomer = createdCustomers.get(10); |
|||
Customer fetchedCustomer = client.getCustomerById(searchCustomer.getId().getId().toString()); |
|||
assertEquals(searchCustomer.getTitle(), fetchedCustomer.getTitle()); |
|||
|
|||
// find by title
|
|||
Customer fetchedByTitle = client.getTenantCustomer(searchCustomer.getTitle()); |
|||
assertEquals(searchCustomer.getId().getId(), fetchedByTitle.getId().getId()); |
|||
|
|||
// update customer
|
|||
fetchedCustomer.setCity("New York"); |
|||
fetchedCustomer.setCountry("US"); |
|||
Customer updatedCustomer = client.saveCustomer(fetchedCustomer, null, null, null); |
|||
assertEquals("New York", updatedCustomer.getCity()); |
|||
assertEquals("US", updatedCustomer.getCountry()); |
|||
|
|||
// assign device to customer and verify
|
|||
Device device = new Device(); |
|||
device.setName("CustomerTestDevice_" + timestamp); |
|||
device.setType("default"); |
|||
Device createdDevice = client.saveDevice(device, null, null, null, null); |
|||
|
|||
String customerId = createdCustomers.get(0).getId().getId().toString(); |
|||
client.assignDeviceToCustomer(customerId, createdDevice.getId().getId().toString()); |
|||
|
|||
PageDataDevice customerDevices = client.getCustomerDevices(customerId, 100, 0, null, null, null, null); |
|||
assertEquals(1, customerDevices.getData().size()); |
|||
assertEquals(createdDevice.getName(), customerDevices.getData().get(0).getName()); |
|||
|
|||
// unassign device from customer
|
|||
client.unassignDeviceFromCustomer(createdDevice.getId().getId().toString()); |
|||
PageDataDevice devicesAfterUnassign = client.getCustomerDevices(customerId, 100, 0, null, null, null, null); |
|||
assertEquals(0, devicesAfterUnassign.getData().size()); |
|||
|
|||
// delete customer
|
|||
UUID customerToDeleteId = createdCustomers.get(0).getId().getId(); |
|||
client.deleteCustomer(customerToDeleteId.toString()); |
|||
|
|||
// verify deletion
|
|||
PageDataCustomer customersAfterDelete = client.getCustomers(100, 0, null, null, null); |
|||
assertEquals(initialSize - 1, customersAfterDelete.getData().size()); |
|||
|
|||
assertReturns404(() -> |
|||
client.getCustomerById(customerToDeleteId.toString()) |
|||
); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,123 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.Dashboard; |
|||
import org.thingsboard.client.model.DashboardInfo; |
|||
import org.thingsboard.client.model.PageDataDashboardInfo; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class DashboardApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testDashboardLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
|
|||
// create 20 dashboards
|
|||
for (int i = 0; i < 20; i++) { |
|||
Dashboard dashboard = new Dashboard(); |
|||
String dashboardTitle = ((i % 2 == 0) ? TEST_PREFIX : TEST_PREFIX_2) + timestamp + "_" + i; |
|||
dashboard.setTitle(dashboardTitle); |
|||
|
|||
client.saveDashboard(dashboard, null); |
|||
} |
|||
|
|||
// find all, check count
|
|||
PageDataDashboardInfo allDashboards = client.getTenantDashboards(100, 0, null, null, null, null); |
|||
assertNotNull(allDashboards); |
|||
assertNotNull(allDashboards.getData()); |
|||
int initialSize = allDashboards.getData().size(); |
|||
assertEquals("Expected 20 dashboards, but got " + initialSize, 20, initialSize); |
|||
|
|||
List<DashboardInfo> createdDashboards = allDashboards.getData(); |
|||
|
|||
// find all with search text, check count
|
|||
PageDataDashboardInfo filteredDashboards = client.getTenantDashboards(100, 0, null, TEST_PREFIX_2, null, null); |
|||
assertEquals("Expected exactly 10 dashboards matching prefix", 10, filteredDashboards.getData().size()); |
|||
|
|||
// find by id
|
|||
DashboardInfo searchDashboard = createdDashboards.get(10); |
|||
DashboardInfo fetchedDashboard = client.getDashboardInfoById(searchDashboard.getId().getId().toString()); |
|||
assertEquals(searchDashboard.getTitle(), fetchedDashboard.getTitle()); |
|||
|
|||
// update dashboard
|
|||
Dashboard dashboardToUpdate = new Dashboard(); |
|||
dashboardToUpdate.setId(fetchedDashboard.getId()); |
|||
dashboardToUpdate.setTitle(fetchedDashboard.getTitle() + "_updated"); |
|||
dashboardToUpdate.setVersion(fetchedDashboard.getVersion()); |
|||
client.saveDashboard(dashboardToUpdate, null); |
|||
|
|||
DashboardInfo updatedDashboard = client.getDashboardInfoById(fetchedDashboard.getId().getId().toString()); |
|||
assertEquals(fetchedDashboard.getTitle() + "_updated", updatedDashboard.getTitle()); |
|||
|
|||
// assign dashboard to customer and verify
|
|||
String customerId = savedClientCustomer.getId().getId().toString(); |
|||
String dashboardId = createdDashboards.get(0).getId().getId().toString(); |
|||
client.assignDashboardToCustomer(customerId, dashboardId); |
|||
|
|||
PageDataDashboardInfo customerDashboards = client.getCustomerDashboards(customerId, 100, 0, null, null, null, null); |
|||
assertEquals(1, customerDashboards.getData().size()); |
|||
assertEquals(createdDashboards.get(0).getTitle(), customerDashboards.getData().get(0).getTitle()); |
|||
|
|||
// unassign dashboard from customer
|
|||
client.unassignDashboardFromCustomer(customerId, dashboardId); |
|||
PageDataDashboardInfo dashboardsAfterUnassign = client.getCustomerDashboards(customerId, 100, 0, null, null, null, null); |
|||
assertEquals(0, dashboardsAfterUnassign.getData().size()); |
|||
|
|||
// make dashboard public and verify
|
|||
client.assignDashboardToPublicCustomer(dashboardId); |
|||
DashboardInfo publicDashboard = client.getDashboardInfoById(dashboardId); |
|||
assertNotNull(publicDashboard.getAssignedCustomers()); |
|||
assertTrue(publicDashboard.getAssignedCustomers().size() > 0); |
|||
|
|||
// remove public access
|
|||
client.unassignDashboardFromPublicCustomer(dashboardId); |
|||
|
|||
// delete dashboard
|
|||
UUID dashboardToDeleteId = createdDashboards.get(0).getId().getId(); |
|||
client.deleteDashboard(dashboardToDeleteId.toString()); |
|||
|
|||
// verify deletion
|
|||
PageDataDashboardInfo dashboardsAfterDelete = client.getTenantDashboards(100, 0, null, null, null, null); |
|||
assertEquals(initialSize - 1, dashboardsAfterDelete.getData().size()); |
|||
|
|||
assertReturns404(() -> |
|||
client.getDashboardInfoById(dashboardToDeleteId.toString()) |
|||
); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetServerTime() throws Exception { |
|||
Long serverTime = client.getServerTime(); |
|||
assertNotNull(serverTime); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetMaxDatapointsLimit() throws Exception { |
|||
Long maxDatapointsLimit = client.getMaxDatapointsLimit(); |
|||
assertNotNull(maxDatapointsLimit); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.Device; |
|||
import org.thingsboard.client.model.DeviceCredentials; |
|||
import org.thingsboard.client.model.DeviceCredentialsType; |
|||
import org.thingsboard.client.model.PageDataDevice; |
|||
import org.thingsboard.client.model.SaveDeviceWithCredentialsRequest; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
|
|||
@DaoSqlTest |
|||
public class DeviceApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testDeviceLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<Device> createdDevices = new ArrayList<>(); |
|||
|
|||
// create 20 devices
|
|||
for (int i = 0; i < 20; i++) { |
|||
Device device = new Device(); |
|||
String deviceName = ((i % 2 == 0) ? TEST_PREFIX : TEST_PREFIX_2) + timestamp + "_" + i; |
|||
device.setName(deviceName); |
|||
device.setLabel("Test Device " + i); |
|||
device.setType(((i % 2 == 0) ? "default" : "thermostat")); |
|||
|
|||
Device createdDevice = client.saveDevice(device, null, null, null, null); |
|||
assertNotNull(createdDevice); |
|||
assertNotNull(createdDevice.getId()); |
|||
assertEquals(deviceName, createdDevice.getName()); |
|||
|
|||
createdDevices.add(createdDevice); |
|||
} |
|||
|
|||
// find all, check count
|
|||
PageDataDevice allDevices = client.getTenantDevices(100, 0, null, null, null, null); |
|||
|
|||
assertNotNull(allDevices); |
|||
assertNotNull(allDevices.getData()); |
|||
int initialSize = allDevices.getData().size(); |
|||
assertEquals("Expected at least 20 devices, but got " + allDevices.getData().size(), 20, initialSize); |
|||
|
|||
//find all with search text, check count
|
|||
PageDataDevice allDevicesBySearchText = client.getTenantDevices(10, 0, null, TEST_PREFIX_2, null, null); |
|||
assertEquals("Expected exactly 10 test devices", 10, allDevicesBySearchText.getData().size()); |
|||
|
|||
// find by id
|
|||
Device searchDevice = createdDevices.get(10); |
|||
Device device = client.getDeviceById(searchDevice.getId().getId().toString()); |
|||
assertEquals(searchDevice.getName(), device.getName()); |
|||
|
|||
// create device with credentials
|
|||
Device deviceWithCreds = new Device(); |
|||
deviceWithCreds.setName("device-with-creds"); |
|||
|
|||
DeviceCredentials creds = new DeviceCredentials(); |
|||
creds.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); |
|||
creds.setCredentialsId("TEST_ACCESS_TOKEN"); |
|||
|
|||
SaveDeviceWithCredentialsRequest request = new SaveDeviceWithCredentialsRequest(); |
|||
request.setDevice(deviceWithCreds); |
|||
request.setCredentials(creds); |
|||
|
|||
Device savedDeviceWithCreds = client.saveDeviceWithCredentials(request, null, null, null); |
|||
assertEquals("device-with-creds", savedDeviceWithCreds.getName()); |
|||
|
|||
// find credentials by device id
|
|||
DeviceCredentials fetchedCreds = client.getDeviceCredentialsByDeviceId(savedDeviceWithCreds.getId().getId().toString()); |
|||
assertEquals(creds.getCredentialsId(), fetchedCreds.getCredentialsId()); |
|||
|
|||
// delete device
|
|||
UUID deviceToDeleteId = createdDevices.get(0).getId().getId(); |
|||
client.deleteDevice(deviceToDeleteId.toString()); |
|||
|
|||
// Verify the device is deleted
|
|||
PageDataDevice devicesAfterDelete = client.getTenantDevices(100, 0, null, null, null, null); |
|||
assertEquals(initialSize, devicesAfterDelete.getData().size()); |
|||
|
|||
assertReturns404(() -> |
|||
client.getDeviceById(deviceToDeleteId.toString())); |
|||
|
|||
// assign device to customer
|
|||
client.assignDeviceToCustomer(savedClientCustomer.getId().getId().toString(), savedDeviceWithCreds.getId().getId().toString()); |
|||
|
|||
// check customer devices
|
|||
PageDataDevice pageDataDevice = client.getCustomerDevices(savedClientCustomer.getId().getId().toString(), 100, 0, null, null, null, null); |
|||
List<Device> data = pageDataDevice.getData(); |
|||
assertEquals(1, data.size()); |
|||
assertEquals(savedDeviceWithCreds.getName(), data.get(0).getName()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.Device; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
|
|||
@DaoSqlTest |
|||
public class DeviceConnectivityApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testGetDevicePublishTelemetryCommands() throws Exception { |
|||
Device device = new Device(); |
|||
device.setName(TEST_PREFIX + System.currentTimeMillis()); |
|||
device.setType("default"); |
|||
|
|||
Device savedDevice = client.saveDevice(device, null, null, null, null); |
|||
String token = client.getDeviceCredentialsByDeviceId(savedDevice.getId().getId().toString()).getCredentialsId(); |
|||
|
|||
String deviceId = savedDevice.getId().getId().toString(); |
|||
|
|||
JsonNode commands = client.getDevicePublishTelemetryCommands(deviceId); |
|||
assertEquals("curl -v -X POST http://localhost:8080/api/v1/" + token + "/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", commands.get("http").get("http").asText()); |
|||
assertEquals("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry -u \"" + token + "\" -m \"{temperature:25}\"", commands.get("mqtt").get("mqtt").asText()); |
|||
assertEquals("coap-client -v 6 -m POST -t \"application/json\" -e \"{temperature:25}\" coap://localhost:5683/api/v1/" + token + "/telemetry", commands.get("coap").get("coap").asText()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetDevicePublishTelemetryCommands_nonExistentDevice() { |
|||
String nonExistentId = UUID.randomUUID().toString(); |
|||
assertReturns404(() -> client.getDevicePublishTelemetryCommands(nonExistentId)); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,157 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.DefaultDeviceProfileConfiguration; |
|||
import org.thingsboard.client.model.DefaultDeviceProfileTransportConfiguration; |
|||
import org.thingsboard.client.model.DeviceProfile; |
|||
import org.thingsboard.client.model.DeviceProfileData; |
|||
import org.thingsboard.client.model.DeviceProfileInfo; |
|||
import org.thingsboard.client.model.DeviceProfileType; |
|||
import org.thingsboard.client.model.DeviceTransportType; |
|||
import org.thingsboard.client.model.EntityInfo; |
|||
import org.thingsboard.client.model.PageDataDeviceProfile; |
|||
import org.thingsboard.client.model.PageDataDeviceProfileInfo; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertFalse; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class DeviceProfileApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testDeviceProfileLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<DeviceProfile> createdProfiles = new ArrayList<>(); |
|||
|
|||
// Get initial count (there should be a default profile)
|
|||
PageDataDeviceProfile initialProfiles = client.getDeviceProfiles(100, 0, null, null, null); |
|||
assertNotNull(initialProfiles); |
|||
int initialSize = initialProfiles.getData().size(); |
|||
assertTrue("Expected at least 1 default device profile", initialSize >= 1); |
|||
|
|||
// Get default device profile info
|
|||
DeviceProfileInfo defaultProfileInfo = client.getDefaultDeviceProfileInfo(); |
|||
assertNotNull(defaultProfileInfo); |
|||
assertNotNull(defaultProfileInfo.getName()); |
|||
|
|||
// Create multiple device profiles
|
|||
for (int i = 0; i < 5; i++) { |
|||
DeviceProfile deviceProfile = new DeviceProfile(); |
|||
deviceProfile.setName("Test Device Profile " + timestamp + "_" + i); |
|||
deviceProfile.setDescription("Test description " + i); |
|||
deviceProfile.setType(DeviceProfileType.DEFAULT); |
|||
deviceProfile.setTransportType(DeviceTransportType.DEFAULT); |
|||
|
|||
DeviceProfileData deviceProfileData = new DeviceProfileData(); |
|||
DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); |
|||
configuration.setType(DeviceProfileType.DEFAULT.getValue()); |
|||
deviceProfileData.setConfiguration(configuration); |
|||
DefaultDeviceProfileTransportConfiguration transportConf = new DefaultDeviceProfileTransportConfiguration(); |
|||
transportConf.setType(DeviceTransportType.DEFAULT.getValue()); |
|||
deviceProfileData.setTransportConfiguration(transportConf); |
|||
deviceProfile.setProfileData(deviceProfileData); |
|||
deviceProfile.setDefault(false); |
|||
deviceProfile.setDefaultRuleChainId(null); |
|||
|
|||
DeviceProfile created = client.saveDeviceProfile(deviceProfile); |
|||
assertNotNull(created); |
|||
assertNotNull(created.getId()); |
|||
assertEquals(deviceProfile.getName(), created.getName()); |
|||
assertEquals(deviceProfile.getDescription(), created.getDescription()); |
|||
assertEquals(DeviceProfileType.DEFAULT, created.getType()); |
|||
assertEquals(DeviceTransportType.DEFAULT, created.getTransportType()); |
|||
assertFalse(created.getDefault()); |
|||
|
|||
createdProfiles.add(created); |
|||
} |
|||
|
|||
// Find all, check count
|
|||
PageDataDeviceProfile allProfiles = client.getDeviceProfiles(100, 0, null, null, null); |
|||
assertNotNull(allProfiles); |
|||
assertEquals(initialSize + 5, allProfiles.getData().size()); |
|||
|
|||
// Find all with text search
|
|||
PageDataDeviceProfile filteredProfiles = client.getDeviceProfiles(100, 0, "Test Device Profile " + timestamp, null, null); |
|||
assertEquals(5, filteredProfiles.getData().size()); |
|||
|
|||
// Get by id
|
|||
DeviceProfile searchProfile = createdProfiles.get(2); |
|||
DeviceProfile fetchedProfile = client.getDeviceProfileById(searchProfile.getId().getId().toString(), false); |
|||
assertEquals(searchProfile.getName(), fetchedProfile.getName()); |
|||
assertEquals(searchProfile.getDescription(), fetchedProfile.getDescription()); |
|||
|
|||
// Update device profile
|
|||
fetchedProfile.setDescription("Updated description"); |
|||
DeviceProfile updatedProfile = client.saveDeviceProfile(fetchedProfile); |
|||
assertEquals("Updated description", updatedProfile.getDescription()); |
|||
assertEquals(fetchedProfile.getName(), updatedProfile.getName()); |
|||
|
|||
// Get device profile info by id
|
|||
DeviceProfileInfo profileInfo = client.getDefaultDeviceProfileInfo(); |
|||
assertNotNull(profileInfo); |
|||
assertEquals(searchProfile.getType().getValue().toLowerCase(), profileInfo.getName()); |
|||
assertEquals(DeviceTransportType.DEFAULT, profileInfo.getTransportType()); |
|||
|
|||
// Get device profile infos (paginated)
|
|||
PageDataDeviceProfileInfo profileInfos = client.getDeviceProfileInfos(100, 0, null, null, null, null); |
|||
assertNotNull(profileInfos); |
|||
assertEquals(initialSize + 5, profileInfos.getData().size()); |
|||
|
|||
// Set a profile as default
|
|||
DeviceProfile profileToSetDefault = createdProfiles.get(1); |
|||
DeviceProfile newDefault = client.setDefaultDeviceProfile(profileToSetDefault.getId().getId().toString()); |
|||
assertNotNull(newDefault); |
|||
assertTrue(newDefault.getDefault()); |
|||
|
|||
// Verify default profile info now points to the new default
|
|||
DeviceProfileInfo newDefaultInfo = client.getDefaultDeviceProfileInfo(); |
|||
assertEquals(profileToSetDefault.getName(), newDefaultInfo.getName()); |
|||
|
|||
// Get device profile names
|
|||
List<EntityInfo> profileNames = client.getDeviceProfileNames(false); |
|||
assertNotNull(profileNames); |
|||
assertEquals(createdProfiles.size() + 1, profileNames.size()); |
|||
|
|||
// Delete device profile (cannot delete the default one, so delete a non-default one)
|
|||
UUID profileToDeleteId = createdProfiles.get(0).getId().getId(); |
|||
client.deleteDeviceProfile(profileToDeleteId.toString()); |
|||
|
|||
// Verify the profile is deleted
|
|||
assertReturns404(() -> |
|||
client.getDeviceProfileById(profileToDeleteId.toString(), false)); |
|||
|
|||
// Verify count after deletion
|
|||
PageDataDeviceProfile profilesAfterDelete = client.getDeviceProfiles(100, 0, null, null, null); |
|||
assertEquals(initialSize + 4, profilesAfterDelete.getData().size()); |
|||
|
|||
// Restore original default profile
|
|||
DeviceProfile originalDefault = initialProfiles.getData().stream() |
|||
.filter(DeviceProfile::getDefault) |
|||
.findFirst() |
|||
.orElseThrow(); |
|||
client.setDefaultDeviceProfile(originalDefault.getId().getId().toString()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,105 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.After; |
|||
import org.junit.Test; |
|||
import org.thingsboard.client.ApiException; |
|||
import org.thingsboard.client.model.Domain; |
|||
import org.thingsboard.client.model.DomainInfo; |
|||
import org.thingsboard.client.model.PageDataDomainInfo; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
|
|||
@DaoSqlTest |
|||
public class DomainApiClientTest extends AbstractApiClientTest { |
|||
|
|||
List<Domain> createdDomains = new ArrayList<>(); |
|||
|
|||
@After |
|||
public void afterDomainTest() { |
|||
createdDomains.forEach(domain -> { |
|||
try { |
|||
client.deleteDomain(domain.getId().getId()); |
|||
} catch (ApiException e) { |
|||
// ignore
|
|||
} |
|||
}); |
|||
} |
|||
|
|||
@Test |
|||
public void testDomainLifecycle() throws Exception { |
|||
client.login("sysadmin@thingsboard.org", "sysadmin"); |
|||
|
|||
long timestamp = System.currentTimeMillis(); |
|||
|
|||
// create 5 domains
|
|||
for (int i = 0; i < 5; i++) { |
|||
Domain domain = new Domain(); |
|||
domain.setName("domain." + i + ".com"); |
|||
domain.setOauth2Enabled(false); |
|||
domain.setPropagateToEdge(false); |
|||
|
|||
Domain created = client.saveDomain(domain, null); |
|||
assertNotNull(created); |
|||
assertNotNull(created.getId()); |
|||
assertEquals(domain.getName(), created.getName()); |
|||
assertEquals(false, created.getOauth2Enabled()); |
|||
|
|||
createdDomains.add(created); |
|||
} |
|||
|
|||
// list tenant domains with text search
|
|||
PageDataDomainInfo filteredDomains = client.getTenantDomainInfos(100, 0, |
|||
"domain.", null, null); |
|||
assertNotNull(filteredDomains); |
|||
assertEquals(5, filteredDomains.getData().size()); |
|||
|
|||
// get domain info by id
|
|||
Domain searchDomain = createdDomains.get(2); |
|||
DomainInfo fetchedInfo = client.getDomainInfoById(searchDomain.getId().getId()); |
|||
assertEquals(searchDomain.getName(), fetchedInfo.getName()); |
|||
assertEquals(searchDomain.getOauth2Enabled(), fetchedInfo.getOauth2Enabled()); |
|||
assertNotNull(fetchedInfo.getOauth2ClientInfos()); |
|||
|
|||
// update domain
|
|||
Domain domainToUpdate = createdDomains.get(3); |
|||
domainToUpdate.setPropagateToEdge(true); |
|||
Domain updatedDomain = client.saveDomain(domainToUpdate, null); |
|||
assertEquals(true, updatedDomain.getPropagateToEdge()); |
|||
|
|||
// delete domain
|
|||
UUID domainToDeleteId = createdDomains.get(0).getId().getId(); |
|||
createdDomains.remove(0); |
|||
client.deleteDomain(domainToDeleteId); |
|||
|
|||
// verify deletion
|
|||
assertReturns404(() -> |
|||
client.getDomainInfoById(domainToDeleteId) |
|||
); |
|||
|
|||
PageDataDomainInfo domainsAfterDelete = client.getTenantDomainInfos(100, 0, |
|||
"domain.", null, null); |
|||
assertEquals(4, domainsAfterDelete.getData().size()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,141 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.Edge; |
|||
import org.thingsboard.client.model.EdgeInfo; |
|||
import org.thingsboard.client.model.PageDataEdge; |
|||
import org.thingsboard.client.model.PageDataEdgeInfo; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
|
|||
@DaoSqlTest |
|||
public class EdgeApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testEdgeLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<Edge> createdEdges = new ArrayList<>(); |
|||
|
|||
// create 5 edges
|
|||
for (int i = 0; i < 5; i++) { |
|||
Edge edge = new Edge(); |
|||
edge.setName(TEST_PREFIX + "Edge_" + timestamp + "_" + i); |
|||
edge.setType("gateway"); |
|||
edge.setLabel("Test Edge " + i); |
|||
edge.setRoutingKey("routing_key_" + timestamp + "_" + i); |
|||
edge.setSecret("secret_key_" + timestamp + "_" + i); |
|||
|
|||
Edge created = client.saveEdge(edge); |
|||
assertNotNull(created); |
|||
assertNotNull(created.getId()); |
|||
assertEquals(edge.getName(), created.getName()); |
|||
assertEquals("gateway", created.getType()); |
|||
assertNotNull(created.getRoutingKey()); |
|||
assertNotNull(created.getSecret()); |
|||
|
|||
createdEdges.add(created); |
|||
} |
|||
|
|||
// list tenant edges with text search
|
|||
PageDataEdge filteredEdges = client.getTenantEdges(100, 0, null, |
|||
TEST_PREFIX + "Edge_" + timestamp, null, null); |
|||
assertNotNull(filteredEdges); |
|||
assertEquals(5, filteredEdges.getData().size()); |
|||
|
|||
// list tenant edges with type filter
|
|||
PageDataEdge typedEdges = client.getTenantEdges(100, 0, "gateway", |
|||
TEST_PREFIX + "Edge_" + timestamp, null, null); |
|||
assertEquals(5, typedEdges.getData().size()); |
|||
|
|||
// get tenant edge infos
|
|||
PageDataEdgeInfo edgeInfos = client.getTenantEdgeInfos(100, 0, null, |
|||
TEST_PREFIX + "Edge_" + timestamp, null, null); |
|||
assertEquals(5, edgeInfos.getData().size()); |
|||
|
|||
// get edge by id
|
|||
Edge searchEdge = createdEdges.get(2); |
|||
Edge fetchedEdge = client.getEdgeById(searchEdge.getId().getId().toString()); |
|||
assertEquals(searchEdge.getName(), fetchedEdge.getName()); |
|||
assertEquals(searchEdge.getType(), fetchedEdge.getType()); |
|||
assertEquals(searchEdge.getRoutingKey(), fetchedEdge.getRoutingKey()); |
|||
|
|||
// get edge by name
|
|||
Edge fetchedByName = client.getTenantEdgeByName(searchEdge.getName()); |
|||
assertEquals(searchEdge.getId().getId(), fetchedByName.getId().getId()); |
|||
|
|||
// get edges by list of ids
|
|||
List<String> idsToFetch = List.of( |
|||
createdEdges.get(0).getId().getId().toString(), |
|||
createdEdges.get(1).getId().getId().toString() |
|||
); |
|||
List<Edge> edgeList = client.getEdgeList(idsToFetch); |
|||
assertEquals(2, edgeList.size()); |
|||
|
|||
// update edge
|
|||
Edge edgeToUpdate = createdEdges.get(3); |
|||
edgeToUpdate.setLabel("Updated Label"); |
|||
Edge updatedEdge = client.saveEdge(edgeToUpdate); |
|||
assertEquals("Updated Label", updatedEdge.getLabel()); |
|||
|
|||
// assign edge to customer
|
|||
String customerId = savedClientCustomer.getId().getId().toString(); |
|||
String edgeId = createdEdges.get(1).getId().getId().toString(); |
|||
Edge assignedEdge = client.assignEdgeToCustomer(customerId, edgeId); |
|||
assertNotNull(assignedEdge.getCustomerId()); |
|||
|
|||
// get customer edges
|
|||
PageDataEdge customerEdges = client.getCustomerEdges(customerId, 100, 0, |
|||
null, TEST_PREFIX + "Edge_" + timestamp, null, null); |
|||
assertEquals(1, customerEdges.getData().size()); |
|||
|
|||
// get customer edge infos
|
|||
PageDataEdgeInfo customerEdgeInfos = client.getCustomerEdgeInfos(customerId, 100, 0, |
|||
null, TEST_PREFIX + "Edge_" + timestamp, null, null); |
|||
assertEquals(1, customerEdgeInfos.getData().size()); |
|||
EdgeInfo edgeInfo = customerEdgeInfos.getData().get(0); |
|||
assertNotNull(edgeInfo.getCustomerTitle()); |
|||
|
|||
// unassign edge from customer
|
|||
Edge unassignedEdge = client.unassignEdgeFromCustomer(edgeId); |
|||
assertNotNull(unassignedEdge); |
|||
|
|||
PageDataEdge customerEdgesAfter = client.getCustomerEdges(customerId, 100, 0, |
|||
null, TEST_PREFIX + "Edge_" + timestamp, null, null); |
|||
assertEquals(0, customerEdgesAfter.getData().size()); |
|||
|
|||
// delete edge
|
|||
UUID edgeToDeleteId = createdEdges.get(0).getId().getId(); |
|||
client.deleteEdge(edgeToDeleteId.toString()); |
|||
|
|||
// verify deletion
|
|||
assertReturns404(() -> |
|||
client.getEdgeById(edgeToDeleteId.toString()) |
|||
); |
|||
|
|||
PageDataEdge edgesAfterDelete = client.getTenantEdges(100, 0, null, |
|||
TEST_PREFIX + "Edge_" + timestamp, null, null); |
|||
assertEquals(4, edgesAfterDelete.getData().size()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,289 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.AliasEntityId; |
|||
import org.thingsboard.client.model.Asset; |
|||
import org.thingsboard.client.model.AssetTypeFilter; |
|||
import org.thingsboard.client.model.Device; |
|||
import org.thingsboard.client.model.DeviceTypeFilter; |
|||
import org.thingsboard.client.model.Direction; |
|||
import org.thingsboard.client.model.EntityData; |
|||
import org.thingsboard.client.model.EntityDataPageLink; |
|||
import org.thingsboard.client.model.EntityDataQuery; |
|||
import org.thingsboard.client.model.EntityDataSortOrder; |
|||
import org.thingsboard.client.model.EntityKey; |
|||
import org.thingsboard.client.model.EntityKeyType; |
|||
import org.thingsboard.client.model.EntityKeyValueType; |
|||
import org.thingsboard.client.model.EntityListFilter; |
|||
import org.thingsboard.client.model.EntityNameFilter; |
|||
import org.thingsboard.client.model.EntityType; |
|||
import org.thingsboard.client.model.FilterPredicateValueString; |
|||
import org.thingsboard.client.model.KeyFilter; |
|||
import org.thingsboard.client.model.PageDataEntityData; |
|||
import org.thingsboard.client.model.SingleEntityFilter; |
|||
import org.thingsboard.client.model.StringFilterPredicate; |
|||
import org.thingsboard.client.model.StringOperation; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.List; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertFalse; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class EntityQueryApiClientTest extends AbstractApiClientTest { |
|||
|
|||
private static final String QUERY_TEST_PREFIX = "QueryTest_"; |
|||
|
|||
private EntityDataPageLink pageLink(int pageSize) { |
|||
return new EntityDataPageLink() |
|||
.pageSize(pageSize) |
|||
.page(0) |
|||
.sortOrder(new EntityDataSortOrder() |
|||
.key(new EntityKey().type(EntityKeyType.ENTITY_FIELD).key("name")) |
|||
.direction(Direction.ASC)); |
|||
} |
|||
|
|||
@Test |
|||
public void testFindByDeviceTypeFilter() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
String type1 = "temperatureSensor"; |
|||
String type2 = "humiditySensor"; |
|||
|
|||
for (int i = 0; i < 3; i++) { |
|||
Device d = new Device(); |
|||
d.setName(QUERY_TEST_PREFIX + "temp_" + ts + "_" + i); |
|||
d.setType(type1); |
|||
client.saveDevice(d, null, null, null, null); |
|||
} |
|||
for (int i = 0; i < 2; i++) { |
|||
Device d = new Device(); |
|||
d.setName(QUERY_TEST_PREFIX + "hum_" + ts + "_" + i); |
|||
d.setType(type2); |
|||
client.saveDevice(d, null, null, null, null); |
|||
} |
|||
|
|||
// filter by single device type
|
|||
EntityDataQuery singleTypeQuery = new EntityDataQuery() |
|||
.entityFilter(new DeviceTypeFilter() |
|||
.deviceTypes(List.of(type1))) |
|||
.pageLink(pageLink(10)) |
|||
.addEntityFieldsItem(new EntityKey().type(EntityKeyType.ENTITY_FIELD).key("name")); |
|||
|
|||
PageDataEntityData result = client.findEntityDataByQuery(singleTypeQuery); |
|||
assertNotNull(result); |
|||
assertEquals(3, result.getTotalElements().intValue()); |
|||
for (EntityData entity : result.getData()) { |
|||
assertNotNull(entity.getEntityId()); |
|||
} |
|||
|
|||
// filter by multiple device types
|
|||
EntityDataQuery multiTypeQuery = new EntityDataQuery() |
|||
.entityFilter(new DeviceTypeFilter() |
|||
.deviceTypes(List.of(type1, type2))) |
|||
.pageLink(pageLink(10)) |
|||
.addEntityFieldsItem(new EntityKey().type(EntityKeyType.ENTITY_FIELD).key("name")); |
|||
|
|||
PageDataEntityData multiResult = client.findEntityDataByQuery(multiTypeQuery); |
|||
assertNotNull(multiResult); |
|||
assertEquals(5, multiResult.getTotalElements().intValue()); |
|||
|
|||
// filter by device type + name filter
|
|||
EntityDataQuery nameFilterQuery = new EntityDataQuery() |
|||
.entityFilter(new DeviceTypeFilter() |
|||
.deviceTypes(List.of(type1, type2)) |
|||
.deviceNameFilter(QUERY_TEST_PREFIX + "temp_" + ts)) |
|||
.pageLink(pageLink(10)) |
|||
.addEntityFieldsItem(new EntityKey().type(EntityKeyType.ENTITY_FIELD).key("name")); |
|||
|
|||
PageDataEntityData nameResult = client.findEntityDataByQuery(nameFilterQuery); |
|||
assertNotNull(nameResult); |
|||
assertEquals(3, nameResult.getTotalElements().intValue()); |
|||
} |
|||
|
|||
@Test |
|||
public void testFindByEntityNameFilter() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
String prefix = QUERY_TEST_PREFIX + "named_" + ts; |
|||
|
|||
for (int i = 0; i < 4; i++) { |
|||
Device d = new Device(); |
|||
d.setName(prefix + "_" + i); |
|||
d.setType("default"); |
|||
client.saveDevice(d, null, null, null, null); |
|||
} |
|||
|
|||
EntityDataQuery query = new EntityDataQuery() |
|||
.entityFilter(new EntityNameFilter() |
|||
.entityType(EntityType.DEVICE) |
|||
.entityNameFilter(prefix)) |
|||
.pageLink(pageLink(10)) |
|||
.addEntityFieldsItem(new EntityKey().type(EntityKeyType.ENTITY_FIELD).key("name")); |
|||
|
|||
PageDataEntityData result = client.findEntityDataByQuery(query); |
|||
assertNotNull(result); |
|||
assertEquals(4, result.getTotalElements().intValue()); |
|||
assertFalse(result.getHasNext()); |
|||
} |
|||
|
|||
@Test |
|||
public void testFindByEntityListFilter() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
|
|||
Device d1 = client.saveDevice(new Device().name(QUERY_TEST_PREFIX + "list_" + ts + "_1").type("default"), null, null, null, null); |
|||
Device d2 = client.saveDevice(new Device().name(QUERY_TEST_PREFIX + "list_" + ts + "_2").type("default"), null, null, null, null); |
|||
client.saveDevice(new Device().name(QUERY_TEST_PREFIX + "list_" + ts + "_3").type("default"), null, null, null, null); |
|||
|
|||
EntityDataQuery query = new EntityDataQuery() |
|||
.entityFilter(new EntityListFilter() |
|||
.entityType(EntityType.DEVICE) |
|||
.entityList(List.of( |
|||
d1.getId().getId().toString(), |
|||
d2.getId().getId().toString()))) |
|||
.pageLink(pageLink(10)) |
|||
.addEntityFieldsItem(new EntityKey().type(EntityKeyType.ENTITY_FIELD).key("name")); |
|||
|
|||
PageDataEntityData result = client.findEntityDataByQuery(query); |
|||
assertNotNull(result); |
|||
assertEquals(2, result.getTotalElements().intValue()); |
|||
|
|||
List<String> returnedIds = result.getData().stream() |
|||
.map(e -> e.getEntityId().getId().toString()) |
|||
.collect(Collectors.toList()); |
|||
assertTrue(returnedIds.contains(d1.getId().getId().toString())); |
|||
assertTrue(returnedIds.contains(d2.getId().getId().toString())); |
|||
} |
|||
|
|||
@Test |
|||
public void testFindBySingleEntityFilter() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = client.saveDevice(new Device().name(QUERY_TEST_PREFIX + "single_" + ts).type("default"), null, null, null, null); |
|||
|
|||
EntityDataQuery query = new EntityDataQuery() |
|||
.entityFilter(new SingleEntityFilter() |
|||
.singleEntity(new AliasEntityId() |
|||
.id(device.getId().getId()) |
|||
.entityType(EntityType.DEVICE))) |
|||
.pageLink(pageLink(10)) |
|||
.addEntityFieldsItem(new EntityKey().type(EntityKeyType.ENTITY_FIELD).key("name")); |
|||
|
|||
PageDataEntityData result = client.findEntityDataByQuery(query); |
|||
assertNotNull(result); |
|||
assertEquals(1, result.getTotalElements().intValue()); |
|||
assertEquals(device.getId().getId().toString(), |
|||
result.getData().get(0).getEntityId().getId().toString()); |
|||
} |
|||
|
|||
@Test |
|||
public void testFindByAssetTypeFilter() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
String assetType = "building"; |
|||
|
|||
for (int i = 0; i < 3; i++) { |
|||
Asset a = new Asset(); |
|||
a.setName(QUERY_TEST_PREFIX + "asset_" + ts + "_" + i); |
|||
a.setType(assetType); |
|||
client.saveAsset(a, null, null, null); |
|||
} |
|||
|
|||
EntityDataQuery query = new EntityDataQuery() |
|||
.entityFilter(new AssetTypeFilter() |
|||
.assetTypes(List.of(assetType))) |
|||
.pageLink(pageLink(10)) |
|||
.addEntityFieldsItem(new EntityKey().type(EntityKeyType.ENTITY_FIELD).key("name")); |
|||
|
|||
PageDataEntityData result = client.findEntityDataByQuery(query); |
|||
assertNotNull(result); |
|||
assertEquals(3, result.getTotalElements().intValue()); |
|||
} |
|||
|
|||
@Test |
|||
public void testFindWithKeyFilter() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
String matchName = QUERY_TEST_PREFIX + "kf_match_" + ts; |
|||
String noMatchName = QUERY_TEST_PREFIX + "kf_other_" + ts; |
|||
|
|||
client.saveDevice(new Device().name(matchName).type("default"), null, null, null, null); |
|||
client.saveDevice(new Device().name(noMatchName).type("default"), null, null, null, null); |
|||
|
|||
KeyFilter nameKeyFilter = new KeyFilter() |
|||
.key(new EntityKey().type(EntityKeyType.ENTITY_FIELD).key("name")) |
|||
.valueType(EntityKeyValueType.STRING) |
|||
.predicate(new StringFilterPredicate() |
|||
.operation(StringOperation.CONTAINS) |
|||
.value(new FilterPredicateValueString().defaultValue("kf_match")) |
|||
.ignoreCase(true)); |
|||
|
|||
EntityDataQuery query = new EntityDataQuery() |
|||
.entityFilter(new EntityNameFilter() |
|||
.entityType(EntityType.DEVICE) |
|||
.entityNameFilter(QUERY_TEST_PREFIX + "kf_")) |
|||
.addKeyFiltersItem(nameKeyFilter) |
|||
.pageLink(pageLink(10)) |
|||
.addEntityFieldsItem(new EntityKey().type(EntityKeyType.ENTITY_FIELD).key("name")); |
|||
|
|||
PageDataEntityData result = client.findEntityDataByQuery(query); |
|||
assertNotNull(result); |
|||
assertEquals(1, result.getTotalElements().intValue()); |
|||
} |
|||
|
|||
@Test |
|||
public void testFindWithPagination() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
|
|||
for (int i = 0; i < 5; i++) { |
|||
Device d = new Device(); |
|||
d.setName(QUERY_TEST_PREFIX + "page_" + ts + "_" + i); |
|||
d.setType("default"); |
|||
client.saveDevice(d, null, null, null, null); |
|||
} |
|||
|
|||
EntityDataPageLink smallPage = new EntityDataPageLink() |
|||
.pageSize(2) |
|||
.page(0) |
|||
.sortOrder(new EntityDataSortOrder() |
|||
.key(new EntityKey().type(EntityKeyType.ENTITY_FIELD).key("name")) |
|||
.direction(Direction.ASC)); |
|||
|
|||
EntityDataQuery query = new EntityDataQuery() |
|||
.entityFilter(new EntityNameFilter() |
|||
.entityType(EntityType.DEVICE) |
|||
.entityNameFilter(QUERY_TEST_PREFIX + "page_" + ts)) |
|||
.pageLink(smallPage) |
|||
.addEntityFieldsItem(new EntityKey().type(EntityKeyType.ENTITY_FIELD).key("name")); |
|||
|
|||
// first page
|
|||
PageDataEntityData page1 = client.findEntityDataByQuery(query); |
|||
assertNotNull(page1); |
|||
assertEquals(5, page1.getTotalElements().intValue()); |
|||
assertEquals(3, page1.getTotalPages().intValue()); |
|||
assertEquals(2, page1.getData().size()); |
|||
assertTrue(page1.getHasNext()); |
|||
|
|||
// last page
|
|||
smallPage.setPage(2); |
|||
PageDataEntityData lastPage = client.findEntityDataByQuery(query); |
|||
assertNotNull(lastPage); |
|||
assertEquals(1, lastPage.getData().size()); |
|||
assertFalse(lastPage.getHasNext()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,182 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.Asset; |
|||
import org.thingsboard.client.model.Device; |
|||
import org.thingsboard.client.model.EntityRelation; |
|||
import org.thingsboard.client.model.EntityRelationInfo; |
|||
import org.thingsboard.client.model.EntityRelationsQuery; |
|||
import org.thingsboard.client.model.EntitySearchDirection; |
|||
import org.thingsboard.client.model.EntityType; |
|||
import org.thingsboard.client.model.RelationEntityTypeFilter; |
|||
import org.thingsboard.client.model.RelationTypeGroup; |
|||
import org.thingsboard.client.model.RelationsSearchParameters; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.List; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class EntityRelationApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testEntityRelationLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
|
|||
// create assets and devices to relate
|
|||
Asset building = new Asset(); |
|||
building.setName(TEST_PREFIX + "Building_" + timestamp); |
|||
building.setType("building"); |
|||
building = client.saveAsset(building, null, null, null); |
|||
|
|||
Asset floor = new Asset(); |
|||
floor.setName(TEST_PREFIX + "Floor_" + timestamp); |
|||
floor.setType("floor"); |
|||
floor = client.saveAsset(floor, null, null, null); |
|||
|
|||
Device device1 = new Device(); |
|||
device1.setName(TEST_PREFIX + "Sensor_" + timestamp + "_1"); |
|||
device1.setType("sensor"); |
|||
device1 = client.saveDevice(device1, null, null, null, null); |
|||
|
|||
Device device2 = new Device(); |
|||
device2.setName(TEST_PREFIX + "Sensor_" + timestamp + "_2"); |
|||
device2.setType("sensor"); |
|||
device2 = client.saveDevice(device2, null, null, null, null); |
|||
|
|||
Device device3 = new Device(); |
|||
device3.setName(TEST_PREFIX + "Sensor_" + timestamp + "_3"); |
|||
device3.setType("sensor"); |
|||
device3 = client.saveDevice(device3, null, null, null, null); |
|||
|
|||
// create relations: building -> Contains -> floor, floor -> Contains -> device1/device2/device3
|
|||
EntityRelation buildingToFloor = new EntityRelation(); |
|||
buildingToFloor.setFrom(building.getId()); |
|||
buildingToFloor.setTo(floor.getId()); |
|||
buildingToFloor.setType("Contains"); |
|||
buildingToFloor.setTypeGroup(RelationTypeGroup.COMMON); |
|||
EntityRelation savedRelation = client.saveRelation(buildingToFloor); |
|||
assertNotNull(savedRelation); |
|||
assertEquals("Contains", savedRelation.getType()); |
|||
|
|||
client.saveRelation(new EntityRelation() |
|||
.from(floor.getId()) |
|||
.to(device1.getId()) |
|||
.type("Contains") |
|||
.typeGroup(RelationTypeGroup.COMMON)); |
|||
client.saveRelation(new EntityRelation() |
|||
.from(floor.getId()) |
|||
.to(device2.getId()) |
|||
.type("Contains").typeGroup(RelationTypeGroup.COMMON)); |
|||
client.saveRelation(new EntityRelation() |
|||
.from(floor.getId()) |
|||
.to(device3.getId()) |
|||
.type("Manages") |
|||
.typeGroup(RelationTypeGroup.COMMON)); |
|||
|
|||
// get specific relation
|
|||
EntityRelation fetched = client.getRelation( |
|||
building.getId().getId().toString(), "ASSET", |
|||
"Contains", |
|||
floor.getId().getId().toString(), "ASSET", |
|||
RelationTypeGroup.COMMON.getValue()); |
|||
assertNotNull(fetched); |
|||
assertEquals("Contains", fetched.getType()); |
|||
|
|||
// find all relations from floor
|
|||
List<EntityRelation> fromFloor = client.findEntityRelationsByFrom("ASSET", |
|||
floor.getId().getId().toString(), RelationTypeGroup.COMMON.getValue()); |
|||
assertEquals(3, fromFloor.size()); |
|||
|
|||
// find relations from floor with type filter "Contains"
|
|||
List<EntityRelation> containsFromFloor = client.findEntityRelationsByFromAndRelationType("ASSET", |
|||
floor.getId().getId().toString(), "Contains", RelationTypeGroup.COMMON.getValue()); |
|||
assertEquals(2, containsFromFloor.size()); |
|||
|
|||
// find relations to device1
|
|||
List<EntityRelation> toDevice1 = client.findEntityRelationsByTo("DEVICE", |
|||
device1.getId().getId().toString(), RelationTypeGroup.COMMON.getValue()); |
|||
assertEquals(1, toDevice1.size()); |
|||
assertEquals("Contains", toDevice1.get(0).getType()); |
|||
|
|||
// find relations to device3 with type filter "Manages"
|
|||
List<EntityRelation> managesToDevice3 = client.findEntityRelationsByToAndRelationType("DEVICE", |
|||
device3.getId().getId().toString(), "Manages", RelationTypeGroup.COMMON.getValue()); |
|||
assertEquals(1, managesToDevice3.size()); |
|||
|
|||
// find info by from (includes entity names)
|
|||
List<EntityRelationInfo> infoFromFloor = client.findEntityRelationInfosByFrom("ASSET", |
|||
floor.getId().getId().toString(), RelationTypeGroup.COMMON.getValue()); |
|||
assertEquals(3, infoFromFloor.size()); |
|||
Device finalDevice = device1; |
|||
assertTrue(infoFromFloor.stream().anyMatch(info -> |
|||
finalDevice.getName().equals(info.getToName()))); |
|||
|
|||
// find info by to
|
|||
List<EntityRelationInfo> infoToDevice2 = client.findEntityRelationInfosByTo("DEVICE", |
|||
device2.getId().getId().toString(), RelationTypeGroup.COMMON.getValue()); |
|||
assertEquals(1, infoToDevice2.size()); |
|||
assertEquals(floor.getName(), infoToDevice2.get(0).getFromName()); |
|||
|
|||
// find by query - search from building, direction FROM, max 2 levels
|
|||
RelationsSearchParameters params = new RelationsSearchParameters(); |
|||
params.setRootId(building.getId().getId()); |
|||
params.setRootType(EntityType.ASSET); |
|||
params.setDirection(EntitySearchDirection.FROM); |
|||
params.setRelationTypeGroup(RelationTypeGroup.COMMON); |
|||
params.setMaxLevel(2); |
|||
|
|||
RelationEntityTypeFilter filter = new RelationEntityTypeFilter(); |
|||
filter.setRelationType("Contains"); |
|||
filter.setEntityTypes(List.of(EntityType.ASSET, EntityType.DEVICE)); |
|||
|
|||
EntityRelationsQuery query = new EntityRelationsQuery(); |
|||
query.setParameters(params); |
|||
query.setFilters(List.of(filter)); |
|||
|
|||
List<EntityRelation> queryResult = client.findEntityRelationsByQuery(query); |
|||
assertTrue(queryResult.size() >= 3); |
|||
|
|||
// find info by query
|
|||
List<EntityRelationInfo> infoQueryResult = client.findEntityRelationInfosByQuery(query); |
|||
assertTrue(infoQueryResult.size() >= 3); |
|||
|
|||
// delete single relation
|
|||
client.deleteRelation( |
|||
floor.getId().getId().toString(), "ASSET", |
|||
"Manages", |
|||
device3.getId().getId().toString(), "DEVICE", |
|||
RelationTypeGroup.COMMON.getValue()); |
|||
|
|||
// verify deletion
|
|||
List<EntityRelation> afterDelete = client.findEntityRelationsByFrom("ASSET", |
|||
floor.getId().getId().toString(), RelationTypeGroup.COMMON.getValue()); |
|||
assertEquals(2, afterDelete.size()); |
|||
|
|||
// delete all relations for building
|
|||
client.deleteRelations(building.getId().getId().toString(), "ASSET"); |
|||
|
|||
List<EntityRelation> afterDeleteAll = client.findEntityRelationsByFrom("ASSET", |
|||
building.getId().getId().toString(), RelationTypeGroup.COMMON.getValue()); |
|||
assertEquals(0, afterDeleteAll.size()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,267 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.AttributesEntityView; |
|||
import org.thingsboard.client.model.Device; |
|||
import org.thingsboard.client.model.EntitySubtype; |
|||
import org.thingsboard.client.model.EntityView; |
|||
import org.thingsboard.client.model.EntityViewInfo; |
|||
import org.thingsboard.client.model.PageDataEntityView; |
|||
import org.thingsboard.client.model.PageDataEntityViewInfo; |
|||
import org.thingsboard.client.model.TelemetryEntityView; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertFalse; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class EntityViewApiClientTest extends AbstractApiClientTest { |
|||
|
|||
private static final String EV_PREFIX = "EvTest_"; |
|||
|
|||
@Test |
|||
public void testSaveAndGetEntityView() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createTestDevice(String.valueOf(ts)); |
|||
|
|||
EntityView ev = new EntityView(); |
|||
ev.setName(EV_PREFIX + "save_" + ts); |
|||
ev.setType("testType"); |
|||
ev.setEntityId(device.getId()); |
|||
ev.setKeys(new TelemetryEntityView() |
|||
.timeseries(List.of("temperature", "humidity")) |
|||
.attributes(new AttributesEntityView() |
|||
.cs(List.of("firmware")) |
|||
.ss(List.of("active")) |
|||
.sh(List.of()))); |
|||
ev.setStartTimeMs(1000L); |
|||
ev.setEndTimeMs(2000L); |
|||
|
|||
EntityView saved = client.saveEntityView(ev, null, null, null); |
|||
assertNotNull(saved); |
|||
assertNotNull(saved.getId()); |
|||
assertEquals(ev.getName(), saved.getName()); |
|||
assertEquals("testType", saved.getType()); |
|||
assertEquals(device.getId().getId(), saved.getEntityId().getId()); |
|||
assertEquals(List.of("temperature", "humidity"), saved.getKeys().getTimeseries()); |
|||
assertEquals(1000L, saved.getStartTimeMs().longValue()); |
|||
assertEquals(2000L, saved.getEndTimeMs().longValue()); |
|||
|
|||
// get by id
|
|||
String evId = saved.getId().getId().toString(); |
|||
EntityView fetched = client.getEntityViewById(evId); |
|||
assertNotNull(fetched); |
|||
assertEquals(saved.getName(), fetched.getName()); |
|||
assertEquals(saved.getType(), fetched.getType()); |
|||
assertEquals(saved.getEntityId().getId(), fetched.getEntityId().getId()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetEntityViewInfoById() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createTestDevice(String.valueOf(ts)); |
|||
EntityView saved = createEntityView(EV_PREFIX + "info_" + ts, "infoType", device); |
|||
|
|||
EntityViewInfo info = client.getEntityViewInfoById(saved.getId().getId().toString()); |
|||
assertNotNull(info); |
|||
assertEquals(saved.getName(), info.getName()); |
|||
assertEquals("infoType", info.getType()); |
|||
assertNotNull(info.getEntityId()); |
|||
} |
|||
|
|||
@Test |
|||
public void testUpdateEntityView() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createTestDevice(String.valueOf(ts)); |
|||
EntityView saved = createEntityView(EV_PREFIX + "update_" + ts, "default", device); |
|||
|
|||
saved.setName(EV_PREFIX + "updated_" + ts); |
|||
saved.setKeys(new TelemetryEntityView() |
|||
.timeseries(List.of("temperature", "pressure")) |
|||
.attributes(new AttributesEntityView() |
|||
.cs(List.of()) |
|||
.ss(List.of()) |
|||
.sh(List.of()))); |
|||
|
|||
EntityView updated = client.saveEntityView(saved, null, null, null); |
|||
assertEquals(EV_PREFIX + "updated_" + ts, updated.getName()); |
|||
assertEquals(List.of("temperature", "pressure"), updated.getKeys().getTimeseries()); |
|||
assertEquals(saved.getId().getId(), updated.getId().getId()); |
|||
} |
|||
|
|||
@Test |
|||
public void testDeleteEntityView() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createTestDevice(String.valueOf(ts)); |
|||
EntityView saved = createEntityView(EV_PREFIX + "delete_" + ts, "default", device); |
|||
|
|||
String evId = saved.getId().getId().toString(); |
|||
client.getEntityViewById(evId); |
|||
|
|||
client.deleteEntityView(evId); |
|||
|
|||
assertReturns404(() -> client.getEntityViewById(evId)); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetTenantEntityViews() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createTestDevice(String.valueOf(ts)); |
|||
|
|||
for (int i = 0; i < 3; i++) { |
|||
createEntityView(EV_PREFIX + "tenant_" + ts + "_" + i, "tenantViewType", device); |
|||
} |
|||
|
|||
PageDataEntityView page = client.getTenantEntityViews(100, 0, null, EV_PREFIX + "tenant_" + ts, null, null); |
|||
assertNotNull(page); |
|||
assertEquals(3, page.getTotalElements().intValue()); |
|||
for (EntityView ev : page.getData()) { |
|||
assertTrue(ev.getName().startsWith(EV_PREFIX + "tenant_" + ts)); |
|||
} |
|||
} |
|||
|
|||
@Test |
|||
public void testGetTenantEntityViewInfos() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createTestDevice(String.valueOf(ts)); |
|||
createEntityView(EV_PREFIX + "tinfo_" + ts, "default", device); |
|||
|
|||
PageDataEntityViewInfo page = client.getTenantEntityViewInfos(100, 0, null, EV_PREFIX + "tinfo_" + ts, null, null); |
|||
assertNotNull(page); |
|||
assertEquals(1, page.getTotalElements().intValue()); |
|||
assertEquals(EV_PREFIX + "tinfo_" + ts, page.getData().get(0).getName()); |
|||
} |
|||
|
|||
@Test |
|||
public void testAssignAndUnassignEntityViewToCustomer() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createTestDevice(String.valueOf(ts)); |
|||
EntityView saved = createEntityView(EV_PREFIX + "assign_" + ts, "default", device); |
|||
|
|||
String evId = saved.getId().getId().toString(); |
|||
String customerId = savedClientCustomer.getId().getId().toString(); |
|||
|
|||
// assign to customer
|
|||
EntityView assigned = client.assignEntityViewToCustomer(customerId, evId); |
|||
assertNotNull(assigned); |
|||
assertEquals(savedClientCustomer.getId().getId(), assigned.getCustomerId().getId()); |
|||
|
|||
// verify in customer entity views
|
|||
PageDataEntityView customerViews = client.getCustomerEntityViews( |
|||
customerId, 100, 0, null, EV_PREFIX + "assign_" + ts, null, null); |
|||
assertEquals(1, customerViews.getTotalElements().intValue()); |
|||
assertEquals(saved.getName(), customerViews.getData().get(0).getName()); |
|||
|
|||
// unassign from customer
|
|||
EntityView unassigned = client.unassignEntityViewFromCustomer(evId); |
|||
assertNotNull(unassigned); |
|||
|
|||
PageDataEntityView afterUnassign = client.getCustomerEntityViews( |
|||
customerId, 100, 0, null, EV_PREFIX + "assign_" + ts, null, null); |
|||
assertEquals(0, afterUnassign.getTotalElements().intValue()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetCustomerEntityViewInfos() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createTestDevice(String.valueOf(ts)); |
|||
EntityView saved = createEntityView(EV_PREFIX + "cinfo_" + ts, "default", device); |
|||
|
|||
String evId = saved.getId().getId().toString(); |
|||
String customerId = savedClientCustomer.getId().getId().toString(); |
|||
|
|||
client.assignEntityViewToCustomer(customerId, evId); |
|||
|
|||
PageDataEntityViewInfo infos = client.getCustomerEntityViewInfos( |
|||
customerId, 100, 0, null, EV_PREFIX + "cinfo_" + ts, null, null); |
|||
assertNotNull(infos); |
|||
assertEquals(1, infos.getTotalElements().intValue()); |
|||
assertEquals(saved.getName(), infos.getData().get(0).getName()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetEntityViewTypes() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createTestDevice(String.valueOf(ts)); |
|||
createEntityView(EV_PREFIX + "types_" + ts, "uniqueEvType_" + ts, device); |
|||
|
|||
List<EntitySubtype> types = client.getEntityViewTypes(); |
|||
assertNotNull(types); |
|||
assertFalse(types.isEmpty()); |
|||
|
|||
List<String> typeNames = types.stream() |
|||
.map(EntitySubtype::getType) |
|||
.collect(Collectors.toList()); |
|||
assertTrue(typeNames.contains("uniqueEvType_" + ts)); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetEntityViewById_notFound() { |
|||
String nonExistentId = UUID.randomUUID().toString(); |
|||
assertReturns404(() -> client.getEntityViewById(nonExistentId)); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetTenantEntityViewsPagination() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createTestDevice(String.valueOf(ts)); |
|||
|
|||
for (int i = 0; i < 5; i++) { |
|||
createEntityView(EV_PREFIX + "paged_" + ts + "_" + i, "default", device); |
|||
} |
|||
|
|||
PageDataEntityView page1 = client.getTenantEntityViews(2, 0, null, EV_PREFIX + "paged_" + ts, null, null); |
|||
assertNotNull(page1); |
|||
assertEquals(5, page1.getTotalElements().intValue()); |
|||
assertEquals(3, page1.getTotalPages().intValue()); |
|||
assertEquals(2, page1.getData().size()); |
|||
assertTrue(page1.getHasNext()); |
|||
|
|||
PageDataEntityView lastPage = client.getTenantEntityViews(2, 2, null, EV_PREFIX + "paged_" + ts, null, null); |
|||
assertEquals(1, lastPage.getData().size()); |
|||
assertFalse(lastPage.getHasNext()); |
|||
} |
|||
|
|||
private Device createTestDevice(String suffix) throws Exception { |
|||
Device device = new Device(); |
|||
device.setName(EV_PREFIX + "device_" + suffix); |
|||
device.setType("default"); |
|||
return client.saveDevice(device, null, null, null, null); |
|||
} |
|||
|
|||
private EntityView createEntityView(String name, String type, Device device) throws Exception { |
|||
EntityView ev = new EntityView(); |
|||
ev.setName(name); |
|||
ev.setType(type); |
|||
ev.setEntityId(device.getId()); |
|||
ev.setKeys(new TelemetryEntityView() |
|||
.timeseries(List.of("temperature")) |
|||
.attributes(new AttributesEntityView() |
|||
.cs(List.of()) |
|||
.ss(List.of()) |
|||
.sh(List.of()))); |
|||
return client.saveEntityView(ev, null, null, null); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,156 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.MobileApp; |
|||
import org.thingsboard.client.model.MobileAppBundle; |
|||
import org.thingsboard.client.model.MobileAppBundleInfo; |
|||
import org.thingsboard.client.model.MobileAppStatus; |
|||
import org.thingsboard.client.model.PageDataMobileApp; |
|||
import org.thingsboard.client.model.PageDataMobileAppBundleInfo; |
|||
import org.thingsboard.client.model.PlatformType; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
|
|||
@DaoSqlTest |
|||
public class MobileAppApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testMobileAppLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<MobileApp> createdApps = new ArrayList<>(); |
|||
|
|||
// create 3 Android apps
|
|||
for (int i = 0; i < 3; i++) { |
|||
MobileApp app = new MobileApp(); |
|||
app.setPkgName("com.test.android." + timestamp + "." + i); |
|||
app.setTitle(TEST_PREFIX + "AndroidApp_" + timestamp + "_" + i); |
|||
app.setAppSecret("secret_android_" + timestamp + "_" + i); |
|||
app.setPlatformType(PlatformType.ANDROID); |
|||
app.setStatus(MobileAppStatus.DRAFT); |
|||
|
|||
MobileApp created = client.saveMobileApp(app); |
|||
assertNotNull(created); |
|||
assertNotNull(created.getId()); |
|||
assertEquals(app.getPkgName(), created.getPkgName()); |
|||
assertEquals(PlatformType.ANDROID, created.getPlatformType()); |
|||
assertEquals(MobileAppStatus.DRAFT, created.getStatus()); |
|||
|
|||
createdApps.add(created); |
|||
} |
|||
|
|||
// create 2 iOS apps
|
|||
for (int i = 0; i < 2; i++) { |
|||
MobileApp app = new MobileApp(); |
|||
app.setPkgName("com.test.ios." + timestamp + "." + i); |
|||
app.setTitle(TEST_PREFIX + "IosApp_" + timestamp + "_" + i); |
|||
app.setAppSecret("secret_ios_" + timestamp + "_" + i); |
|||
app.setPlatformType(PlatformType.IOS); |
|||
app.setStatus(MobileAppStatus.DRAFT); |
|||
|
|||
MobileApp created = client.saveMobileApp(app); |
|||
assertNotNull(created); |
|||
createdApps.add(created); |
|||
} |
|||
|
|||
// list all tenant mobile apps
|
|||
PageDataMobileApp allApps = client.getTenantMobileApps(100, 0, null, |
|||
null, null, null); |
|||
assertNotNull(allApps); |
|||
assertEquals(5, allApps.getData().size()); |
|||
|
|||
// list with platform type filter
|
|||
PageDataMobileApp androidApps = client.getTenantMobileApps(100, 0, PlatformType.ANDROID, |
|||
null, null, null); |
|||
assertEquals(3, androidApps.getData().size()); |
|||
|
|||
PageDataMobileApp iosApps = client.getTenantMobileApps(100, 0, PlatformType.IOS, |
|||
null, null, null); |
|||
assertEquals(2, iosApps.getData().size()); |
|||
|
|||
// get mobile app by id
|
|||
MobileApp searchApp = createdApps.get(1); |
|||
MobileApp fetchedApp = client.getMobileAppById(searchApp.getId().getId()); |
|||
assertEquals(searchApp.getPkgName(), fetchedApp.getPkgName()); |
|||
assertEquals(searchApp.getTitle(), fetchedApp.getTitle()); |
|||
assertEquals(searchApp.getPlatformType(), fetchedApp.getPlatformType()); |
|||
|
|||
// update mobile app
|
|||
MobileApp appToUpdate = createdApps.get(2); |
|||
appToUpdate.setTitle(appToUpdate.getTitle() + "_updated"); |
|||
MobileApp updatedApp = client.saveMobileApp(appToUpdate); |
|||
assertEquals(appToUpdate.getTitle(), updatedApp.getTitle()); |
|||
|
|||
// create mobile app bundle with android and ios apps
|
|||
MobileAppBundle bundle = new MobileAppBundle(); |
|||
bundle.setTitle(TEST_PREFIX + "Bundle_" + timestamp); |
|||
bundle.setDescription("Test bundle"); |
|||
bundle.setAndroidAppId(createdApps.get(0).getId()); |
|||
bundle.setIosAppId(createdApps.get(3).getId()); |
|||
bundle.setOauth2Enabled(false); |
|||
|
|||
MobileAppBundle savedBundle = client.saveMobileAppBundle(bundle, null); |
|||
assertNotNull(savedBundle); |
|||
assertNotNull(savedBundle.getId()); |
|||
assertEquals(bundle.getTitle(), savedBundle.getTitle()); |
|||
|
|||
// get bundle info by id
|
|||
MobileAppBundleInfo bundleInfo = client.getMobileAppBundleInfoById(savedBundle.getId().getId()); |
|||
assertEquals(savedBundle.getTitle(), bundleInfo.getTitle()); |
|||
assertEquals("Test bundle", bundleInfo.getDescription()); |
|||
assertNotNull(bundleInfo.getAndroidPkgName()); |
|||
assertNotNull(bundleInfo.getIosPkgName()); |
|||
|
|||
// list tenant bundles
|
|||
PageDataMobileAppBundleInfo bundles = client.getTenantMobileAppBundleInfos(100, 0, |
|||
TEST_PREFIX + "Bundle_" + timestamp, null, null); |
|||
assertEquals(1, bundles.getData().size()); |
|||
|
|||
// update bundle
|
|||
savedBundle.setDescription("Updated description"); |
|||
MobileAppBundle updatedBundle = client.saveMobileAppBundle(savedBundle, null); |
|||
assertEquals("Updated description", updatedBundle.getDescription()); |
|||
|
|||
// delete bundle
|
|||
client.deleteMobileAppBundle(savedBundle.getId().getId()); |
|||
|
|||
// verify bundle deletion
|
|||
assertReturns404(() -> |
|||
client.getMobileAppBundleInfoById(savedBundle.getId().getId()) |
|||
); |
|||
|
|||
// delete mobile app
|
|||
UUID appToDeleteId = createdApps.get(0).getId().getId(); |
|||
client.deleteMobileApp(appToDeleteId); |
|||
|
|||
// verify app deletion
|
|||
assertReturns404(() -> |
|||
client.getMobileAppById(appToDeleteId) |
|||
); |
|||
|
|||
PageDataMobileApp appsAfterDelete = client.getTenantMobileApps(100, 0, null, |
|||
null, null, null); |
|||
assertEquals(4, appsAfterDelete.getData().size()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,278 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.EntityActionNotificationRuleTriggerConfig; |
|||
import org.thingsboard.client.model.EntityActionRecipientsConfig; |
|||
import org.thingsboard.client.model.EntityType; |
|||
import org.thingsboard.client.model.NotificationDeliveryMethod; |
|||
import org.thingsboard.client.model.NotificationRequest; |
|||
import org.thingsboard.client.model.NotificationRequestInfo; |
|||
import org.thingsboard.client.model.NotificationRule; |
|||
import org.thingsboard.client.model.NotificationRuleInfo; |
|||
import org.thingsboard.client.model.NotificationRuleTriggerType; |
|||
import org.thingsboard.client.model.NotificationSettings; |
|||
import org.thingsboard.client.model.NotificationTarget; |
|||
import org.thingsboard.client.model.NotificationTemplate; |
|||
import org.thingsboard.client.model.NotificationTemplateConfig; |
|||
import org.thingsboard.client.model.NotificationType; |
|||
import org.thingsboard.client.model.PageDataNotification; |
|||
import org.thingsboard.client.model.PageDataNotificationRequestInfo; |
|||
import org.thingsboard.client.model.PageDataNotificationRuleInfo; |
|||
import org.thingsboard.client.model.PageDataNotificationTarget; |
|||
import org.thingsboard.client.model.PageDataNotificationTemplate; |
|||
import org.thingsboard.client.model.PlatformUsersNotificationTargetConfig; |
|||
import org.thingsboard.client.model.TenantAdministratorsFilter; |
|||
import org.thingsboard.client.model.WebDeliveryMethodNotificationTemplate; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.List; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertFalse; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class NotificationApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testNotificationLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
|
|||
// === 1. Notification Target CRUD ===
|
|||
|
|||
// Create target
|
|||
TenantAdministratorsFilter usersFilter = new TenantAdministratorsFilter(); |
|||
PlatformUsersNotificationTargetConfig targetConfig = |
|||
new PlatformUsersNotificationTargetConfig().usersFilter(usersFilter); |
|||
NotificationTarget target = |
|||
new NotificationTarget() |
|||
.name("Test Target " + timestamp) |
|||
._configuration(targetConfig); |
|||
|
|||
NotificationTarget savedTarget = client.saveNotificationTarget(target); |
|||
assertNotNull(savedTarget); |
|||
assertNotNull(savedTarget.getId()); |
|||
assertEquals("Test Target " + timestamp, savedTarget.getName()); |
|||
|
|||
// Get target by ID
|
|||
NotificationTarget fetchedTarget = |
|||
client.getNotificationTargetById(savedTarget.getId().getId()); |
|||
assertEquals(savedTarget.getName(), fetchedTarget.getName()); |
|||
|
|||
// List targets
|
|||
PageDataNotificationTarget targetsPage = |
|||
client.getNotificationTargets(100, 0, null, null, null); |
|||
assertNotNull(targetsPage); |
|||
assertNotNull(targetsPage.getData()); |
|||
assertTrue( |
|||
targetsPage.getData().stream() |
|||
.anyMatch(t -> t.getName().equals(savedTarget.getName()))); |
|||
|
|||
// Update target
|
|||
savedTarget.setName("Updated Target " + timestamp); |
|||
NotificationTarget updatedTarget = client.saveNotificationTarget(savedTarget); |
|||
assertEquals("Updated Target " + timestamp, updatedTarget.getName()); |
|||
|
|||
// === 2. Notification Template CRUD ===
|
|||
|
|||
// Create template
|
|||
WebDeliveryMethodNotificationTemplate webTemplate = |
|||
new WebDeliveryMethodNotificationTemplate() |
|||
.subject("Test Subject") |
|||
.body("Test notification body") |
|||
.enabled(true); |
|||
NotificationTemplateConfig templateConfig = |
|||
new NotificationTemplateConfig() |
|||
.putDeliveryMethodsTemplatesItem("WEB", webTemplate); |
|||
NotificationTemplate template = |
|||
new NotificationTemplate() |
|||
.name("Test Template " + timestamp) |
|||
.notificationType(NotificationType.GENERAL) |
|||
._configuration(templateConfig); |
|||
|
|||
NotificationTemplate savedTemplate = client.saveNotificationTemplate(template); |
|||
assertNotNull(savedTemplate); |
|||
assertNotNull(savedTemplate.getId()); |
|||
assertEquals("Test Template " + timestamp, savedTemplate.getName()); |
|||
|
|||
// Get template by ID
|
|||
NotificationTemplate fetchedTemplate = |
|||
client.getNotificationTemplateById(savedTemplate.getId().getId()); |
|||
assertEquals(savedTemplate.getName(), fetchedTemplate.getName()); |
|||
assertEquals(NotificationType.GENERAL, fetchedTemplate.getNotificationType()); |
|||
|
|||
// List templates
|
|||
PageDataNotificationTemplate templatesPage = |
|||
client.getNotificationTemplates(100, 0, null, null, null, null); |
|||
assertNotNull(templatesPage); |
|||
assertTrue( |
|||
templatesPage.getData().stream() |
|||
.anyMatch(t -> t.getName().equals(savedTemplate.getName()))); |
|||
|
|||
// Update template
|
|||
savedTemplate.setName("Updated Template " + timestamp); |
|||
NotificationTemplate updatedTemplate = client.saveNotificationTemplate(savedTemplate); |
|||
assertEquals("Updated Template " + timestamp, updatedTemplate.getName()); |
|||
|
|||
// === 3. Send notification & read notifications ===
|
|||
|
|||
// Send notification request
|
|||
NotificationRequest request = |
|||
new NotificationRequest() |
|||
.targets(List.of(savedTarget.getId().getId())) |
|||
.templateId(savedTemplate.getId()); |
|||
NotificationRequest sentRequest = client.createNotificationRequest(request); |
|||
assertNotNull(sentRequest); |
|||
assertNotNull(sentRequest.getId()); |
|||
|
|||
// Get request by ID
|
|||
NotificationRequestInfo fetchedRequest = |
|||
client.getNotificationRequestById(sentRequest.getId().getId()); |
|||
assertNotNull(fetchedRequest); |
|||
|
|||
// List requests
|
|||
PageDataNotificationRequestInfo requestsPage = |
|||
client.getNotificationRequests(100, 0, null, null, null); |
|||
assertNotNull(requestsPage); |
|||
assertFalse(requestsPage.getData().isEmpty()); |
|||
|
|||
// Get notifications for current user
|
|||
PageDataNotification notificationsPage = |
|||
client.getNotifications(100, 0, null, null, null, null, null); |
|||
assertNotNull(notificationsPage); |
|||
assertFalse(notificationsPage.getData().isEmpty()); |
|||
|
|||
// Get unread count
|
|||
Integer unreadCount = client.getUnreadNotificationsCount("WEB"); |
|||
assertNotNull(unreadCount); |
|||
assertTrue("Expected at least one unread notification", unreadCount > 0); |
|||
|
|||
// Mark single notification as read
|
|||
client.markNotificationAsRead( |
|||
notificationsPage.getData().get(0).getId().getId()); |
|||
|
|||
// Mark all as read
|
|||
client.markAllNotificationsAsRead(null); |
|||
Integer unreadAfterMarkAll = client.getUnreadNotificationsCount(null); |
|||
assertEquals("Expected no unread notifications after marking all as read", 0, unreadAfterMarkAll.intValue()); |
|||
|
|||
// === 4. Notification Settings ===
|
|||
|
|||
NotificationSettings settings = client.getNotificationSettings(); |
|||
assertNotNull(settings); |
|||
|
|||
List<NotificationDeliveryMethod> deliveryMethods = client.getAvailableDeliveryMethods(); |
|||
assertNotNull(deliveryMethods); |
|||
assertTrue(deliveryMethods.contains(NotificationDeliveryMethod.WEB)); |
|||
|
|||
// === 5. Cleanup ===
|
|||
|
|||
// Delete notification request
|
|||
client.deleteNotificationRequest(sentRequest.getId().getId()); |
|||
assertReturns404(() -> client.getNotificationRequestById(sentRequest.getId().getId())); |
|||
|
|||
// Delete template
|
|||
client.deleteNotificationTemplateById(savedTemplate.getId().getId()); |
|||
assertReturns404(() -> client.getNotificationTemplateById(savedTemplate.getId().getId())); |
|||
|
|||
// Delete target
|
|||
client.deleteNotificationTargetById(savedTarget.getId().getId()); |
|||
assertReturns404(() -> client.getNotificationTargetById(savedTarget.getId().getId())); |
|||
} |
|||
|
|||
@Test |
|||
public void testNotificationRuleLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
|
|||
// Create a target for the rule recipients
|
|||
TenantAdministratorsFilter usersFilter = new TenantAdministratorsFilter(); |
|||
PlatformUsersNotificationTargetConfig targetConfig = |
|||
new PlatformUsersNotificationTargetConfig().usersFilter(usersFilter); |
|||
NotificationTarget target = |
|||
new NotificationTarget() |
|||
.name("Rule Test Target " + timestamp) |
|||
._configuration(targetConfig); |
|||
NotificationTarget savedTarget = client.saveNotificationTarget(target); |
|||
|
|||
// Create a template of type ENTITY_ACTION
|
|||
WebDeliveryMethodNotificationTemplate webTemplate = |
|||
new WebDeliveryMethodNotificationTemplate() |
|||
.subject("Entity action: ${entityType}") |
|||
.body("Entity ${entityName} was ${actionType}") |
|||
.enabled(true); |
|||
NotificationTemplateConfig templateConfig = |
|||
new NotificationTemplateConfig() |
|||
.putDeliveryMethodsTemplatesItem("WEB", webTemplate); |
|||
NotificationTemplate template = |
|||
new NotificationTemplate() |
|||
.name("Rule Test Template " + timestamp) |
|||
.notificationType(NotificationType.ENTITY_ACTION) |
|||
._configuration(templateConfig); |
|||
NotificationTemplate savedTemplate = client.saveNotificationTemplate(template); |
|||
|
|||
// Build trigger config: fire on DEVICE create/update
|
|||
EntityActionNotificationRuleTriggerConfig triggerConfig = |
|||
new EntityActionNotificationRuleTriggerConfig() |
|||
.addEntityTypesItem(EntityType.DEVICE) |
|||
.created(true) |
|||
.updated(true) |
|||
.deleted(false); |
|||
|
|||
// Build recipients config
|
|||
EntityActionRecipientsConfig recipientsConfig = new EntityActionRecipientsConfig() |
|||
.addTargetsItem(savedTarget.getId().getId()); |
|||
|
|||
// saveNotificationRule - create
|
|||
NotificationRule rule = new NotificationRule() |
|||
.name("Test Rule " + timestamp) |
|||
.enabled(true) |
|||
.templateId(savedTemplate.getId()) |
|||
.triggerType(NotificationRuleTriggerType.ENTITY_ACTION) |
|||
.triggerConfig(triggerConfig) |
|||
.recipientsConfig(recipientsConfig); |
|||
|
|||
NotificationRule savedRule = client.saveNotificationRule(rule); |
|||
assertNotNull(savedRule); |
|||
assertNotNull(savedRule.getId()); |
|||
assertEquals("Test Rule " + timestamp, savedRule.getName()); |
|||
assertEquals(NotificationRuleTriggerType.ENTITY_ACTION, savedRule.getTriggerType()); |
|||
assertEquals(Boolean.TRUE, savedRule.getEnabled()); |
|||
|
|||
// getNotificationRuleById
|
|||
NotificationRuleInfo fetchedRule = client.getNotificationRuleById(savedRule.getId().getId()); |
|||
assertNotNull(fetchedRule); |
|||
assertEquals(savedRule.getName(), fetchedRule.getName()); |
|||
assertEquals(NotificationRuleTriggerType.ENTITY_ACTION, fetchedRule.getTriggerType()); |
|||
|
|||
// getNotificationRules - verify it appears in the list
|
|||
PageDataNotificationRuleInfo rulesPage = client.getNotificationRules(100, 0, null, null, null); |
|||
assertNotNull(rulesPage); |
|||
assertTrue(rulesPage.getData().stream() |
|||
.anyMatch(r -> r.getId().getId().equals(savedRule.getId().getId()))); |
|||
|
|||
// deleteNotificationRule
|
|||
client.deleteNotificationRule(savedRule.getId().getId()); |
|||
assertReturns404(() -> client.getNotificationRuleById(savedRule.getId().getId())); |
|||
|
|||
// Cleanup
|
|||
client.deleteNotificationTemplateById(savedTemplate.getId().getId()); |
|||
client.deleteNotificationTargetById(savedTarget.getId().getId()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,138 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.MapperType; |
|||
import org.thingsboard.client.model.OAuth2BasicMapperConfig; |
|||
import org.thingsboard.client.model.OAuth2Client; |
|||
import org.thingsboard.client.model.OAuth2ClientInfo; |
|||
import org.thingsboard.client.model.OAuth2MapperConfig; |
|||
import org.thingsboard.client.model.PageDataOAuth2ClientInfo; |
|||
import org.thingsboard.client.model.PlatformType; |
|||
import org.thingsboard.client.model.TenantNameStrategyType; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
|
|||
@DaoSqlTest |
|||
public class Oauth2ApiClientTest extends AbstractApiClientTest { |
|||
|
|||
private OAuth2Client createOAuth2Client(String title, String clientId, String clientSecret) { |
|||
OAuth2BasicMapperConfig basicConfig = new OAuth2BasicMapperConfig(); |
|||
basicConfig.setEmailAttributeKey("email"); |
|||
basicConfig.setFirstNameAttributeKey("given_name"); |
|||
basicConfig.setLastNameAttributeKey("family_name"); |
|||
basicConfig.setTenantNameStrategy(TenantNameStrategyType.DOMAIN); |
|||
|
|||
OAuth2MapperConfig mapperConfig = new OAuth2MapperConfig(); |
|||
mapperConfig.setType(MapperType.BASIC); |
|||
mapperConfig.setAllowUserCreation(true); |
|||
mapperConfig.setActivateUser(false); |
|||
mapperConfig.setBasic(basicConfig); |
|||
|
|||
OAuth2Client oAuth2Client = new OAuth2Client(); |
|||
oAuth2Client.setTitle(title); |
|||
oAuth2Client.setClientId(clientId); |
|||
oAuth2Client.setClientSecret(clientSecret); |
|||
oAuth2Client.setAuthorizationUri("https://accounts.google.com/o/oauth2/v2/auth"); |
|||
oAuth2Client.setAccessTokenUri("https://oauth2.googleapis.com/token"); |
|||
oAuth2Client.setScope(List.of("openid", "email", "profile")); |
|||
oAuth2Client.setUserInfoUri("https://openidconnect.googleapis.com/v1/userinfo"); |
|||
oAuth2Client.setUserNameAttributeName("email"); |
|||
oAuth2Client.setClientAuthenticationMethod("POST"); |
|||
oAuth2Client.setLoginButtonLabel(title); |
|||
oAuth2Client.setMapperConfig(mapperConfig); |
|||
oAuth2Client.setPlatforms(List.of(PlatformType.WEB)); |
|||
|
|||
return oAuth2Client; |
|||
} |
|||
|
|||
@Test |
|||
public void testOAuth2ClientLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<OAuth2Client> createdClients = new ArrayList<>(); |
|||
|
|||
// create 5 OAuth2 clients
|
|||
for (int i = 0; i < 5; i++) { |
|||
String title = TEST_PREFIX + "OAuth2_" + timestamp + "_" + i; |
|||
OAuth2Client oAuth2Client = createOAuth2Client(title, |
|||
"client_id_" + timestamp + "_" + i, |
|||
"client_secret_" + timestamp + "_" + i); |
|||
|
|||
OAuth2Client created = client.saveOAuth2Client(oAuth2Client); |
|||
assertNotNull(created); |
|||
assertNotNull(created.getId()); |
|||
assertEquals(title, created.getTitle()); |
|||
assertEquals("POST", created.getClientAuthenticationMethod()); |
|||
assertNotNull(created.getMapperConfig()); |
|||
assertEquals(MapperType.BASIC, created.getMapperConfig().getType()); |
|||
|
|||
createdClients.add(created); |
|||
} |
|||
|
|||
// list tenant OAuth2 client infos
|
|||
PageDataOAuth2ClientInfo clientInfos = client.findTenantOAuth2ClientInfos(100, 0, |
|||
TEST_PREFIX + "OAuth2_" + timestamp, null, null); |
|||
assertNotNull(clientInfos); |
|||
assertEquals(5, clientInfos.getData().size()); |
|||
|
|||
// get OAuth2 client by id
|
|||
OAuth2Client searchClient = createdClients.get(2); |
|||
OAuth2Client fetchedClient = client.getOAuth2ClientById(searchClient.getId().getId()); |
|||
assertEquals(searchClient.getTitle(), fetchedClient.getTitle()); |
|||
assertEquals(searchClient.getClientId(), fetchedClient.getClientId()); |
|||
assertEquals(searchClient.getAuthorizationUri(), fetchedClient.getAuthorizationUri()); |
|||
assertEquals(3, fetchedClient.getScope().size()); |
|||
|
|||
// fetch client infos by ids
|
|||
List<String> idsToFetch = List.of( |
|||
createdClients.get(0).getId().getId().toString(), |
|||
createdClients.get(1).getId().getId().toString() |
|||
); |
|||
List<OAuth2ClientInfo> fetchedInfos = client.findTenantOAuth2ClientInfosByIds(idsToFetch); |
|||
assertEquals(2, fetchedInfos.size()); |
|||
|
|||
// update OAuth2 client
|
|||
OAuth2Client clientToUpdate = client.getOAuth2ClientById(createdClients.get(3).getId().getId()); |
|||
clientToUpdate.setTitle(clientToUpdate.getTitle() + "_updated"); |
|||
clientToUpdate.setLoginButtonLabel("Updated Login"); |
|||
clientToUpdate.setPlatforms(List.of(PlatformType.WEB, PlatformType.ANDROID)); |
|||
OAuth2Client updatedClient = client.saveOAuth2Client(clientToUpdate); |
|||
assertEquals(clientToUpdate.getTitle(), updatedClient.getTitle()); |
|||
assertEquals("Updated Login", updatedClient.getLoginButtonLabel()); |
|||
assertEquals(2, updatedClient.getPlatforms().size()); |
|||
|
|||
// delete OAuth2 client
|
|||
UUID clientToDeleteId = createdClients.get(0).getId().getId(); |
|||
client.deleteOauth2Client(clientToDeleteId); |
|||
|
|||
// verify deletion
|
|||
assertReturns404(() -> |
|||
client.getOAuth2ClientById(clientToDeleteId) |
|||
); |
|||
|
|||
PageDataOAuth2ClientInfo clientsAfterDelete = client.findTenantOAuth2ClientInfos(100, 0, |
|||
TEST_PREFIX + "OAuth2_" + timestamp, null, null); |
|||
assertEquals(4, clientsAfterDelete.getData().size()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,261 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.ChecksumAlgorithm; |
|||
import org.thingsboard.client.model.DeviceProfileId; |
|||
import org.thingsboard.client.model.DeviceProfileInfo; |
|||
import org.thingsboard.client.model.OtaPackage; |
|||
import org.thingsboard.client.model.OtaPackageInfo; |
|||
import org.thingsboard.client.model.OtaPackageType; |
|||
import org.thingsboard.client.model.PageDataOtaPackageInfo; |
|||
import org.thingsboard.client.model.SaveOtaPackageInfoRequest; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.io.File; |
|||
import java.io.FileWriter; |
|||
import java.nio.file.Files; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertFalse; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class OtaPackageApiClientTest extends AbstractApiClientTest { |
|||
|
|||
private static final String OTA_PREFIX = "OtaTest_"; |
|||
|
|||
private DeviceProfileId getDefaultDeviceProfileId() throws Exception { |
|||
DeviceProfileInfo profileInfo = client.getDefaultDeviceProfileInfo(); |
|||
return (DeviceProfileId) profileInfo.getId(); |
|||
} |
|||
|
|||
private SaveOtaPackageInfoRequest buildOtaPackageInfoRequest( |
|||
String title, String version, OtaPackageType type, |
|||
DeviceProfileId deviceProfileId, boolean usesUrl, String url) { |
|||
SaveOtaPackageInfoRequest request = new SaveOtaPackageInfoRequest(); |
|||
request.setTitle(title); |
|||
request.setType(type); |
|||
request.setUrl(url); |
|||
request.setVersion(version); |
|||
request.setDeviceProfileId(deviceProfileId); |
|||
return request; |
|||
} |
|||
|
|||
private OtaPackageInfo createFirmwareInfo(String suffix) throws Exception { |
|||
DeviceProfileId profileId = getDefaultDeviceProfileId(); |
|||
SaveOtaPackageInfoRequest request = buildOtaPackageInfoRequest( |
|||
OTA_PREFIX + suffix, "1.0." + System.currentTimeMillis(), |
|||
OtaPackageType.FIRMWARE, profileId, false, null); |
|||
return client.saveOtaPackageInfo(request); |
|||
} |
|||
|
|||
private OtaPackageInfo createFirmwareWithUrl(String suffix) throws Exception { |
|||
DeviceProfileId profileId = getDefaultDeviceProfileId(); |
|||
SaveOtaPackageInfoRequest request = buildOtaPackageInfoRequest( |
|||
OTA_PREFIX + suffix, "1.0." + System.currentTimeMillis(), |
|||
OtaPackageType.FIRMWARE, profileId, true, "https://example.com/firmware.bin"); |
|||
return client.saveOtaPackageInfo(request); |
|||
} |
|||
|
|||
@Test |
|||
public void testSaveAndGetOtaPackageInfo() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
DeviceProfileId profileId = getDefaultDeviceProfileId(); |
|||
String title = OTA_PREFIX + "save_" + ts; |
|||
String version = "1.0." + ts; |
|||
|
|||
SaveOtaPackageInfoRequest request = buildOtaPackageInfoRequest( |
|||
title, version, OtaPackageType.FIRMWARE, profileId, true, "https://example.com/fw.bin"); |
|||
|
|||
OtaPackageInfo saved = client.saveOtaPackageInfo(request); |
|||
assertNotNull(saved); |
|||
assertNotNull(saved.getId()); |
|||
assertEquals(title, saved.getTitle()); |
|||
assertEquals(version, saved.getVersion()); |
|||
assertEquals(OtaPackageType.FIRMWARE, saved.getType()); |
|||
assertTrue(saved.getUrl().contains("example.com")); |
|||
|
|||
// get info by id
|
|||
String pkgId = saved.getId().getId().toString(); |
|||
OtaPackageInfo fetched = client.getOtaPackageInfoById(pkgId); |
|||
assertNotNull(fetched); |
|||
assertEquals(title, fetched.getTitle()); |
|||
assertEquals(version, fetched.getVersion()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetOtaPackageById() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
OtaPackageInfo saved = createFirmwareWithUrl("getbyid_" + ts); |
|||
|
|||
OtaPackage fullPkg = client.getOtaPackageById(saved.getId().getId().toString()); |
|||
assertNotNull(fullPkg); |
|||
assertEquals(saved.getTitle(), fullPkg.getTitle()); |
|||
assertEquals(saved.getVersion(), fullPkg.getVersion()); |
|||
} |
|||
|
|||
@Test |
|||
public void testSaveOtaPackageInfoForSoftware() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
DeviceProfileId profileId = getDefaultDeviceProfileId(); |
|||
String title = OTA_PREFIX + "sw_" + ts; |
|||
|
|||
SaveOtaPackageInfoRequest request = buildOtaPackageInfoRequest( |
|||
title, "2.0." + ts, OtaPackageType.SOFTWARE, profileId, true, "https://example.com/sw.bin"); |
|||
|
|||
OtaPackageInfo saved = client.saveOtaPackageInfo(request); |
|||
assertNotNull(saved); |
|||
assertEquals(OtaPackageType.SOFTWARE, saved.getType()); |
|||
assertEquals(title, saved.getTitle()); |
|||
} |
|||
|
|||
@Test |
|||
public void testSaveOtaPackageData() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
OtaPackageInfo info = createFirmwareInfo("data_" + ts); |
|||
|
|||
File tempFile = Files.createTempFile("ota_test_", ".bin").toFile(); |
|||
tempFile.deleteOnExit(); |
|||
try (FileWriter writer = new FileWriter(tempFile)) { |
|||
writer.write("test firmware content " + ts); |
|||
} |
|||
|
|||
OtaPackageInfo updated = client.saveOtaPackageData( |
|||
info.getId().getId().toString(), "MD5", tempFile, null); |
|||
assertNotNull(updated); |
|||
assertTrue(updated.getHasData()); |
|||
assertNotNull(updated.getFileName()); |
|||
assertNotNull(updated.getDataSize()); |
|||
assertTrue(updated.getDataSize() > 0); |
|||
assertEquals(ChecksumAlgorithm.MD5, updated.getChecksumAlgorithm()); |
|||
} |
|||
|
|||
@Test |
|||
public void testDownloadOtaPackage() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
OtaPackageInfo info = createFirmwareInfo("download_" + ts); |
|||
|
|||
String content = "downloadable firmware " + ts; |
|||
File tempFile = Files.createTempFile("ota_dl_", ".bin").toFile(); |
|||
tempFile.deleteOnExit(); |
|||
try (FileWriter writer = new FileWriter(tempFile)) { |
|||
writer.write(content); |
|||
} |
|||
|
|||
client.saveOtaPackageData(info.getId().getId().toString(), "MD5", tempFile, null); |
|||
|
|||
File downloaded = client.downloadOtaPackage(info.getId().getId().toString()); |
|||
assertNotNull(downloaded); |
|||
assertTrue(downloaded.length() > 0); |
|||
String downloadedContent = Files.readString(downloaded.toPath()); |
|||
assertEquals(content, downloadedContent); |
|||
} |
|||
|
|||
@Test |
|||
public void testDeleteOtaPackage() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
OtaPackageInfo saved = createFirmwareWithUrl("delete_" + ts); |
|||
|
|||
String pkgId = saved.getId().getId().toString(); |
|||
client.getOtaPackageInfoById(pkgId); |
|||
|
|||
client.deleteOtaPackage(pkgId); |
|||
|
|||
assertReturns404(() -> client.getOtaPackageInfoById(pkgId)); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetOtaPackages() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
|
|||
for (int i = 0; i < 3; i++) { |
|||
createFirmwareWithUrl("list_" + ts + "_" + i); |
|||
} |
|||
|
|||
PageDataOtaPackageInfo page = client.getOtaPackages(100, 0, OTA_PREFIX + "list_" + ts, null, null); |
|||
assertNotNull(page); |
|||
assertEquals(3, page.getTotalElements().intValue()); |
|||
for (OtaPackageInfo pkg : page.getData()) { |
|||
assertTrue(pkg.getTitle().startsWith(OTA_PREFIX + "list_" + ts)); |
|||
} |
|||
} |
|||
|
|||
@Test |
|||
public void testGetOtaPackagesByDeviceProfileAndType() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
DeviceProfileId profileId = getDefaultDeviceProfileId(); |
|||
|
|||
createFirmwareWithUrl("byprofile_" + ts + "_0"); |
|||
createFirmwareWithUrl("byprofile_" + ts + "_1"); |
|||
|
|||
PageDataOtaPackageInfo page = client.getOtaPackagesByDeviceProfileAndType( |
|||
profileId.getId().toString(), "FIRMWARE", 100, 0, |
|||
OTA_PREFIX + "byprofile_" + ts, null, null); |
|||
assertNotNull(page); |
|||
assertEquals(2, page.getTotalElements().intValue()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetOtaPackageInfoById_notFound() { |
|||
String nonExistentId = UUID.randomUUID().toString(); |
|||
assertReturns404(() -> client.getOtaPackageInfoById(nonExistentId)); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetOtaPackagesPagination() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
|
|||
for (int i = 0; i < 5; i++) { |
|||
createFirmwareWithUrl("paged_" + ts + "_" + i); |
|||
} |
|||
|
|||
PageDataOtaPackageInfo page1 = client.getOtaPackages(2, 0, OTA_PREFIX + "paged_" + ts, null, null); |
|||
assertNotNull(page1); |
|||
assertEquals(5, page1.getTotalElements().intValue()); |
|||
assertEquals(3, page1.getTotalPages().intValue()); |
|||
assertEquals(2, page1.getData().size()); |
|||
assertTrue(page1.getHasNext()); |
|||
|
|||
PageDataOtaPackageInfo lastPage = client.getOtaPackages(2, 2, OTA_PREFIX + "paged_" + ts, null, null); |
|||
assertEquals(1, lastPage.getData().size()); |
|||
assertFalse(lastPage.getHasNext()); |
|||
} |
|||
|
|||
@Test |
|||
public void testUpdateOtaPackageInfo() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
OtaPackageInfo saved = createFirmwareWithUrl("update_" + ts); |
|||
|
|||
SaveOtaPackageInfoRequest updateReq = new SaveOtaPackageInfoRequest(); |
|||
updateReq.setId(saved.getId()); |
|||
updateReq.setTitle(saved.getTitle()); |
|||
updateReq.setType(saved.getType()); |
|||
updateReq.setVersion(saved.getVersion()); |
|||
updateReq.setDeviceProfileId(saved.getDeviceProfileId()); |
|||
updateReq.setUrl(saved.getUrl()); |
|||
updateReq.setAdditionalInfo(OBJECT_MAPPER.createObjectNode().put("infoKey", "infoValue")); |
|||
|
|||
OtaPackageInfo updated = client.saveOtaPackageInfo(updateReq); |
|||
assertNotNull(updated); |
|||
assertEquals(saved.getId().getId(), updated.getId().getId()); |
|||
assertEquals("infoValue", updated.getAdditionalInfo().get("infoKey").asText()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.ApiException; |
|||
import org.thingsboard.client.model.Device; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
|
|||
@DaoSqlTest |
|||
public class RpcV1ApiClientTest extends AbstractApiClientTest { |
|||
|
|||
private static final String ONE_WAY_BODY = |
|||
"{\"method\":\"setGpio\",\"params\":{\"pin\":7,\"value\":1},\"persistent\":true}"; |
|||
private static final String TWO_WAY_BODY = |
|||
"{\"method\":\"getGpio\",\"params\":{\"pin\":7},\"persistent\":true}"; |
|||
|
|||
@Test |
|||
public void testHandleOneWayDeviceRPCRequest() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createNewDevice(TEST_PREFIX + ts); |
|||
String deviceId = device.getId().getId().toString(); |
|||
|
|||
try { |
|||
client.handleOneWayDeviceRPCRequestV1(deviceId, ONE_WAY_BODY); |
|||
} catch (ApiException e) { |
|||
assertEquals("handleOneWayDeviceRPCRequest got an unexpected HTTP error: " + e.getCode(), |
|||
0, e.getCode()); |
|||
} |
|||
|
|||
client.deleteDevice(deviceId); |
|||
} |
|||
|
|||
@Test |
|||
public void testHandleTwoWayDeviceRPCRequest() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createNewDevice(TEST_PREFIX + ts); |
|||
String deviceId = device.getId().getId().toString(); |
|||
|
|||
try { |
|||
client.handleTwoWayDeviceRPCRequestV1(deviceId, TWO_WAY_BODY); |
|||
} catch (ApiException e) { |
|||
assertEquals("handleTwoWayDeviceRPCRequest got an unexpected HTTP error: " + e.getCode(), |
|||
0, e.getCode()); |
|||
} |
|||
|
|||
client.deleteDevice(deviceId); |
|||
} |
|||
|
|||
private Device createNewDevice(String name) throws ApiException { |
|||
Device device = new Device(); |
|||
device.setName(name); |
|||
device.setType("default"); |
|||
return client.saveDevice(device, null, null, null, null); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.ApiException; |
|||
import org.thingsboard.client.model.Device; |
|||
import org.thingsboard.client.model.Rpc; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.io.IOException; |
|||
import java.net.URI; |
|||
import java.net.http.HttpClient; |
|||
import java.net.http.HttpRequest; |
|||
import java.net.http.HttpResponse; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
|
|||
@DaoSqlTest |
|||
public class RpcV2ApiClientTest extends AbstractApiClientTest { |
|||
|
|||
private static final String PERSISTENT_BODY = |
|||
"{\"method\":\"setGpio\",\"params\":{\"pin\":7,\"value\":1},\"persistent\":true}"; |
|||
|
|||
@Test |
|||
public void testHandleOneWayDeviceRPCRequest() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createNewDevice(TEST_PREFIX + ts); |
|||
String deviceId = device.getId().getId().toString(); |
|||
|
|||
try { |
|||
client.handleOneWayDeviceRPCRequestV2(deviceId, PERSISTENT_BODY); |
|||
} catch (ApiException e) { |
|||
assertEquals("handleOneWayDeviceRPCRequest1 got an unexpected HTTP error: " + e.getCode(), |
|||
0, e.getCode()); |
|||
} |
|||
|
|||
client.deleteDevice(deviceId); |
|||
} |
|||
|
|||
@Test |
|||
public void testHandleTwoWayDeviceRPCRequest() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createNewDevice(TEST_PREFIX + ts); |
|||
String deviceId = device.getId().getId().toString(); |
|||
|
|||
try { |
|||
client.handleTwoWayDeviceRPCRequestV2(deviceId, PERSISTENT_BODY); |
|||
} catch (ApiException e) { |
|||
assertEquals("handleTwoWayDeviceRPCRequest1 got an unexpected HTTP error: " + e.getCode(), |
|||
0, e.getCode()); |
|||
} |
|||
|
|||
client.deleteDevice(deviceId); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetPersistedRpcAndDeleteRpc() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createNewDevice(TEST_PREFIX + ts); |
|||
String deviceId = device.getId().getId().toString(); |
|||
|
|||
String rpcId = postPersistentRpcAndGetId(deviceId); |
|||
assertNotNull(rpcId); |
|||
|
|||
Rpc rpc = client.getPersistedRpc(rpcId); |
|||
assertNotNull(rpc); |
|||
assertNotNull(rpc.getId()); |
|||
|
|||
client.deleteRpc(rpcId); |
|||
|
|||
assertReturns404(() -> client.getPersistedRpc(rpcId)); |
|||
|
|||
client.deleteDevice(deviceId); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetPersistedRpcNotFound() { |
|||
assertReturns404(() -> client.getPersistedRpc(UUID.randomUUID().toString())); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetPersistedRpcByDevice() throws Exception { |
|||
long ts = System.currentTimeMillis(); |
|||
Device device = createNewDevice(TEST_PREFIX + ts); |
|||
String deviceId = device.getId().getId().toString(); |
|||
|
|||
postPersistentRpcAndGetId(deviceId); |
|||
|
|||
try { |
|||
client.getPersistedRpcByDevice(deviceId, 100, 0, null, null, null, null); |
|||
} catch (ApiException e) { |
|||
assertEquals("getPersistedRpcByDevice got an unexpected HTTP error: " + e.getCode(), |
|||
0, e.getCode()); |
|||
} |
|||
|
|||
client.deleteDevice(deviceId); |
|||
} |
|||
|
|||
private Device createNewDevice(String name) throws ApiException { |
|||
Device device = new Device(); |
|||
device.setName(name); |
|||
device.setType("default"); |
|||
return client.saveDevice(device, null, null, null, null); |
|||
} |
|||
|
|||
private String postPersistentRpcAndGetId(String deviceId) throws IOException, InterruptedException { |
|||
HttpRequest request = HttpRequest.newBuilder() |
|||
.uri(URI.create(getBaseUrl() + "/api/plugins/rpc/oneway/" + deviceId)) |
|||
.header("Content-Type", "application/json") |
|||
.header("Authorization", "Bearer " + client.getToken()) |
|||
.POST(HttpRequest.BodyPublishers.ofString(PERSISTENT_BODY)) |
|||
.build(); |
|||
HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); |
|||
return OBJECT_MAPPER.readTree(response.body()).get("rpcId").asText(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,163 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.NodeConnectionInfo; |
|||
import org.thingsboard.client.model.PageDataRuleChain; |
|||
import org.thingsboard.client.model.RuleChain; |
|||
import org.thingsboard.client.model.RuleChainMetaData; |
|||
import org.thingsboard.client.model.RuleChainType; |
|||
import org.thingsboard.client.model.RuleNode; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class RuleChainApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testRuleChainAndNodeLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<RuleChain> createdChains = new ArrayList<>(); |
|||
|
|||
// create 5 rule chains
|
|||
for (int i = 0; i < 5; i++) { |
|||
RuleChain ruleChain = new RuleChain(); |
|||
ruleChain.setName(TEST_PREFIX + "RuleChain_" + timestamp + "_" + i); |
|||
ruleChain.setType(RuleChainType.CORE); |
|||
ruleChain.setDebugMode(false); |
|||
|
|||
RuleChain created = client.saveRuleChain(ruleChain); |
|||
assertNotNull(created); |
|||
assertNotNull(created.getId()); |
|||
assertEquals(ruleChain.getName(), created.getName()); |
|||
assertEquals(RuleChainType.CORE, created.getType()); |
|||
|
|||
createdChains.add(created); |
|||
} |
|||
|
|||
// list rule chains with text search
|
|||
PageDataRuleChain filteredChains = client.getRuleChains(100, 0, null, |
|||
TEST_PREFIX + "RuleChain_" + timestamp, null, null); |
|||
assertNotNull(filteredChains); |
|||
assertEquals(5, filteredChains.getData().size()); |
|||
|
|||
// get rule chain by id
|
|||
RuleChain searchChain = createdChains.get(2); |
|||
RuleChain fetchedChain = client.getRuleChainById(searchChain.getId().getId().toString()); |
|||
assertEquals(searchChain.getName(), fetchedChain.getName()); |
|||
assertEquals(searchChain.getType(), fetchedChain.getType()); |
|||
|
|||
// get metadata (initially has default node)
|
|||
RuleChainMetaData metadata = client.getRuleChainMetaData(searchChain.getId().getId().toString()); |
|||
assertNotNull(metadata); |
|||
assertEquals(searchChain.getId().getId(), metadata.getRuleChainId().getId()); |
|||
|
|||
// save metadata with rule nodes and connections
|
|||
RuleChainMetaData newMetadata = new RuleChainMetaData(metadata.getRuleChainId()); |
|||
newMetadata.setVersion(metadata.getVersion()); |
|||
newMetadata.setFirstNodeIndex(0); |
|||
|
|||
// node 0: message type switch
|
|||
RuleNode switchNode = new RuleNode(); |
|||
switchNode.setName("Message Type Switch"); |
|||
switchNode.setType("org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode"); |
|||
switchNode.setConfiguration(OBJECT_MAPPER.createObjectNode().put("version", 0)); |
|||
switchNode.setAdditionalInfo(OBJECT_MAPPER.createObjectNode().put("layoutX", 200).put("layoutY", 150)); |
|||
|
|||
// node 1: log node for telemetry
|
|||
RuleNode logNode = new RuleNode(); |
|||
logNode.setName("Log Telemetry"); |
|||
logNode.setType("org.thingsboard.rule.engine.action.TbLogNode"); |
|||
logNode.setConfiguration(OBJECT_MAPPER.createObjectNode() |
|||
.put("scriptLang", "TBEL") |
|||
.put("jsScript", "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);") |
|||
.put("tbelScript", "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);")); |
|||
logNode.setAdditionalInfo(OBJECT_MAPPER.createObjectNode().put("layoutX", 500).put("layoutY", 100)); |
|||
|
|||
// node 2: save timeseries
|
|||
RuleNode saveNode = new RuleNode(); |
|||
saveNode.setName("Save Timeseries"); |
|||
saveNode.setType("org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode"); |
|||
saveNode.setConfiguration(OBJECT_MAPPER.createObjectNode() |
|||
.put("defaultTTL", 0) |
|||
.put("skipLatestPersistence", false) |
|||
.put("useServerTs", false)); |
|||
saveNode.setAdditionalInfo(OBJECT_MAPPER.createObjectNode().put("layoutX", 500).put("layoutY", 250)); |
|||
|
|||
newMetadata.setNodes(List.of(switchNode, logNode, saveNode)); |
|||
|
|||
// connection: switch -> log (on "Post telemetry")
|
|||
NodeConnectionInfo conn1 = new NodeConnectionInfo(); |
|||
conn1.setFromIndex(0); |
|||
conn1.setToIndex(1); |
|||
conn1.setType("Post telemetry"); |
|||
|
|||
// connection: switch -> save timeseries (on "Post telemetry")
|
|||
NodeConnectionInfo conn2 = new NodeConnectionInfo(); |
|||
conn2.setFromIndex(0); |
|||
conn2.setToIndex(2); |
|||
conn2.setType("Post telemetry"); |
|||
|
|||
newMetadata.setConnections(List.of(conn1, conn2)); |
|||
newMetadata.setRuleChainConnections(List.of()); |
|||
|
|||
RuleChainMetaData savedMetadata = client.saveRuleChainMetaData(newMetadata, false); |
|||
assertNotNull(savedMetadata); |
|||
assertEquals(3, savedMetadata.getNodes().size()); |
|||
assertEquals(2, savedMetadata.getConnections().size()); |
|||
|
|||
// verify saved nodes
|
|||
RuleChainMetaData fetchedMetadata = client.getRuleChainMetaData(searchChain.getId().getId().toString()); |
|||
assertEquals(3, fetchedMetadata.getNodes().size()); |
|||
assertTrue(fetchedMetadata.getNodes().stream() |
|||
.anyMatch(node -> "Log Telemetry".equals(node.getName()))); |
|||
assertTrue(fetchedMetadata.getNodes().stream() |
|||
.anyMatch(node -> "Save Timeseries".equals(node.getName()))); |
|||
|
|||
// get output labels
|
|||
client.getRuleChainOutputLabels(searchChain.getId().getId().toString()); |
|||
|
|||
// update rule chain
|
|||
RuleChain chainToUpdate = createdChains.get(3); |
|||
chainToUpdate.setName(chainToUpdate.getName() + "_updated"); |
|||
chainToUpdate.setDebugMode(true); |
|||
RuleChain updatedChain = client.saveRuleChain(chainToUpdate); |
|||
assertEquals(chainToUpdate.getName(), updatedChain.getName()); |
|||
assertEquals(true, updatedChain.getDebugMode()); |
|||
|
|||
// delete rule chain
|
|||
UUID chainToDeleteId = createdChains.get(0).getId().getId(); |
|||
client.deleteRuleChain(chainToDeleteId.toString()); |
|||
|
|||
// verify deletion
|
|||
assertReturns404(() -> |
|||
client.getRuleChainById(chainToDeleteId.toString()) |
|||
); |
|||
|
|||
PageDataRuleChain chainsAfterDelete = client.getRuleChains(100, 0, null, |
|||
TEST_PREFIX + "RuleChain_" + timestamp, null, null); |
|||
assertEquals(4, chainsAfterDelete.getData().size()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,151 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.PageDataTbResourceInfo; |
|||
import org.thingsboard.client.model.ResourceExportData; |
|||
import org.thingsboard.client.model.TbImageDeleteResult; |
|||
import org.thingsboard.client.model.TbResourceInfo; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import javax.imageio.ImageIO; |
|||
import java.awt.Color; |
|||
import java.awt.Graphics2D; |
|||
import java.awt.image.BufferedImage; |
|||
import java.io.File; |
|||
import java.io.IOException; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class TbImageApiClientTest extends AbstractApiClientTest { |
|||
|
|||
private File createTempImage(String name, Color color) throws IOException { |
|||
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); |
|||
Graphics2D g = img.createGraphics(); |
|||
g.setColor(color); |
|||
g.fillRect(0, 0, 100, 100); |
|||
g.dispose(); |
|||
|
|||
File tempFile = File.createTempFile(name, ".png"); |
|||
tempFile.deleteOnExit(); |
|||
ImageIO.write(img, "png", tempFile); |
|||
return tempFile; |
|||
} |
|||
|
|||
@Test |
|||
public void testImageLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<TbResourceInfo> createdImages = new ArrayList<>(); |
|||
Color[] colors = {Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW, Color.CYAN}; |
|||
|
|||
// upload 5 images
|
|||
for (int i = 0; i < 5; i++) { |
|||
String title = TEST_PREFIX + "Image_" + timestamp + "_" + i; |
|||
File imageFile = createTempImage("test_image_" + i, colors[i]); |
|||
|
|||
TbResourceInfo uploaded = client.uploadImage(imageFile, title, null); |
|||
assertNotNull(uploaded); |
|||
assertNotNull(uploaded.getResourceKey()); |
|||
assertEquals(title, uploaded.getTitle()); |
|||
assertNotNull(uploaded.getLink()); |
|||
|
|||
createdImages.add(uploaded); |
|||
} |
|||
|
|||
// list images with text search
|
|||
PageDataTbResourceInfo filteredImages = client.getImages(100, 0, null, false, |
|||
TEST_PREFIX + "Image_" + timestamp, null, null); |
|||
assertNotNull(filteredImages); |
|||
assertEquals(5, filteredImages.getData().size()); |
|||
|
|||
// get image info by type and key
|
|||
TbResourceInfo searchImage = createdImages.get(2); |
|||
TbResourceInfo fetchedInfo = client.getImageInfo("tenant", searchImage.getResourceKey()); |
|||
assertEquals(searchImage.getTitle(), fetchedInfo.getTitle()); |
|||
assertEquals(searchImage.getResourceKey(), fetchedInfo.getResourceKey()); |
|||
|
|||
// download image
|
|||
File downloadedImage = client.downloadImage("tenant", searchImage.getResourceKey(), null, null); |
|||
assertNotNull(downloadedImage); |
|||
assertTrue(downloadedImage.exists()); |
|||
assertTrue(downloadedImage.length() > 0); |
|||
|
|||
// download image preview
|
|||
File preview = client.downloadImagePreview("tenant", searchImage.getResourceKey(), null, null); |
|||
assertNotNull(preview); |
|||
assertTrue(preview.exists()); |
|||
assertTrue(preview.length() > 0); |
|||
|
|||
// update image file
|
|||
File updatedImageFile = createTempImage("updated_image", Color.MAGENTA); |
|||
TbResourceInfo updatedImage = client.updateImage("tenant", searchImage.getResourceKey(), updatedImageFile); |
|||
assertNotNull(updatedImage); |
|||
assertEquals(searchImage.getResourceKey(), updatedImage.getResourceKey()); |
|||
|
|||
// update image info (title)
|
|||
TbResourceInfo infoToUpdate = client.getImageInfo("tenant", createdImages.get(3).getResourceKey()); |
|||
infoToUpdate.setTitle(infoToUpdate.getTitle() + "_updated"); |
|||
TbResourceInfo updatedInfo = client.updateImageInfo("tenant", infoToUpdate.getResourceKey(), infoToUpdate); |
|||
assertEquals(infoToUpdate.getTitle(), updatedInfo.getTitle()); |
|||
|
|||
// make image public
|
|||
TbResourceInfo publicImage = client.updateImagePublicStatus("tenant", |
|||
createdImages.get(1).getResourceKey(), true); |
|||
assertTrue(publicImage.getPublic()); |
|||
assertNotNull(publicImage.getPublicResourceKey()); |
|||
assertNotNull(publicImage.getPublicLink()); |
|||
|
|||
// download public image
|
|||
File publicDownload = client.downloadPublicImage(publicImage.getPublicResourceKey(), null, null); |
|||
assertNotNull(publicDownload); |
|||
assertTrue(publicDownload.exists()); |
|||
assertTrue(publicDownload.length() > 0); |
|||
|
|||
// make image private again
|
|||
TbResourceInfo privateImage = client.updateImagePublicStatus("tenant", |
|||
createdImages.get(1).getResourceKey(), false); |
|||
assertEquals(false, privateImage.getPublic()); |
|||
|
|||
// export image
|
|||
ResourceExportData exportData = client.exportImage("tenant", createdImages.get(4).getResourceKey()); |
|||
assertNotNull(exportData); |
|||
assertNotNull(exportData.getData()); |
|||
assertEquals(createdImages.get(4).getTitle(), exportData.getTitle()); |
|||
assertEquals(createdImages.get(4).getResourceKey(), exportData.getResourceKey()); |
|||
|
|||
// delete image
|
|||
String keyToDelete = createdImages.get(0).getResourceKey(); |
|||
TbImageDeleteResult deleteResult = client.deleteImage("tenant", keyToDelete, false); |
|||
assertNotNull(deleteResult); |
|||
assertTrue(deleteResult.getSuccess()); |
|||
|
|||
// verify deletion
|
|||
assertReturns404(() -> |
|||
client.getImageInfo("tenant", keyToDelete) |
|||
); |
|||
|
|||
PageDataTbResourceInfo imagesAfterDelete = client.getImages(100, 0, null, false, |
|||
TEST_PREFIX + "Image_" + timestamp, null, null); |
|||
assertEquals(4, imagesAfterDelete.getData().size()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.PageDataTbResourceInfo; |
|||
import org.thingsboard.client.model.ResourceType; |
|||
import org.thingsboard.client.model.TbResource; |
|||
import org.thingsboard.client.model.TbResourceInfo; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.io.File; |
|||
import java.util.ArrayList; |
|||
import java.util.Base64; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class TbResourceApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testResourceLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<TbResourceInfo> createdResources = new ArrayList<>(); |
|||
|
|||
// create 5 JS_MODULE resources
|
|||
for (int i = 0; i < 5; i++) { |
|||
TbResource resource = new TbResource(); |
|||
resource.setTitle(TEST_PREFIX + "Resource_" + timestamp + "_" + i); |
|||
resource.setResourceType(ResourceType.JS_MODULE); |
|||
resource.setResourceKey("test_module_" + timestamp + "_" + i + ".js"); |
|||
resource.setFileName("test_module_" + timestamp + "_" + i + ".js"); |
|||
|
|||
String jsContent = "export default function test" + i + "() { return " + i + "; }"; |
|||
resource.setData(Base64.getEncoder().encodeToString(jsContent.getBytes())); |
|||
|
|||
TbResourceInfo created = client.saveResource(resource); |
|||
assertNotNull(created); |
|||
assertNotNull(created.getId()); |
|||
assertEquals(resource.getTitle(), created.getTitle()); |
|||
assertEquals(ResourceType.JS_MODULE, created.getResourceType()); |
|||
|
|||
createdResources.add(created); |
|||
} |
|||
|
|||
// get tenant resources, check count
|
|||
PageDataTbResourceInfo tenantResources = client.getTenantResources(100, 0, null, null, null); |
|||
assertNotNull(tenantResources); |
|||
assertNotNull(tenantResources.getData()); |
|||
int initialSize = tenantResources.getData().size(); |
|||
assertTrue("Expected at least 5 resources, but got " + initialSize, initialSize >= 5); |
|||
|
|||
// find with text search
|
|||
PageDataTbResourceInfo filteredResources = client.getTenantResources(100, 0, |
|||
TEST_PREFIX + "Resource_" + timestamp, null, null); |
|||
assertEquals(5, filteredResources.getData().size()); |
|||
|
|||
// get resources with type filter
|
|||
PageDataTbResourceInfo jsResources = client.getResources(100, 0, |
|||
ResourceType.JS_MODULE.getValue(), null, TEST_PREFIX + "Resource_" + timestamp, null, null); |
|||
assertEquals(5, jsResources.getData().size()); |
|||
|
|||
// get resource info by id
|
|||
TbResourceInfo searchResource = createdResources.get(2); |
|||
TbResourceInfo fetchedInfo = client.getResourceInfoById(searchResource.getId().getId().toString()); |
|||
assertEquals(searchResource.getTitle(), fetchedInfo.getTitle()); |
|||
assertEquals(searchResource.getResourceKey(), fetchedInfo.getResourceKey()); |
|||
|
|||
// get full resource by id (includes data)
|
|||
TbResource fullResource = client.getResourceById(searchResource.getId().getId().toString()); |
|||
assertNotNull(fullResource); |
|||
assertEquals(searchResource.getTitle(), fullResource.getTitle()); |
|||
assertNotNull(fullResource.getData()); |
|||
|
|||
// download resource
|
|||
File downloadedFile = client.downloadResource(searchResource.getId().getId().toString()); |
|||
assertNotNull(downloadedFile); |
|||
assertTrue(downloadedFile.exists()); |
|||
assertTrue(downloadedFile.length() > 0); |
|||
|
|||
// get resources by list of ids
|
|||
List<String> idsToFetch = List.of( |
|||
createdResources.get(0).getId().getId().toString(), |
|||
createdResources.get(1).getId().getId().toString() |
|||
); |
|||
List<TbResourceInfo> resourceList = client.getSystemOrTenantResourcesByIds(idsToFetch); |
|||
assertEquals(2, resourceList.size()); |
|||
|
|||
// update resource
|
|||
TbResource resourceToUpdate = client.getResourceById(createdResources.get(3).getId().getId().toString()); |
|||
resourceToUpdate.setTitle(resourceToUpdate.getTitle() + "_updated"); |
|||
String updatedContent = "export default function updated() { return 42; }"; |
|||
resourceToUpdate.setData(Base64.getEncoder().encodeToString(updatedContent.getBytes())); |
|||
TbResourceInfo updatedResource = client.saveResource(resourceToUpdate); |
|||
assertEquals(resourceToUpdate.getTitle(), updatedResource.getTitle()); |
|||
|
|||
// delete resource
|
|||
UUID resourceToDeleteId = createdResources.get(0).getId().getId(); |
|||
client.deleteResource(resourceToDeleteId.toString(), false); |
|||
|
|||
// verify deletion
|
|||
assertReturns404(() -> |
|||
client.getResourceInfoById(resourceToDeleteId.toString()) |
|||
); |
|||
|
|||
PageDataTbResourceInfo resourcesAfterDelete = client.getTenantResources(100, 0, |
|||
TEST_PREFIX + "Resource_" + timestamp, null, null); |
|||
assertEquals(4, resourcesAfterDelete.getData().size()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,150 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.AttributeData; |
|||
import org.thingsboard.client.model.Device; |
|||
import org.thingsboard.client.model.TsData; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertFalse; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class TelemetryApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testTelemetryLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
|
|||
// create a device for telemetry operations
|
|||
Device device = new Device(); |
|||
device.setName("TelemetryTestDevice_" + timestamp); |
|||
device.setType("default"); |
|||
Device createdDevice = client.saveDevice(device, null, null, null, null); |
|||
assertNotNull(createdDevice); |
|||
|
|||
String entityType = "DEVICE"; |
|||
String entityId = createdDevice.getId().getId().toString(); |
|||
|
|||
// save server-side attributes
|
|||
String serverAttributes = "{\"serverAttr1\": \"value1\", \"serverAttr2\": 42}"; |
|||
client.saveEntityAttributesV2(entityType, entityId, "SERVER_SCOPE", serverAttributes); |
|||
|
|||
// save shared attributes
|
|||
String sharedAttributes = "{\"sharedAttr1\": \"sharedValue1\", \"sharedAttr2\": true}"; |
|||
client.saveEntityAttributesV2(entityType, entityId, "SHARED_SCOPE", sharedAttributes); |
|||
|
|||
// get attribute keys
|
|||
List<String> allKeys = client.getAttributeKeys(entityType, entityId); |
|||
assertNotNull(allKeys); |
|||
assertTrue(allKeys.containsAll(List.of("serverAttr1", "serverAttr2", "sharedAttr1", "sharedAttr2"))); |
|||
|
|||
// get attribute keys by scope
|
|||
List<String> serverKeys = client.getAttributeKeysByScope(entityType, entityId, "SERVER_SCOPE"); |
|||
assertEquals(2 + 1, serverKeys.size()); //active attribute is automatically added to server scope
|
|||
assertTrue(serverKeys.containsAll(List.of("serverAttr1", "serverAttr2", "active"))); |
|||
|
|||
// get attributes by scope
|
|||
List<AttributeData> serverAttrs = client.getAttributesByScope(entityType, entityId, "SERVER_SCOPE", "serverAttr1,serverAttr2", null); |
|||
assertNotNull(serverAttrs); |
|||
assertEquals(2, serverAttrs.size()); |
|||
|
|||
// get all attributes
|
|||
List<AttributeData> allAttrs = client.getAttributes(entityType, entityId, "serverAttr1,sharedAttr1", null); |
|||
assertEquals(2, allAttrs.size()); |
|||
assertEquals("value1", allAttrs.stream().filter(attr -> attr.getKey().equals("serverAttr1")).findFirst().orElseThrow().getValue().toString()); |
|||
assertEquals("sharedValue1", allAttrs.stream().filter(attr -> attr.getKey().equals("sharedAttr1")).findFirst().orElseThrow().getValue().toString()); |
|||
|
|||
// save timeseries data
|
|||
long ts1 = timestamp - 60000; |
|||
long ts2 = timestamp - 30000; |
|||
long ts3 = timestamp; |
|||
String telemetryBody = "{\"ts\":" + ts1 + ",\"values\":{\"temperature\":25.5,\"humidity\":60}}"; |
|||
client.saveEntityTelemetry(entityType, entityId, "ANY", telemetryBody); |
|||
|
|||
String telemetryBody2 = "{\"ts\":" + ts2 + ",\"values\":{\"temperature\":26.0,\"humidity\":58}}"; |
|||
client.saveEntityTelemetry(entityType, entityId, "ANY", telemetryBody2); |
|||
|
|||
String telemetryBody3 = "{\"ts\":" + ts3 + ",\"values\":{\"temperature\":27.1,\"humidity\":55}}"; |
|||
client.saveEntityTelemetry(entityType, entityId, "ANY", telemetryBody3); |
|||
|
|||
// get timeseries keys
|
|||
List<String> tsKeys = client.getTimeseriesKeys(entityType, entityId); |
|||
assertNotNull(tsKeys); |
|||
assertEquals(2, tsKeys.size()); |
|||
assertTrue(tsKeys.containsAll(List.of("humidity", "temperature"))); |
|||
|
|||
// get latest timeseries
|
|||
Map<String, List<TsData>> latestData = client.getLatestTimeseries(entityType, entityId, "temperature,humidity", false, null); |
|||
assertNotNull(latestData); |
|||
assertNotNull(latestData.get("temperature")); |
|||
assertFalse(latestData.get("temperature").isEmpty()); |
|||
assertEquals("27.1", latestData.get("temperature").get(0).getValue().toString()); |
|||
|
|||
// get timeseries history
|
|||
Map<String, List<TsData>> historyData = client.getTimeseriesHistory( |
|||
entityType, entityId, |
|||
ts1 - 1000, ts3 + 1000, "temperature", |
|||
null, null, null, null, "NONE", "ASC", false, null); |
|||
assertNotNull(historyData); |
|||
List<TsData> tempHistory = historyData.get("temperature"); |
|||
assertNotNull(tempHistory); |
|||
assertEquals(3, tempHistory.size()); |
|||
assertEquals("25.5", tempHistory.get(0).getValue().toString()); |
|||
assertEquals("27.1", tempHistory.get(2).getValue().toString()); |
|||
|
|||
// delete timeseries
|
|||
client.deleteEntityTimeseries(entityType, entityId, "humidity", true, null, null, true, false, null); |
|||
|
|||
List<String> keysAfterDelete = client.getTimeseriesKeys(entityType, entityId); |
|||
assertFalse(keysAfterDelete.contains("humidity")); |
|||
|
|||
// delete attributes
|
|||
client.deleteEntityAttributes(entityType, entityId, "SERVER_SCOPE", "serverAttr1", null); |
|||
|
|||
List<String> serverKeysAfterDelete = client.getAttributeKeysByScope(entityType, entityId, "SERVER_SCOPE"); |
|||
assertFalse(serverKeysAfterDelete.contains("serverAttr1")); |
|||
assertTrue(serverKeysAfterDelete.contains("serverAttr2")); |
|||
|
|||
// save device attributes using device-specific endpoint
|
|||
client.saveDeviceAttributes(entityId, "SERVER_SCOPE", "{\"deviceSpecificAttr\": \"test\"}"); |
|||
|
|||
List<String> deviceKeys = client.getAttributeKeysByScope(entityType, entityId, "SERVER_SCOPE"); |
|||
assertTrue(deviceKeys.contains("deviceSpecificAttr")); |
|||
|
|||
// delete device attributes
|
|||
client.deleteDeviceAttributes(entityId, "SERVER_SCOPE", "deviceSpecificAttr", null); |
|||
|
|||
List<String> deviceKeysAfterDelete = client.getAttributeKeysByScope(entityType, entityId, "SERVER_SCOPE"); |
|||
assertFalse(deviceKeysAfterDelete.contains("deviceSpecificAttr")); |
|||
|
|||
// save telemetry with TTL
|
|||
String ttlTelemetry = "{\"ts\":" + timestamp + ",\"values\":{\"shortLived\":99}}"; |
|||
client.saveEntityTelemetryWithTTL(entityType, entityId, "ANY", 86400L, ttlTelemetry); |
|||
|
|||
Map<String, List<TsData>> latestWithTtl = client.getLatestTimeseries(entityType, entityId, "shortLived", false, null); |
|||
assertNotNull(latestWithTtl.get("shortLived")); |
|||
assertEquals("99", latestWithTtl.get("shortLived").get(0).getValue().toString()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,119 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.ApiException; |
|||
import org.thingsboard.client.model.Authority; |
|||
import org.thingsboard.client.model.PageDataTenant; |
|||
import org.thingsboard.client.model.PageDataUser; |
|||
import org.thingsboard.client.model.Tenant; |
|||
import org.thingsboard.client.model.User; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
|
|||
@DaoSqlTest |
|||
public class TenantApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testTenantLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<Tenant> createdTenants = new ArrayList<>(); |
|||
|
|||
// authenticate as sysadmin for tenant management
|
|||
client.login("sysadmin@thingsboard.org", "sysadmin"); |
|||
|
|||
// create 20 tenants
|
|||
for (int i = 0; i < 20; i++) { |
|||
Tenant tenant = new Tenant(); |
|||
String tenantTitle = ((i % 2 == 0) ? TEST_PREFIX : TEST_PREFIX_2) + timestamp + "_" + i; |
|||
tenant.setTitle(tenantTitle); |
|||
tenant.setEmail("tenant_" + timestamp + "_" + i + "@test.com"); |
|||
tenant.setCountry("US"); |
|||
tenant.setCity("City" + i); |
|||
|
|||
Tenant createdTenant = client.saveTenant(tenant); |
|||
assertNotNull(createdTenant); |
|||
assertNotNull(createdTenant.getId()); |
|||
assertEquals(tenantTitle, createdTenant.getTitle()); |
|||
|
|||
createdTenants.add(createdTenant); |
|||
} |
|||
|
|||
try { |
|||
// find all with search text, check count
|
|||
PageDataTenant filteredTenants = client.getTenants(100, 0, TEST_PREFIX_2, null, null); |
|||
assertEquals("Expected exactly 10 tenants matching prefix", 10, filteredTenants.getData().size()); |
|||
|
|||
// find by id
|
|||
Tenant searchTenant = createdTenants.get(10); |
|||
Tenant fetchedTenant = client.getTenantById(searchTenant.getId().getId().toString()); |
|||
assertEquals(searchTenant.getTitle(), fetchedTenant.getTitle()); |
|||
assertEquals(searchTenant.getEmail(), fetchedTenant.getEmail()); |
|||
|
|||
// update tenant
|
|||
fetchedTenant.setCity("Updated City"); |
|||
fetchedTenant.setCountry("DE"); |
|||
Tenant updatedTenant = client.saveTenant(fetchedTenant); |
|||
assertEquals("Updated City", updatedTenant.getCity()); |
|||
assertEquals("DE", updatedTenant.getCountry()); |
|||
|
|||
// create a tenant admin for one of the tenants and verify listing
|
|||
Tenant tenantForAdmin = createdTenants.get(0); |
|||
User adminUser = new User(); |
|||
adminUser.setEmail("tenanttest_admin_" + timestamp + "@test.com"); |
|||
adminUser.setAuthority(Authority.TENANT_ADMIN); |
|||
adminUser.setTenantId(tenantForAdmin.getId()); |
|||
adminUser.setFirstName("TestAdmin"); |
|||
User savedAdmin = client.saveUser(adminUser, "false"); |
|||
assertNotNull(savedAdmin); |
|||
|
|||
PageDataUser tenantAdmins = client.getTenantAdmins( |
|||
tenantForAdmin.getId().getId().toString(), 100, 0, null, null, null); |
|||
assertEquals(1, tenantAdmins.getData().size()); |
|||
assertEquals(savedAdmin.getEmail(), tenantAdmins.getData().get(0).getEmail()); |
|||
|
|||
// delete tenant
|
|||
UUID tenantToDeleteId = createdTenants.get(0).getId().getId(); |
|||
client.deleteTenant(tenantToDeleteId.toString()); |
|||
createdTenants.remove(0); |
|||
|
|||
// verify deletion
|
|||
PageDataTenant tenantsAfterDelete = client.getTenants(100, 0, TEST_PREFIX_2, null, null); |
|||
assertEquals(10, tenantsAfterDelete.getData().size()); |
|||
|
|||
assertReturns404(() -> |
|||
client.getTenantById(tenantToDeleteId.toString()) |
|||
); |
|||
} finally { |
|||
// clean up all created tenants (deleting tenant cascades to users)
|
|||
client.login("sysadmin@thingsboard.org", "sysadmin"); |
|||
for (Tenant tenant : createdTenants) { |
|||
try { |
|||
client.deleteTenant(tenant.getId().getId().toString()); |
|||
} catch (ApiException ignored) { |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,179 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.ApiException; |
|||
import org.thingsboard.client.model.DefaultTenantProfileConfiguration; |
|||
import org.thingsboard.client.model.EntityInfo; |
|||
import org.thingsboard.client.model.PageDataEntityInfo; |
|||
import org.thingsboard.client.model.PageDataTenantProfile; |
|||
import org.thingsboard.client.model.TenantProfile; |
|||
import org.thingsboard.client.model.TenantProfileData; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertFalse; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class TenantProfileApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testTenantProfileLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<TenantProfile> createdProfiles = new ArrayList<>(); |
|||
|
|||
// authenticate as sysadmin for tenant profile management
|
|||
client.login("sysadmin@thingsboard.org", "sysadmin"); |
|||
|
|||
// get initial count (there should be a default profile)
|
|||
PageDataTenantProfile initialProfiles = client.getTenantProfiles(100, 0, null, null, null); |
|||
assertNotNull(initialProfiles); |
|||
int initialSize = initialProfiles.getData().size(); |
|||
assertTrue("Expected at least 1 default tenant profile", initialSize >= 1); |
|||
|
|||
// get default tenant profile info
|
|||
EntityInfo defaultProfileInfo = client.getDefaultTenantProfileInfo(); |
|||
assertNotNull(defaultProfileInfo); |
|||
assertNotNull(defaultProfileInfo.getName()); |
|||
|
|||
try { |
|||
// create 5 tenant profiles
|
|||
for (int i = 0; i < 5; i++) { |
|||
TenantProfile profile = new TenantProfile(); |
|||
profile.setName(TEST_PREFIX + "TenantProfile_" + timestamp + "_" + i); |
|||
profile.setDescription("Test tenant profile " + i); |
|||
profile.setIsolatedTbRuleEngine(false); |
|||
|
|||
TenantProfileData profileData = new TenantProfileData(); |
|||
DefaultTenantProfileConfiguration config = new DefaultTenantProfileConfiguration(); |
|||
config.setMaxDevices(100L); |
|||
config.setMaxAssets(100L); |
|||
config.setMaxCustomers(50L); |
|||
config.setMaxUsers(50L); |
|||
config.setMaxDashboards(50L); |
|||
config.setMaxRuleChains(20L); |
|||
config.setMaxDataPointsPerRollingArg(20L); |
|||
config.setMaxRelatedEntitiesToReturnPerCfArgument(20); |
|||
config.setMaxRelationLevelPerCfArgument(20); |
|||
profileData.setConfiguration(config); |
|||
profile.setProfileData(profileData); |
|||
profile.setDefault(false); |
|||
|
|||
TenantProfile created = client.saveTenantProfile(profile); |
|||
assertNotNull(created); |
|||
assertNotNull(created.getId()); |
|||
assertEquals(profile.getName(), created.getName()); |
|||
assertEquals(profile.getDescription(), created.getDescription()); |
|||
assertFalse(created.getDefault()); |
|||
|
|||
createdProfiles.add(created); |
|||
} |
|||
|
|||
// find all, check count
|
|||
PageDataTenantProfile allProfiles = client.getTenantProfiles(100, 0, null, null, null); |
|||
assertNotNull(allProfiles); |
|||
assertEquals(initialSize + 5, allProfiles.getData().size()); |
|||
|
|||
// find with text search
|
|||
PageDataTenantProfile filteredProfiles = client.getTenantProfiles(100, 0, |
|||
TEST_PREFIX + "TenantProfile_" + timestamp, null, null); |
|||
assertEquals(5, filteredProfiles.getData().size()); |
|||
|
|||
// get by id
|
|||
TenantProfile searchProfile = createdProfiles.get(2); |
|||
TenantProfile fetchedProfile = client.getTenantProfileById(searchProfile.getId().getId().toString()); |
|||
assertEquals(searchProfile.getName(), fetchedProfile.getName()); |
|||
assertEquals(searchProfile.getDescription(), fetchedProfile.getDescription()); |
|||
|
|||
// update tenant profile
|
|||
fetchedProfile.setDescription("Updated description"); |
|||
TenantProfile updatedProfile = client.saveTenantProfile(fetchedProfile); |
|||
assertEquals("Updated description", updatedProfile.getDescription()); |
|||
assertEquals(fetchedProfile.getName(), updatedProfile.getName()); |
|||
|
|||
// get tenant profile infos (paginated)
|
|||
PageDataEntityInfo profileInfos = client.getTenantProfileInfos(100, 0, null, null, null); |
|||
assertNotNull(profileInfos); |
|||
assertEquals(initialSize + 5, profileInfos.getData().size()); |
|||
|
|||
// get profiles by list of ids
|
|||
List<String> idsToFetch = List.of( |
|||
createdProfiles.get(0).getId().getId().toString(), |
|||
createdProfiles.get(1).getId().getId().toString() |
|||
); |
|||
List<TenantProfile> profileList = client.getTenantProfileList(idsToFetch); |
|||
assertEquals(2, profileList.size()); |
|||
|
|||
// set a profile as default
|
|||
TenantProfile profileToSetDefault = createdProfiles.get(1); |
|||
client.setDefaultTenantProfile(profileToSetDefault.getId().getId().toString()); |
|||
EntityInfo defaultTenantProfileInfo = client.getDefaultTenantProfileInfo(); |
|||
assertEquals(profileToSetDefault.getName(), defaultTenantProfileInfo.getName()); |
|||
|
|||
// verify default profile info now points to the new default
|
|||
EntityInfo newDefaultInfo = client.getDefaultTenantProfileInfo(); |
|||
assertEquals(profileToSetDefault.getName(), newDefaultInfo.getName()); |
|||
|
|||
// restore original default profile
|
|||
TenantProfile originalDefault = initialProfiles.getData().stream() |
|||
.filter(TenantProfile::getDefault) |
|||
.findFirst() |
|||
.orElseThrow(); |
|||
client.setDefaultTenantProfile(originalDefault.getId().getId().toString()); |
|||
|
|||
// delete tenant profile (cannot delete the default one)
|
|||
UUID profileToDeleteId = createdProfiles.get(0).getId().getId(); |
|||
client.deleteTenantProfile(profileToDeleteId.toString()); |
|||
createdProfiles.remove(0); |
|||
|
|||
// verify deletion
|
|||
assertReturns404(() -> |
|||
client.getTenantProfileById(profileToDeleteId.toString()) |
|||
); |
|||
|
|||
PageDataTenantProfile profilesAfterDelete = client.getTenantProfiles(100, 0, null, null, null); |
|||
assertEquals(initialSize + 4, profilesAfterDelete.getData().size()); |
|||
} finally { |
|||
// clean up created profiles
|
|||
client.login("sysadmin@thingsboard.org", "sysadmin"); |
|||
|
|||
// ensure original default is restored before deleting test profiles
|
|||
TenantProfile originalDefault = initialProfiles.getData().stream() |
|||
.filter(TenantProfile::getDefault) |
|||
.findFirst() |
|||
.orElseThrow(); |
|||
try { |
|||
client.setDefaultTenantProfile(originalDefault.getId().getId().toString()); |
|||
} catch (ApiException ignored) { |
|||
} |
|||
|
|||
for (TenantProfile profile : createdProfiles) { |
|||
try { |
|||
client.deleteTenantProfile(profile.getId().getId().toString()); |
|||
} catch (ApiException ignored) { |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.AccountTwoFaSettings; |
|||
import org.thingsboard.client.model.PlatformTwoFaSettings; |
|||
import org.thingsboard.client.model.TotpTwoFaAccountConfig; |
|||
import org.thingsboard.client.model.TotpTwoFaProviderConfig; |
|||
import org.thingsboard.client.model.TwoFaAccountConfig; |
|||
import org.thingsboard.client.model.TwoFaProviderType; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.List; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertFalse; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@DaoSqlTest |
|||
public class TwoFactorAuthApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testTwoFactorAuthLifecycle() throws Exception { |
|||
// save original platform 2FA settings as sysadmin
|
|||
client.login("sysadmin@thingsboard.org", "sysadmin"); |
|||
|
|||
// configure platform 2FA settings with TOTP provider
|
|||
TotpTwoFaProviderConfig totpProviderConfig = new TotpTwoFaProviderConfig(); |
|||
totpProviderConfig.setIssuerName("TestThingsBoard"); |
|||
|
|||
PlatformTwoFaSettings newSettings = new PlatformTwoFaSettings(); |
|||
newSettings.setProviders(List.of(totpProviderConfig)); |
|||
newSettings.setMinVerificationCodeSendPeriod(30); |
|||
newSettings.setTotalAllowedTimeForVerification(300); |
|||
newSettings.setMaxVerificationFailuresBeforeUserLockout(5); |
|||
|
|||
PlatformTwoFaSettings savedSettings = client.savePlatformTwoFaSettings(newSettings); |
|||
assertNotNull(savedSettings); |
|||
assertNotNull(savedSettings.getProviders()); |
|||
assertFalse(savedSettings.getProviders().isEmpty()); |
|||
assertEquals(30, savedSettings.getMinVerificationCodeSendPeriod().intValue()); |
|||
assertEquals(300, savedSettings.getTotalAllowedTimeForVerification().intValue()); |
|||
|
|||
// get available 2FA providers (should include TOTP)
|
|||
List<TwoFaProviderType> providerTypes = client.getAvailableTwoFaProviderTypes(); |
|||
assertNotNull(providerTypes); |
|||
assertTrue(providerTypes.contains(TwoFaProviderType.TOTP)); |
|||
|
|||
// get account 2FA settings (should be empty initially)
|
|||
AccountTwoFaSettings accountSettings = client.getAccountTwoFaSettings(); |
|||
assertNull(accountSettings); |
|||
|
|||
// generate TOTP account config
|
|||
TwoFaAccountConfig generatedConfig = client.generateTwoFaAccountConfig(TwoFaProviderType.TOTP.getValue()); |
|||
assertNotNull(generatedConfig); |
|||
TotpTwoFaAccountConfig totpConfig = (TotpTwoFaAccountConfig) generatedConfig; |
|||
assertNotNull(totpConfig); |
|||
assertNotNull(totpConfig.getAuthUrl()); |
|||
assertTrue(totpConfig.getAuthUrl().startsWith("otpauth://totp/")); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,135 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.Authority; |
|||
import org.thingsboard.client.model.Customer; |
|||
import org.thingsboard.client.model.JwtPair; |
|||
import org.thingsboard.client.model.PageDataUser; |
|||
import org.thingsboard.client.model.User; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
|
|||
@DaoSqlTest |
|||
public class UserApiClientTest extends AbstractApiClientTest { |
|||
|
|||
@Test |
|||
public void testUserLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<User> createdUsers = new ArrayList<>(); |
|||
|
|||
// create 20 tenant admin users
|
|||
for (int i = 0; i < 20; i++) { |
|||
User user = new User(); |
|||
String email = ((i % 2 == 0) ? TEST_PREFIX : TEST_PREFIX_2) + timestamp + "_" + i + "@test.com"; |
|||
user.setEmail(email); |
|||
user.setAuthority(Authority.TENANT_ADMIN); |
|||
user.setTenantId(savedClientTenant.getId()); |
|||
user.setFirstName("First" + i); |
|||
user.setLastName("Last" + i); |
|||
|
|||
User createdUser = client.saveUser(user, "false"); |
|||
assertNotNull(createdUser); |
|||
assertNotNull(createdUser.getId()); |
|||
assertEquals(email, createdUser.getEmail()); |
|||
assertEquals(Authority.TENANT_ADMIN, createdUser.getAuthority()); |
|||
|
|||
createdUsers.add(createdUser); |
|||
} |
|||
|
|||
// find all tenant admins, check count (20 created + 1 from setup)
|
|||
PageDataUser allUsers = client.getUsers(100, 0, null, null, null); |
|||
assertNotNull(allUsers); |
|||
assertNotNull(allUsers.getData()); |
|||
int initialSize = allUsers.getData().size(); |
|||
assertEquals("Expected 21 users (20 created + 2 from setup), but got " + initialSize, 22, initialSize); |
|||
|
|||
// find with search text, check count
|
|||
PageDataUser filteredUsers = client.getUsers(100, 0, TEST_PREFIX_2, null, null); |
|||
assertEquals("Expected exactly 10 users matching prefix", 10, filteredUsers.getData().size()); |
|||
|
|||
// find by id
|
|||
User searchUser = createdUsers.get(10); |
|||
User fetchedUser = client.getUserById(searchUser.getId().getId().toString()); |
|||
assertEquals(searchUser.getEmail(), fetchedUser.getEmail()); |
|||
assertEquals(searchUser.getFirstName(), fetchedUser.getFirstName()); |
|||
|
|||
// update user
|
|||
fetchedUser.setFirstName("UpdatedFirst"); |
|||
fetchedUser.setLastName("UpdatedLast"); |
|||
User updatedUser = client.saveUser(fetchedUser, "false"); |
|||
assertEquals("UpdatedFirst", updatedUser.getFirstName()); |
|||
assertEquals("UpdatedLast", updatedUser.getLastName()); |
|||
|
|||
// activate user and get token
|
|||
activateUser(createdUsers.get(0).getId(), "password123", false); |
|||
JwtPair userToken = client.getUserToken(createdUsers.get(0).getId().getId().toString()); |
|||
assertNotNull(userToken); |
|||
assertNotNull(userToken.getToken()); |
|||
|
|||
// disable user credentials
|
|||
client.setUserCredentialsEnabled(createdUsers.get(0).getId().getId().toString(), "false"); |
|||
|
|||
// re-enable user credentials
|
|||
client.setUserCredentialsEnabled(createdUsers.get(0).getId().getId().toString(), "true"); |
|||
|
|||
// create customer users and verify listing
|
|||
Customer customer2 = new Customer(); |
|||
customer2.setTitle("User test customer " + timestamp); |
|||
customer2.setEmail("usertest_" + timestamp + "@test.com"); |
|||
Customer savedCustomer2 = client.saveCustomer(customer2, null, null, null); |
|||
|
|||
List<User> customerUsers = new ArrayList<>(); |
|||
for (int i = 0; i < 5; i++) { |
|||
User customerUser = new User(); |
|||
customerUser.setEmail("custuser_" + timestamp + "_" + i + "@test.com"); |
|||
customerUser.setAuthority(Authority.CUSTOMER_USER); |
|||
customerUser.setTenantId(savedClientTenant.getId()); |
|||
customerUser.setCustomerId(savedCustomer2.getId()); |
|||
customerUser.setFirstName("CustFirst" + i); |
|||
customerUser.setLastName("CustLast" + i); |
|||
|
|||
User created = client.saveUser(customerUser, "false"); |
|||
assertNotNull(created); |
|||
customerUsers.add(created); |
|||
} |
|||
|
|||
// list customer users
|
|||
PageDataUser customerUserPage = client.getCustomerUsers( |
|||
savedCustomer2.getId().getId().toString(), 100, 0, null, null, null); |
|||
assertEquals("Expected 5 customer users", 5, customerUserPage.getData().size()); |
|||
|
|||
// delete user
|
|||
UUID userToDeleteId = createdUsers.get(0).getId().getId(); |
|||
client.deleteUser(userToDeleteId.toString()); |
|||
|
|||
// verify deletion
|
|||
PageDataUser usersAfterDelete = client.getUsers(100, 0, null, null, null); |
|||
assertEquals(initialSize + 5 - 1, usersAfterDelete.getData().size()); |
|||
|
|||
assertReturns404(() -> |
|||
client.getUserById(userToDeleteId.toString()) |
|||
); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,155 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.client; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import org.junit.Test; |
|||
import org.thingsboard.client.model.PageDataWidgetTypeInfo; |
|||
import org.thingsboard.client.model.WidgetTypeDetails; |
|||
import org.thingsboard.client.model.WidgetTypeInfo; |
|||
import org.thingsboard.client.model.WidgetsBundle; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
|
|||
@DaoSqlTest |
|||
public class WidgetTypeApiClientTest extends AbstractApiClientTest { |
|||
|
|||
private JsonNode createDescriptor(String type) { |
|||
return OBJECT_MAPPER.createObjectNode() |
|||
.put("type", type) |
|||
.put("sizeX", 7.5) |
|||
.put("sizeY", 5) |
|||
.put("resources", "[]") |
|||
.put("templateHtml", "<div class='test-widget'>Test</div>") |
|||
.put("templateCss", ".test-widget { font-size: 14px; }") |
|||
.put("controllerScript", "self.onInit = function() {};") |
|||
.put("settingsSchema", "{}") |
|||
.put("dataKeySettingsSchema", "{}"); |
|||
} |
|||
|
|||
@Test |
|||
public void testWidgetTypeLifecycle() throws Exception { |
|||
long timestamp = System.currentTimeMillis(); |
|||
List<WidgetTypeDetails> createdWidgetTypes = new ArrayList<>(); |
|||
|
|||
// create a widgets bundle
|
|||
WidgetsBundle bundle = new WidgetsBundle(null, null, null, |
|||
TEST_PREFIX + "Bundle_" + timestamp, null, false, |
|||
"Test bundle description", null, null); |
|||
WidgetsBundle savedBundle = client.saveWidgetsBundle(bundle); |
|||
assertNotNull(savedBundle); |
|||
assertNotNull(savedBundle.getId()); |
|||
assertEquals(bundle.getTitle(), savedBundle.getTitle()); |
|||
|
|||
// create 5 widget types
|
|||
for (int i = 0; i < 5; i++) { |
|||
String name = TEST_PREFIX + "Widget_" + timestamp + "_" + i; |
|||
JsonNode descriptor = createDescriptor("latest"); |
|||
|
|||
WidgetTypeDetails widgetType = new WidgetTypeDetails(null, null, null, name, descriptor); |
|||
widgetType.setDescription("Test widget " + i); |
|||
widgetType.setDeprecated(false); |
|||
widgetType.setTags(List.of("test", "automated")); |
|||
|
|||
WidgetTypeDetails created = client.saveWidgetType(widgetType, false); |
|||
assertNotNull(created); |
|||
assertNotNull(created.getId()); |
|||
assertEquals(name, created.getName()); |
|||
assertNotNull(created.getFqn()); |
|||
|
|||
createdWidgetTypes.add(created); |
|||
} |
|||
|
|||
// list widget types with text search (tenant only)
|
|||
PageDataWidgetTypeInfo filteredTypes = client.getWidgetTypes(100, 0, |
|||
TEST_PREFIX + "Widget_" + timestamp, null, null, |
|||
true, false, null, null, null); |
|||
assertNotNull(filteredTypes); |
|||
assertEquals(5, filteredTypes.getData().size()); |
|||
|
|||
// get widget type details by id
|
|||
WidgetTypeDetails searchWidget = createdWidgetTypes.get(2); |
|||
WidgetTypeDetails fetchedDetails = client.getWidgetTypeById( |
|||
searchWidget.getId().getId().toString(), true); |
|||
assertEquals(searchWidget.getName(), fetchedDetails.getName()); |
|||
assertEquals(searchWidget.getFqn(), fetchedDetails.getFqn()); |
|||
assertEquals("Test widget 2", fetchedDetails.getDescription()); |
|||
|
|||
// get widget type info by id
|
|||
WidgetTypeInfo fetchedInfo = client.getWidgetTypeInfoById( |
|||
searchWidget.getId().getId().toString()); |
|||
assertEquals(searchWidget.getName(), fetchedInfo.getName()); |
|||
|
|||
// add widget types to bundle
|
|||
List<String> widgetTypeIds = createdWidgetTypes.stream() |
|||
.map(wt -> wt.getId().getId().toString()) |
|||
.collect(Collectors.toList()); |
|||
client.updateWidgetsBundleWidgetTypes(savedBundle.getId().getId().toString(), widgetTypeIds); |
|||
|
|||
// get bundle widget type fqns
|
|||
List<String> bundleFqns = client.getBundleWidgetTypeFqns(savedBundle.getId().getId().toString()); |
|||
assertEquals(5, bundleFqns.size()); |
|||
|
|||
// get bundle widget types details
|
|||
List<WidgetTypeDetails> bundleDetails = client.getBundleWidgetTypesDetails( |
|||
savedBundle.getId().getId().toString(), false); |
|||
assertEquals(5, bundleDetails.size()); |
|||
|
|||
// get bundle widget types infos (paginated)
|
|||
PageDataWidgetTypeInfo bundleInfos = client.getBundleWidgetTypesInfos( |
|||
savedBundle.getId().getId().toString(), 100, 0, |
|||
null, null, null, null, null, null); |
|||
assertEquals(5, bundleInfos.getData().size()); |
|||
|
|||
// update widget type
|
|||
WidgetTypeDetails widgetToUpdate = client.getWidgetTypeById( |
|||
createdWidgetTypes.get(3).getId().getId().toString(), true); |
|||
widgetToUpdate.setDescription("Updated description"); |
|||
widgetToUpdate.setDeprecated(true); |
|||
widgetToUpdate.setTags(List.of("test", "updated")); |
|||
WidgetTypeDetails updatedWidget = client.saveWidgetType(widgetToUpdate, false); |
|||
assertEquals("Updated description", updatedWidget.getDescription()); |
|||
assertEquals(true, updatedWidget.getDeprecated()); |
|||
|
|||
// delete widget type
|
|||
String widgetToDeleteId = createdWidgetTypes.get(0).getId().getId().toString(); |
|||
client.deleteWidgetType(widgetToDeleteId); |
|||
|
|||
// verify deletion
|
|||
assertReturns404(() -> |
|||
client.getWidgetTypeById(widgetToDeleteId, false) |
|||
); |
|||
|
|||
PageDataWidgetTypeInfo typesAfterDelete = client.getWidgetTypes(100, 0, |
|||
TEST_PREFIX + "Widget_" + timestamp, null, null, |
|||
true, false, null, null, null); |
|||
assertEquals(4, typesAfterDelete.getData().size()); |
|||
|
|||
// delete widgets bundle
|
|||
client.deleteWidgetsBundle(savedBundle.getId().getId().toString()); |
|||
|
|||
assertReturns404(() -> |
|||
client.getWidgetsBundleById(savedBundle.getId().getId().toString(), false) |
|||
); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,420 @@ |
|||
/** |
|||
* Copyright © 2016-2026 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.controller; |
|||
|
|||
import com.fasterxml.jackson.core.type.TypeReference; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import org.junit.After; |
|||
import org.junit.Before; |
|||
import org.junit.Test; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.Tenant; |
|||
import org.thingsboard.server.common.data.User; |
|||
import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
|||
import org.thingsboard.server.common.data.alarm.rule.AlarmRule; |
|||
import org.thingsboard.server.common.data.alarm.rule.condition.SimpleAlarmCondition; |
|||
import org.thingsboard.server.common.data.alarm.rule.condition.expression.TbelAlarmConditionExpression; |
|||
import org.thingsboard.server.common.data.cf.AlarmRuleDefinition; |
|||
import org.thingsboard.server.common.data.cf.AlarmRuleDefinitionInfo; |
|||
import org.thingsboard.server.common.data.cf.CalculatedField; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldType; |
|||
import org.thingsboard.server.common.data.cf.configuration.AlarmCalculatedFieldConfiguration; |
|||
import org.thingsboard.server.common.data.cf.configuration.Argument; |
|||
import org.thingsboard.server.common.data.cf.configuration.ArgumentType; |
|||
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey; |
|||
import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration; |
|||
import org.thingsboard.server.common.data.cf.configuration.TimeSeriesOutput; |
|||
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.page.SortOrder; |
|||
import org.thingsboard.server.common.data.security.Authority; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.Comparator; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
|
|||
@DaoSqlTest |
|||
public class AlarmRuleControllerTest extends AbstractControllerTest { |
|||
|
|||
private Tenant savedTenant; |
|||
|
|||
@Before |
|||
public void beforeTest() throws Exception { |
|||
loginSysAdmin(); |
|||
|
|||
Tenant tenant = new Tenant(); |
|||
tenant.setTitle("My tenant"); |
|||
savedTenant = saveTenant(tenant); |
|||
assertThat(savedTenant).isNotNull(); |
|||
|
|||
User tenantAdmin = new User(); |
|||
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); |
|||
tenantAdmin.setTenantId(savedTenant.getId()); |
|||
tenantAdmin.setEmail("tenant2@thingsboard.org"); |
|||
tenantAdmin.setFirstName("Joe"); |
|||
tenantAdmin.setLastName("Downs"); |
|||
|
|||
createUserAndLogin(tenantAdmin, "testPassword1"); |
|||
} |
|||
|
|||
@After |
|||
public void afterTest() throws Exception { |
|||
loginSysAdmin(); |
|||
deleteTenant(savedTenant.getId()); |
|||
} |
|||
|
|||
@Test |
|||
public void testSaveAlarmRule() throws Exception { |
|||
Device testDevice = createDevice("Test device", "1234567890"); |
|||
AlarmRuleDefinition alarmRule = createTestAlarmRule(testDevice.getId(), "High Temperature"); |
|||
|
|||
AlarmRuleDefinition saved = saveAlarmRule(alarmRule); |
|||
|
|||
assertThat(saved).isNotNull(); |
|||
assertThat(saved.getId()).isNotNull(); |
|||
assertThat(saved.getCreatedTime()).isGreaterThan(0); |
|||
assertThat(saved.getTenantId()).isEqualTo(savedTenant.getId()); |
|||
assertThat(saved.getEntityId()).isEqualTo(testDevice.getId()); |
|||
assertThat(saved.getName()).isEqualTo("High Temperature"); |
|||
assertThat(saved.getConfiguration()).isNotNull(); |
|||
assertThat(saved.getConfiguration().getCreateRules()).containsKey(AlarmSeverity.CRITICAL); |
|||
|
|||
saved.setName("Updated Alarm Rule"); |
|||
AlarmRuleDefinition updated = saveAlarmRule(saved); |
|||
|
|||
assertThat(updated.getName()).isEqualTo("Updated Alarm Rule"); |
|||
assertThat(updated.getVersion()).isEqualTo(saved.getVersion() + 1); |
|||
|
|||
doDelete("/api/alarm/rule/" + saved.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAlarmRuleById() throws Exception { |
|||
Device testDevice = createDevice("Test device", "1234567890"); |
|||
AlarmRuleDefinition alarmRule = createTestAlarmRule(testDevice.getId(), "Test Alarm"); |
|||
|
|||
AlarmRuleDefinition saved = saveAlarmRule(alarmRule); |
|||
AlarmRuleDefinition fetched = doGet("/api/alarm/rule/" + saved.getId().getId(), AlarmRuleDefinition.class); |
|||
|
|||
assertThat(fetched).isNotNull(); |
|||
assertThat(fetched).isEqualTo(saved); |
|||
|
|||
doDelete("/api/alarm/rule/" + saved.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAlarmRuleById_notFound() throws Exception { |
|||
doGet("/api/alarm/rule/" + UUID.randomUUID()) |
|||
.andExpect(status().isNotFound()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAlarmRuleById_calculatedFieldNotAlarm() throws Exception { |
|||
Device testDevice = createDevice("Test device", "1234567890"); |
|||
CalculatedField cf = createSimpleCalculatedField(testDevice.getId()); |
|||
CalculatedField savedCf = doPost("/api/calculatedField", cf, CalculatedField.class); |
|||
|
|||
doGet("/api/alarm/rule/" + savedCf.getId().getId()) |
|||
.andExpect(status().isNotFound()); |
|||
|
|||
doDelete("/api/calculatedField/" + savedCf.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAlarmRulesByEntityId() throws Exception { |
|||
Device device1 = createDevice("Device 1", "1234567890"); |
|||
Device device2 = createDevice("Device 2", "0987654321"); |
|||
AlarmRuleDefinition rule1 = saveAlarmRule(createTestAlarmRule(device1.getId(), "Rule 1")); |
|||
saveAlarmRule(createTestAlarmRule(device2.getId(), "Rule 2")); |
|||
|
|||
PageData<AlarmRuleDefinition> result = doGetTypedWithPageLink( |
|||
"/api/alarm/rules/" + EntityType.DEVICE + "/" + device1.getUuidId() + "?", |
|||
new TypeReference<>() {}, new PageLink(10)); |
|||
|
|||
assertThat(result.getData()).hasSize(1); |
|||
assertThat(result.getData().get(0).getId()).isEqualTo(rule1.getId()); |
|||
assertThat(result.getData().get(0).getName()).isEqualTo("Rule 1"); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAlarmRules() throws Exception { |
|||
Device device = createDevice("Device A", "1234567890"); |
|||
AlarmRuleDefinition deviceRule = saveAlarmRule(createTestAlarmRule(device.getId(), "Device Alarm")); |
|||
|
|||
DeviceProfile profile = doPost("/api/deviceProfile", createDeviceProfile("Profile A"), DeviceProfile.class); |
|||
AlarmRuleDefinition profileRule = saveAlarmRule(createTestAlarmRule(profile.getId(), "Profile Alarm")); |
|||
|
|||
// All alarm rules
|
|||
List<AlarmRuleDefinitionInfo> all = getAlarmRules(null, null); |
|||
assertThat(all).extracting(AlarmRuleDefinition::getName) |
|||
.contains("Device Alarm", "Profile Alarm"); |
|||
|
|||
// Filter by entity type: DEVICE
|
|||
List<AlarmRuleDefinitionInfo> deviceRules = getAlarmRules(EntityType.DEVICE, null); |
|||
assertThat(deviceRules).extracting(AlarmRuleDefinition::getName) |
|||
.containsOnly("Device Alarm"); |
|||
|
|||
// Filter by entity type: DEVICE_PROFILE
|
|||
List<AlarmRuleDefinitionInfo> profileRules = getAlarmRules(EntityType.DEVICE_PROFILE, null); |
|||
assertThat(profileRules).extracting(AlarmRuleDefinition::getName) |
|||
.containsOnly("Profile Alarm"); |
|||
|
|||
// Filter by specific entity IDs
|
|||
List<AlarmRuleDefinitionInfo> specificRules = getAlarmRules(EntityType.DEVICE, List.of(device.getUuidId())); |
|||
assertThat(specificRules).extracting(AlarmRuleDefinition::getName) |
|||
.containsOnly("Device Alarm"); |
|||
|
|||
// Verify entity names are populated
|
|||
AlarmRuleDefinitionInfo deviceInfo = all.stream() |
|||
.filter(r -> r.getName().equals("Device Alarm")).findFirst().orElseThrow(); |
|||
assertThat(deviceInfo.getEntityName()).isEqualTo("Device A"); |
|||
|
|||
AlarmRuleDefinitionInfo profileInfo = all.stream() |
|||
.filter(r -> r.getName().equals("Profile Alarm")).findFirst().orElseThrow(); |
|||
assertThat(profileInfo.getEntityName()).isEqualTo("Profile A"); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAlarmRules_textSearch() throws Exception { |
|||
Device device = createDevice("Device A", "1234567890"); |
|||
saveAlarmRule(createTestAlarmRule(device.getId(), "Temperature Alarm")); |
|||
saveAlarmRule(createTestAlarmRule(device.getId(), "Humidity Alarm")); |
|||
|
|||
PageData<AlarmRuleDefinitionInfo> result = doGetTypedWithPageLink( |
|||
"/api/alarm/rules?textSearch=Temp&", |
|||
new TypeReference<>() {}, new PageLink(10)); |
|||
|
|||
assertThat(result.getData()).hasSize(1); |
|||
assertThat(result.getData().get(0).getName()).isEqualTo("Temperature Alarm"); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAlarmRuleNames() throws Exception { |
|||
Device device = createDevice("Device A", "1234567890"); |
|||
saveAlarmRule(createTestAlarmRule(device.getId(), "Alpha Alarm")); |
|||
saveAlarmRule(createTestAlarmRule(device.getId(), "Beta Alarm")); |
|||
|
|||
PageData<String> names = getAlarmRuleNames(new PageLink(10, 0, |
|||
null, new SortOrder("", SortOrder.Direction.ASC))); |
|||
assertThat(names.getTotalElements()).isEqualTo(2); |
|||
assertThat(names.getData()).isSortedAccordingTo(Comparator.naturalOrder()); |
|||
assertThat(names.getData()).contains("Alpha Alarm", "Beta Alarm"); |
|||
|
|||
names = getAlarmRuleNames(new PageLink(10, 0, |
|||
null, new SortOrder("", SortOrder.Direction.DESC))); |
|||
assertThat(names.getData()).isSortedAccordingTo(Comparator.reverseOrder()); |
|||
|
|||
names = getAlarmRuleNames(new PageLink(10, 0, |
|||
"Alpha", new SortOrder("", SortOrder.Direction.ASC))); |
|||
assertThat(names.getTotalElements()).isEqualTo(1); |
|||
assertThat(names.getData()).containsOnly("Alpha Alarm"); |
|||
} |
|||
|
|||
@Test |
|||
public void testDeleteAlarmRule() throws Exception { |
|||
Device testDevice = createDevice("Test device", "1234567890"); |
|||
AlarmRuleDefinition saved = saveAlarmRule(createTestAlarmRule(testDevice.getId(), "To Delete")); |
|||
|
|||
assertThat(saved).isNotNull(); |
|||
|
|||
doDelete("/api/alarm/rule/" + saved.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
doGet("/api/alarm/rule/" + saved.getId().getId()) |
|||
.andExpect(status().isNotFound()); |
|||
} |
|||
|
|||
@Test |
|||
public void testDeleteAlarmRule_notFound() throws Exception { |
|||
doDelete("/api/alarm/rule/" + UUID.randomUUID()) |
|||
.andExpect(status().isNotFound()); |
|||
} |
|||
|
|||
@Test |
|||
public void testDeleteAlarmRule_calculatedFieldNotAlarm() throws Exception { |
|||
Device testDevice = createDevice("Test device", "1234567890"); |
|||
CalculatedField cf = createSimpleCalculatedField(testDevice.getId()); |
|||
CalculatedField savedCf = doPost("/api/calculatedField", cf, CalculatedField.class); |
|||
|
|||
doDelete("/api/alarm/rule/" + savedCf.getId().getId()) |
|||
.andExpect(status().isNotFound()); |
|||
|
|||
doDelete("/api/calculatedField/" + savedCf.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetLatestAlarmRuleDebugEvent() throws Exception { |
|||
Device testDevice = createDevice("Test device", "1234567890"); |
|||
AlarmRuleDefinition saved = saveAlarmRule(createTestAlarmRule(testDevice.getId(), "Debug Test")); |
|||
|
|||
doGet("/api/alarm/rule/" + saved.getId().getId() + "/debug") |
|||
.andExpect(status().isOk()); |
|||
|
|||
doDelete("/api/alarm/rule/" + saved.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetLatestAlarmRuleDebugEvent_notFound() throws Exception { |
|||
doGet("/api/alarm/rule/" + UUID.randomUUID() + "/debug") |
|||
.andExpect(status().isNotFound()); |
|||
} |
|||
|
|||
@Test |
|||
public void testTestAlarmRuleScript() throws Exception { |
|||
JsonNode request = JacksonUtil.toJsonNode(""" |
|||
{ |
|||
"expression": "return temperature > 50;", |
|||
"arguments": { |
|||
"temperature": { "type": "SINGLE_VALUE", "ts": 1739776478057, "value": 55 } |
|||
} |
|||
} |
|||
"""); |
|||
|
|||
JsonNode result = doPost("/api/alarm/rule/testScript", request, JsonNode.class); |
|||
|
|||
assertThat(result).isNotNull(); |
|||
assertThat(result.has("output")).isTrue(); |
|||
assertThat(result.has("error")).isTrue(); |
|||
assertThat(result.get("error").asText()).isEmpty(); |
|||
assertThat(result.get("output").asText()).isEqualTo("true"); |
|||
} |
|||
|
|||
@Test |
|||
public void testTestAlarmRuleScript_returnsFalse() throws Exception { |
|||
JsonNode request = JacksonUtil.toJsonNode(""" |
|||
{ |
|||
"expression": "return temperature > 50;", |
|||
"arguments": { |
|||
"temperature": { "type": "SINGLE_VALUE", "ts": 1739776478057, "value": 30 } |
|||
} |
|||
} |
|||
"""); |
|||
|
|||
JsonNode result = doPost("/api/alarm/rule/testScript", request, JsonNode.class); |
|||
|
|||
assertThat(result).isNotNull(); |
|||
assertThat(result.get("error").asText()).isEmpty(); |
|||
assertThat(result.get("output").asText()).isEqualTo("false"); |
|||
} |
|||
|
|||
@Test |
|||
public void testTestAlarmRuleScript_missingExpression() throws Exception { |
|||
JsonNode request = JacksonUtil.toJsonNode(""" |
|||
{ |
|||
"arguments": {} |
|||
} |
|||
"""); |
|||
|
|||
doPost("/api/alarm/rule/testScript", request) |
|||
.andExpect(status().isBadRequest()); |
|||
} |
|||
|
|||
@Test |
|||
public void testTestAlarmRuleScript_invalidExpression() throws Exception { |
|||
JsonNode request = JacksonUtil.toJsonNode(""" |
|||
{ |
|||
"expression": "invalid syntax {{{{", |
|||
"arguments": {} |
|||
} |
|||
"""); |
|||
|
|||
JsonNode result = doPost("/api/alarm/rule/testScript", request, JsonNode.class); |
|||
|
|||
assertThat(result).isNotNull(); |
|||
assertThat(result.get("error").asText()).isNotEmpty(); |
|||
} |
|||
|
|||
// --- Helper methods ---
|
|||
|
|||
private AlarmRuleDefinition createTestAlarmRule(EntityId entityId, String name) { |
|||
AlarmRuleDefinition alarmRule = new AlarmRuleDefinition(); |
|||
alarmRule.setEntityId(entityId); |
|||
alarmRule.setName(name); |
|||
alarmRule.setConfigurationVersion(1); |
|||
alarmRule.setAdditionalInfo(JacksonUtil.newObjectNode()); |
|||
|
|||
AlarmCalculatedFieldConfiguration configuration = new AlarmCalculatedFieldConfiguration(); |
|||
|
|||
Argument argument = new Argument(); |
|||
argument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); |
|||
argument.setDefaultValue("0"); |
|||
configuration.setArguments(Map.of("temperature", argument)); |
|||
|
|||
AlarmRule rule = new AlarmRule(); |
|||
TbelAlarmConditionExpression expression = new TbelAlarmConditionExpression(); |
|||
expression.setExpression("return temperature >= 50;"); |
|||
SimpleAlarmCondition condition = new SimpleAlarmCondition(); |
|||
condition.setExpression(expression); |
|||
rule.setCondition(condition); |
|||
configuration.setCreateRules(Map.of(AlarmSeverity.CRITICAL, rule)); |
|||
|
|||
alarmRule.setConfiguration(configuration); |
|||
return alarmRule; |
|||
} |
|||
|
|||
private CalculatedField createSimpleCalculatedField(EntityId entityId) { |
|||
CalculatedField cf = new CalculatedField(); |
|||
cf.setEntityId(entityId); |
|||
cf.setType(CalculatedFieldType.SIMPLE); |
|||
cf.setName("Simple CF"); |
|||
cf.setConfigurationVersion(1); |
|||
cf.setAdditionalInfo(JacksonUtil.newObjectNode()); |
|||
|
|||
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration(); |
|||
Argument arg = new Argument(); |
|||
arg.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); |
|||
config.setArguments(Map.of("T", arg)); |
|||
config.setExpression("T * 2"); |
|||
TimeSeriesOutput output = new TimeSeriesOutput(); |
|||
output.setName("result"); |
|||
config.setOutput(output); |
|||
cf.setConfiguration(config); |
|||
|
|||
return cf; |
|||
} |
|||
|
|||
private List<AlarmRuleDefinitionInfo> getAlarmRules(EntityType entityType, List<UUID> entities) throws Exception { |
|||
StringBuilder url = new StringBuilder("/api/alarm/rules?"); |
|||
if (entityType != null) { |
|||
url.append("entityType=").append(entityType).append("&"); |
|||
} |
|||
if (entities != null) { |
|||
url.append("entities=").append(String.join(",", |
|||
entities.stream().map(UUID::toString).toList())).append("&"); |
|||
} |
|||
return doGetTypedWithPageLink(url.toString(), |
|||
new TypeReference<PageData<AlarmRuleDefinitionInfo>>() {}, new PageLink(10)).getData(); |
|||
} |
|||
|
|||
private PageData<String> getAlarmRuleNames(PageLink pageLink) throws Exception { |
|||
return doGetTypedWithPageLink("/api/alarm/rules/names?", |
|||
new TypeReference<PageData<String>>() {}, pageLink); |
|||
} |
|||
|
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue