9 changed files with 216 additions and 44 deletions
@ -0,0 +1,200 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.cf; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.node.ObjectNode; |
||||
|
import org.junit.Test; |
||||
|
import org.thingsboard.server.common.data.AttributeScope; |
||||
|
import org.thingsboard.server.common.data.Device; |
||||
|
import org.thingsboard.server.common.data.asset.Asset; |
||||
|
import org.thingsboard.server.common.data.asset.AssetProfile; |
||||
|
import org.thingsboard.server.common.data.cf.CalculatedField; |
||||
|
import org.thingsboard.server.common.data.cf.CalculatedFieldType; |
||||
|
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.CurrentOwnerDynamicSourceConfiguration; |
||||
|
import org.thingsboard.server.common.data.cf.configuration.Output; |
||||
|
import org.thingsboard.server.common.data.cf.configuration.OutputType; |
||||
|
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey; |
||||
|
import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration; |
||||
|
import org.thingsboard.server.common.data.id.AssetProfileId; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.controller.AbstractControllerTest; |
||||
|
import org.thingsboard.server.dao.service.DaoSqlTest; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
import static org.awaitility.Awaitility.await; |
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
|
||||
|
@DaoSqlTest |
||||
|
public class CalculatedFieldCurrentOwnerTest extends AbstractControllerTest { |
||||
|
|
||||
|
public static final int TIMEOUT = 60; |
||||
|
public static final int POLL_INTERVAL = 1; |
||||
|
|
||||
|
@Test |
||||
|
public void testCreateCFWithCurrentOwner() throws Exception { |
||||
|
loginTenantAdmin(); |
||||
|
|
||||
|
postAttributes(customerId, AttributeScope.SERVER_SCOPE, "{\"attrKey\":5}"); |
||||
|
|
||||
|
Device testDevice = createDevice("Test device", "1234567890"); |
||||
|
|
||||
|
doPost("/api/customer/" + customerId.getId() + "/device/" + testDevice.getId().getId()).andExpect(status().isOk()); |
||||
|
|
||||
|
CalculatedField savedCalculatedField = doPost("/api/calculatedField", buildCalculatedField(testDevice.getId()), CalculatedField.class); |
||||
|
|
||||
|
await().alias("create CF -> perform initial calculation").atMost(TIMEOUT, TimeUnit.SECONDS) |
||||
|
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) |
||||
|
.untilAsserted(() -> { |
||||
|
ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "result"); |
||||
|
assertThat(fahrenheitTemp).isNotNull(); |
||||
|
assertThat(fahrenheitTemp.get("result").get(0).get("value").asText()).isEqualTo("105"); |
||||
|
}); |
||||
|
|
||||
|
postAttributes(customerId, AttributeScope.SERVER_SCOPE, "{\"attrKey\":10}"); |
||||
|
|
||||
|
await().alias("update telemetry -> perform calculation").atMost(TIMEOUT, TimeUnit.SECONDS) |
||||
|
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) |
||||
|
.untilAsserted(() -> { |
||||
|
ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "result"); |
||||
|
assertThat(fahrenheitTemp).isNotNull(); |
||||
|
assertThat(fahrenheitTemp.get("result").get(0).get("value").asText()).isEqualTo("110"); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testChangeOwner() throws Exception { |
||||
|
loginSysAdmin(); |
||||
|
|
||||
|
postAttributes(tenantId, AttributeScope.SERVER_SCOPE, "{\"attrKey\":50}"); |
||||
|
|
||||
|
loginTenantAdmin(); |
||||
|
|
||||
|
postAttributes(customerId, AttributeScope.SERVER_SCOPE, "{\"attrKey\":5}"); |
||||
|
Device testDevice = createDevice("Test device", "1234567890"); |
||||
|
doPost("/api/customer/" + customerId.getId() + "/device/" + testDevice.getId().getId()).andExpect(status().isOk()); |
||||
|
|
||||
|
|
||||
|
CalculatedField savedCalculatedField = doPost("/api/calculatedField", buildCalculatedField(testDevice.getId()), CalculatedField.class); |
||||
|
|
||||
|
await().alias("create CF -> perform initial calculation").atMost(TIMEOUT, TimeUnit.SECONDS) |
||||
|
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) |
||||
|
.untilAsserted(() -> { |
||||
|
ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "result"); |
||||
|
assertThat(fahrenheitTemp).isNotNull(); |
||||
|
assertThat(fahrenheitTemp.get("result").get(0).get("value").asText()).isEqualTo("105"); |
||||
|
}); |
||||
|
|
||||
|
doDelete("/api/customer/device/" + testDevice.getId().getId()).andExpect(status().isOk()); |
||||
|
|
||||
|
await().alias("change owner -> perform calculation").atMost(TIMEOUT, TimeUnit.SECONDS) |
||||
|
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) |
||||
|
.untilAsserted(() -> { |
||||
|
ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "result"); |
||||
|
assertThat(fahrenheitTemp).isNotNull(); |
||||
|
assertThat(fahrenheitTemp.get("result").get(0).get("value").asText()).isEqualTo("150"); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testCreateCFWithCurrentOwnerWhenEntityIsProfile() throws Exception { |
||||
|
loginSysAdmin(); |
||||
|
|
||||
|
postAttributes(tenantId, AttributeScope.SERVER_SCOPE, "{\"attrKey\":50}"); |
||||
|
|
||||
|
loginTenantAdmin(); |
||||
|
|
||||
|
postAttributes(customerId, AttributeScope.SERVER_SCOPE, "{\"attrKey\":5}"); |
||||
|
|
||||
|
AssetProfile assetProfile = doPost("/api/assetProfile", createAssetProfile("Test Asset Profile"), AssetProfile.class); |
||||
|
|
||||
|
Asset asset1 = createAsset("Test asset 1", assetProfile.getId()); |
||||
|
doPost("/api/customer/" + customerId.getId() + "/asset/" + asset1.getId().getId()).andExpect(status().isOk()); |
||||
|
|
||||
|
Asset asset2 = createAsset("Test asset 2", assetProfile.getId()); // owner - TENANT
|
||||
|
|
||||
|
CalculatedField savedCalculatedField = doPost("/api/calculatedField", buildCalculatedField(assetProfile.getId()), CalculatedField.class); |
||||
|
|
||||
|
await().alias("create CF -> perform initial calculation").atMost(TIMEOUT, TimeUnit.SECONDS) |
||||
|
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) |
||||
|
.untilAsserted(() -> { |
||||
|
// result of asset 1
|
||||
|
ObjectNode result1 = getLatestTelemetry(asset1.getId(), "result"); |
||||
|
assertThat(result1).isNotNull(); |
||||
|
assertThat(result1.get("result").get(0).get("value").asText()).isEqualTo("105"); |
||||
|
|
||||
|
// result of asset 2
|
||||
|
ObjectNode result2 = getLatestTelemetry(asset2.getId(), "result"); |
||||
|
assertThat(result2).isNotNull(); |
||||
|
assertThat(result2.get("result").get(0).get("value").asText()).isEqualTo("150"); |
||||
|
}); |
||||
|
|
||||
|
doPost("/api/customer/" + customerId.getId() + "/asset/" + asset2.getId().getId()).andExpect(status().isOk()); |
||||
|
|
||||
|
await().alias("change asset2 owner -> recalculate state for asset 2").atMost(TIMEOUT, TimeUnit.SECONDS) |
||||
|
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) |
||||
|
.untilAsserted(() -> { |
||||
|
// result of asset 2
|
||||
|
ObjectNode result2 = getLatestTelemetry(asset2.getId(), "result"); |
||||
|
assertThat(result2).isNotNull(); |
||||
|
assertThat(result2.get("result").get(0).get("value").asText()).isEqualTo("105"); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private CalculatedField buildCalculatedField(EntityId entityId) { |
||||
|
CalculatedField calculatedField = new CalculatedField(); |
||||
|
calculatedField.setEntityId(entityId); |
||||
|
calculatedField.setType(CalculatedFieldType.SIMPLE); |
||||
|
calculatedField.setName("Test Calculated Field"); |
||||
|
|
||||
|
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration(); |
||||
|
|
||||
|
Argument argument = new Argument(); |
||||
|
ReferencedEntityKey refEntityKey = new ReferencedEntityKey("attrKey", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE); |
||||
|
argument.setRefEntityKey(refEntityKey); |
||||
|
argument.setRefDynamicSourceConfiguration(new CurrentOwnerDynamicSourceConfiguration()); |
||||
|
|
||||
|
config.setArguments(Map.of("a", argument)); |
||||
|
|
||||
|
config.setExpression("a + 100"); |
||||
|
|
||||
|
Output output = new Output(); |
||||
|
output.setName("result"); |
||||
|
output.setType(OutputType.TIME_SERIES); |
||||
|
output.setDecimalsByDefault(0); |
||||
|
|
||||
|
config.setOutput(output); |
||||
|
|
||||
|
calculatedField.setConfiguration(config); |
||||
|
return calculatedField; |
||||
|
} |
||||
|
|
||||
|
private ObjectNode getLatestTelemetry(EntityId entityId, String... keys) throws Exception { |
||||
|
return doGetAsync("/api/plugins/telemetry/" + entityId.getEntityType() + "/" + entityId.getId() + "/values/timeseries?keys=" + String.join(",", keys), ObjectNode.class); |
||||
|
} |
||||
|
|
||||
|
private Asset createAsset(String name, AssetProfileId assetProfileId) { |
||||
|
Asset asset = new Asset(); |
||||
|
asset.setName(name); |
||||
|
asset.setAssetProfileId(assetProfileId); |
||||
|
return doPost("/api/asset", asset, Asset.class); |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue