Browse Source

added validation for config and tests

pull/14253/head
IrynaMatveieva 11 months ago
parent
commit
40e0d4a92f
  1. 84
      application/src/test/java/org/thingsboard/server/cf/EntityAggregationCalculatedFieldTest.java
  2. 57
      application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java
  3. 32
      common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/EntityAggregationCalculatedFieldConfiguration.java
  4. 2
      common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/interval/AggInterval.java
  5. 24
      common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/interval/BaseAggInterval.java
  6. 128
      common/data/src/test/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/EntityAggregationCalculatedFieldConfigurationTest.java
  7. 29
      common/data/src/test/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/interval/AggIntervalTest.java

84
application/src/test/java/org/thingsboard/server/cf/EntityAggregationCalculatedFieldTest.java

@ -93,41 +93,71 @@ public class EntityAggregationCalculatedFieldTest extends AbstractControllerTest
}
@Test
public void testCreateCf_checkAggregation() throws Exception {
public void testCreateCfAndNoTelemetryDuringInterval_checkAggregation() throws Exception {
Device device = createDevice("Device", "1234567890111");
CustomInterval customInterval = new CustomInterval("Europe/Kyiv", 0L, 30L);
CustomInterval customInterval = new CustomInterval("Europe/Kyiv", 0L, 5L);
long intervalEndTs = customInterval.getCurrentIntervalEndTs();
CalculatedField totalConsumptionCF = createTotalConsumptionCF(device.getId(), customInterval, null);
long interval = customInterval.getCurrentIntervalDurationMillis();
await().alias("create CF and no telemetry during interval -> save metric with default value")
.atMost(2 * interval, TimeUnit.MILLISECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
ObjectNode result = getLatestTelemetry(device.getId(), "consumption");
assertThat(result).isNotNull();
assertThat(result.get("consumption").get(0).get("value").asText()).isEqualTo("9999");
});
}
@Test
public void testCreateCfWithoutWatermark_checkAggregation() throws Exception {
Device device = createDevice("Device", "1234567890111");
CustomInterval customInterval = new CustomInterval("Europe/Kyiv", 0L, 5L);
long currentIntervalStartTs = customInterval.getCurrentIntervalStartTs();
long currentIntervalEndTs = customInterval.getCurrentIntervalEndTs();
long tsBeforeInterval = currentIntervalStartTs - 1000L;
long tsInInterval_1 = currentIntervalStartTs + 1000L;
long tsInInterval_2 = currentIntervalStartTs + 500L;
long tsInInterval_3 = currentIntervalStartTs + 200L;
long tsBeforeInterval = currentIntervalStartTs - 1000;
long tsInInterval_1 = currentIntervalStartTs + 1000;
long tsInInterval_2 = currentIntervalStartTs + 500;
long tsInInterval_3 = currentIntervalStartTs + 200;
postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":120}}", tsBeforeInterval));
postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":100}}", tsInInterval_1));
postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":180}}", tsInInterval_2));
postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":120}}", tsInInterval_3));
long interval = customInterval.getCurrentIntervalDurationMillis();
Watermark watermark = new Watermark(60);
CalculatedField totalConsumptionCF = createTotalConsumptionCF(device.getId(), customInterval, watermark);
CalculatedField totalConsumptionCF = createTotalConsumptionCF(device.getId(), customInterval, null);
await().alias("create CF -> perform aggregation after interval end")
.atMost(2 * interval, TimeUnit.MILLISECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
ObjectNode result = getLatestTelemetry(device.getId(), "consumption");
assertThat(result).isNotNull();
assertThat(result.get("consumption").get(0).get("value").asText()).isEqualTo("400");
});
postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":500}}", tsInInterval_1));
await().alias("create CF and perform aggregation after interval end")
await().alias("update telemetry that belongs to previous interval -> no aggregation since watermark is not set ")
.atMost(2 * interval, TimeUnit.MILLISECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
ObjectNode result = getLatestTelemetry(device.getId(), "consumptionPerMin");
ObjectNode result = getLatestTelemetry(device.getId(), "consumption");
assertThat(result).isNotNull();
assertThat(result.get("consumptionPerMin").get(0).get("value").asText()).isEqualTo("400");
assertThat(result.get("consumption").get(0).get("value").asText()).isEqualTo("400");
});
}
@Test
public void testCreateCf_checkAggregationDuringWatermark() throws Exception {
public void testCreateCfWithWatermark_checkAggregationDuringWatermark() throws Exception {
Device device = createDevice("Device", "1234567890111");
CustomInterval customInterval = new CustomInterval("Europe/Kyiv", 0L, 30L);
CustomInterval customInterval = new CustomInterval("Europe/Kyiv", 0L, 5L);
long currentIntervalStartTs = customInterval.getCurrentIntervalStartTs();
long currentIntervalEndTs = customInterval.getCurrentIntervalEndTs();
@ -141,27 +171,27 @@ public class EntityAggregationCalculatedFieldTest extends AbstractControllerTest
postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":120}}", tsInInterval_3));
long interval = customInterval.getCurrentIntervalDurationMillis();
Watermark watermark = new Watermark(60);
Watermark watermark = new Watermark(10);
CalculatedField totalConsumptionCF = createTotalConsumptionCF(device.getId(), customInterval, watermark);
await().alias("create CF and perform aggregation after interval end")
await().alias("create CF -> perform aggregation after interval end")
.atMost(2 * interval, TimeUnit.MILLISECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
ObjectNode result = getLatestTelemetry(device.getId(), "consumptionPerMin");
ObjectNode result = getLatestTelemetry(device.getId(), "consumption");
assertThat(result).isNotNull();
assertThat(result.get("consumptionPerMin").get(0).get("value").asText()).isEqualTo("400");
assertThat(result.get("consumption").get(0).get("value").asText()).isEqualTo("400");
});
postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":300}}", tsInInterval_1));
await().alias("create CF and perform aggregation after interval end")
await().alias("update telemetry during watermark -> perform aggregation")
.atMost(2 * 10, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
ObjectNode result = getLatestTelemetry(device.getId(), "consumptionPerMin");
ObjectNode result = getLatestTelemetry(device.getId(), "consumption");
assertThat(result).isNotNull();
assertThat(result.get("consumptionPerMin").get(0).get("value").asText()).isEqualTo("600");
assertThat(result.get("consumption").get(0).get("value").asText()).isEqualTo("600");
});
}
@ -169,15 +199,15 @@ public class EntityAggregationCalculatedFieldTest extends AbstractControllerTest
Map<String, Argument> arguments = new HashMap<>();
Argument argument = new Argument();
argument.setRefEntityKey(new ReferencedEntityKey("energy", ArgumentType.TS_LATEST, null));
argument.setLimit(100);
arguments.put("en", argument);
Map<String, AggMetric> aggMetrics = new HashMap<>();
AggMetric consumptionPerMin = new AggMetric();
consumptionPerMin.setFunction(AggFunction.SUM);
consumptionPerMin.setInput(new AggKeyInput("en"));
aggMetrics.put("consumptionPerMin", consumptionPerMin);
AggMetric consumption = new AggMetric();
consumption.setFunction(AggFunction.SUM);
consumption.setInput(new AggKeyInput("en"));
consumption.setDefaultValue(9999L);
aggMetrics.put("consumption", consumption);
Output output = new Output();
output.setType(OutputType.TIME_SERIES);
@ -208,7 +238,9 @@ public class EntityAggregationCalculatedFieldTest extends AbstractControllerTest
configuration.setArguments(inputs);
configuration.setMetrics(metrics);
configuration.setInterval(aggInterval);
configuration.setWatermark(watermark);
if (watermark != null) {
configuration.setWatermark(watermark);
}
configuration.setOutput(output);
calculatedField.setConfiguration(configuration);

57
application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java

@ -32,6 +32,11 @@ import org.thingsboard.server.common.data.cf.configuration.PropagationCalculated
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
import org.thingsboard.server.common.data.cf.configuration.RelationPathQueryDynamicSourceConfiguration;
import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.aggregation.AggKeyInput;
import org.thingsboard.server.common.data.cf.configuration.aggregation.AggMetric;
import org.thingsboard.server.common.data.cf.configuration.aggregation.single.EntityAggregationCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.aggregation.single.interval.HourInterval;
import org.thingsboard.server.common.data.cf.configuration.aggregation.single.interval.Watermark;
import org.thingsboard.server.common.data.cf.configuration.geofencing.EntityCoordinates;
import org.thingsboard.server.common.data.cf.configuration.geofencing.GeofencingCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.geofencing.ZoneGroupConfiguration;
@ -45,6 +50,7 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
@ -166,6 +172,34 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
.andExpect(status().isOk());
}
@Test
public void testSaveEntityAggregationCalculatedField() throws Exception {
Device testDevice = createDevice("Test device", "1234567890");
CalculatedField calculatedField = getCalculatedField(testDevice.getId(), CalculatedFieldType.ENTITY_AGGREGATION);
CalculatedField savedCalculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class);
assertThat(savedCalculatedField).isNotNull();
assertThat(savedCalculatedField.getId()).isNotNull();
assertThat(savedCalculatedField.getCreatedTime()).isGreaterThan(0);
assertThat(savedCalculatedField.getTenantId()).isEqualTo(savedTenant.getId());
assertThat(savedCalculatedField.getEntityId()).isEqualTo(calculatedField.getEntityId());
assertThat(savedCalculatedField.getType()).isEqualTo(calculatedField.getType());
assertThat(savedCalculatedField.getName()).isEqualTo(calculatedField.getName());
assertThat(savedCalculatedField.getConfiguration()).isEqualTo(getEntityAggregationCalculatedFieldConfig());
assertThat(savedCalculatedField.getVersion()).isEqualTo(1L);
savedCalculatedField.setName("Test CF");
CalculatedField updatedCalculatedField = doPost("/api/calculatedField", savedCalculatedField, CalculatedField.class);
assertThat(updatedCalculatedField.getName()).isEqualTo(savedCalculatedField.getName());
assertThat(updatedCalculatedField.getVersion()).isEqualTo(savedCalculatedField.getVersion() + 1);
doDelete("/api/calculatedField/" + savedCalculatedField.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testSavePropagationCalculatedFieldWithNullArguments() throws Exception {
Device testDevice = createDevice("Test device", "1234567890");
@ -237,6 +271,7 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
case SIMPLE -> calculatedField.setConfiguration(getSimpleCalculatedFieldConfig());
case GEOFENCING -> calculatedField.setConfiguration(getGeofencingCalculatedFieldConfig());
case PROPAGATION -> calculatedField.setConfiguration(getPropagationCalculatedFieldConfig());
case ENTITY_AGGREGATION -> calculatedField.setConfiguration(getEntityAggregationCalculatedFieldConfig());
}
calculatedField.setVersion(1L);
return calculatedField;
@ -287,6 +322,28 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
return config;
}
private CalculatedFieldConfiguration getEntityAggregationCalculatedFieldConfig() {
var config = new EntityAggregationCalculatedFieldConfiguration();
Argument energyArgument = new Argument();
energyArgument.setRefEntityKey(new ReferencedEntityKey("energy", ArgumentType.TS_LATEST, null));
config.setArguments(Map.of("en", energyArgument));
AggMetric metric = new AggMetric();
metric.setInput(new AggKeyInput("en"));
metric.setDefaultValue(9999L);
config.setMetrics(Map.of("consumption", metric));
config.setWatermark(new Watermark(TimeUnit.DAYS.toSeconds(1)));
config.setInterval(new HourInterval("Europe/Kiev", TimeUnit.MINUTES.toSeconds(15)));
Output output = new Output();
output.setType(OutputType.TIME_SERIES);
config.setOutput(output);
return config;
}
private CalculatedFieldConfiguration getSimpleCalculatedFieldConfig() {
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration();

32
common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/EntityAggregationCalculatedFieldConfiguration.java

@ -24,6 +24,7 @@ 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.ArgumentsBasedCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.Output;
import org.thingsboard.server.common.data.cf.configuration.aggregation.AggKeyInput;
import org.thingsboard.server.common.data.cf.configuration.aggregation.AggMetric;
import org.thingsboard.server.common.data.cf.configuration.aggregation.single.interval.AggInterval;
import org.thingsboard.server.common.data.cf.configuration.aggregation.single.interval.Watermark;
@ -53,6 +54,12 @@ public class EntityAggregationCalculatedFieldConfiguration implements ArgumentsB
@Override
public void validate() {
validateArguments();
validateMetrics();
validateInterval();
}
private void validateArguments() {
if (arguments.containsKey("ctx")) {
throw new IllegalArgumentException("Argument name 'ctx' is reserved and cannot be used.");
}
@ -61,4 +68,29 @@ public class EntityAggregationCalculatedFieldConfiguration implements ArgumentsB
}
}
private void validateMetrics() {
if (metrics == null || metrics.isEmpty()) {
throw new IllegalArgumentException("Metrics map cannot be empty.");
}
for (AggMetric metric : metrics.values()) {
if (metric.getInput() instanceof AggKeyInput aggKeyInput) {
if (!arguments.containsKey(aggKeyInput.getKey())) {
throw new IllegalArgumentException(
"Metric references unknown argument: '" + aggKeyInput.getKey() + "'."
);
}
} else {
throw new IllegalArgumentException("Metric key can only refer to argument.");
}
}
}
private void validateInterval() {
if (interval == null) {
throw new IllegalArgumentException("Interval must be defined.");
}
interval.validate();
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/interval/AggInterval.java

@ -62,4 +62,6 @@ public interface AggInterval {
ZonedDateTime getNextIntervalStart(ZonedDateTime currentStart);
void validate();
}

24
common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/interval/BaseAggInterval.java

@ -23,6 +23,7 @@ import lombok.NoArgsConstructor;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.concurrent.TimeUnit;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@ -39,7 +40,7 @@ public abstract class BaseAggInterval implements AggInterval {
return ZoneId.of(tz);
}
protected long getOffset() {
protected long getOffsetSafe() {
return offsetSec != null ? offsetSec : 0L;
}
@ -57,7 +58,7 @@ public abstract class BaseAggInterval implements AggInterval {
@Override
public long getDateTimeIntervalStartTs(ZonedDateTime dateTime) {
long offset = getOffset();
long offset = getOffsetSafe();
ZonedDateTime shiftedNow = dateTime.minusSeconds(offset);
ZonedDateTime alignedStart = getAlignedBoundary(shiftedNow, false);
ZonedDateTime actualStart = alignedStart.plusSeconds(offset);
@ -73,7 +74,7 @@ public abstract class BaseAggInterval implements AggInterval {
@Override
public long getDateTimeIntervalEndTs(ZonedDateTime dateTime) {
long offset = getOffset();
long offset = getOffsetSafe();
ZonedDateTime shiftedNow = dateTime.minusSeconds(offset);
ZonedDateTime alignedEnd = getAlignedBoundary(shiftedNow, true);
ZonedDateTime actualEnd = alignedEnd.plusSeconds(offset);
@ -82,4 +83,21 @@ public abstract class BaseAggInterval implements AggInterval {
protected abstract ZonedDateTime getAlignedBoundary(ZonedDateTime reference, boolean next);
@Override
public void validate() {
try {
getZoneId();
} catch (Exception ex) {
throw new IllegalArgumentException("Invalid timezone in interval: " + ex.getMessage());
}
if (offsetSec != null) {
if (offsetSec < 0) {
throw new IllegalArgumentException("Offset cannot be negative.");
}
if (TimeUnit.SECONDS.toMillis(offsetSec) >= getCurrentIntervalDurationMillis()) {
throw new IllegalArgumentException("Offset must be greater than interval duration.");
}
}
}
}

128
common/data/src/test/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/EntityAggregationCalculatedFieldConfigurationTest.java

@ -0,0 +1,128 @@
/**
* 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.common.data.cf.configuration.aggregation.single;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
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.Output;
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
import org.thingsboard.server.common.data.cf.configuration.aggregation.AggFunctionInput;
import org.thingsboard.server.common.data.cf.configuration.aggregation.AggKeyInput;
import org.thingsboard.server.common.data.cf.configuration.aggregation.AggMetric;
import org.thingsboard.server.common.data.cf.configuration.aggregation.single.interval.HourInterval;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class EntityAggregationCalculatedFieldConfigurationTest {
@Test
void typeShouldBeEntityAggregation() {
var cfg = new EntityAggregationCalculatedFieldConfiguration();
assertThat(cfg.getType()).isEqualTo(CalculatedFieldType.ENTITY_AGGREGATION);
}
@ParameterizedTest
@ValueSource(strings = {"ATTRIBUTE", "TS_ROLLING"})
void validateShouldThrowWhenNotTsLatestArgumentUsed(String argumentType) {
var cfg = new EntityAggregationCalculatedFieldConfiguration();
cfg.setArguments(Map.of("k", validArgument(ArgumentType.valueOf(argumentType))));
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Calculated field with type: '" + cfg.getType() + "' support only TS_LATEST arguments.");
}
@Test
void validateShouldThrowWhenMetricMapIsEmpty() {
var cfg = new EntityAggregationCalculatedFieldConfiguration();
cfg.setArguments(Map.of("k", validArgument(ArgumentType.TS_LATEST)));
cfg.setMetrics(Map.of());
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Metrics map cannot be empty.");
}
@Test
void validateShouldThrowWhenMetricInputIsNotAggKeyInput() {
var cfg = new EntityAggregationCalculatedFieldConfiguration();
cfg.setArguments(Map.of("k", validArgument(ArgumentType.TS_LATEST)));
AggMetric metric = new AggMetric();
metric.setInput(new AggFunctionInput()); // cannot be function
cfg.setMetrics(Map.of("m", metric));
cfg.setInterval(new HourInterval("Europe/Kiev", null));
cfg.setOutput(new Output());
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Metric key can only refer to argument.");
}
@Test
void validateShouldThrowWhenMetricReferencesUnknownArgument() {
var cfg = new EntityAggregationCalculatedFieldConfiguration();
cfg.setArguments(Map.of("k", validArgument(ArgumentType.TS_LATEST)));
AggMetric metric = new AggMetric();
metric.setInput(new AggKeyInput("unknown"));
cfg.setMetrics(Map.of("m", metric));
cfg.setInterval(new HourInterval("Europe/Kiev", null));
cfg.setOutput(new Output());
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Metric references unknown argument: 'unknown'.");
}
@Test
void validateShouldThrowWhenIntervalIsNull() {
var cfg = new EntityAggregationCalculatedFieldConfiguration();
cfg.setArguments(Map.of("k", validArgument(ArgumentType.TS_LATEST)));
cfg.setMetrics(Map.of("m", validMetric()));
cfg.setInterval(null);
cfg.setOutput(new Output());
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Interval must be defined.");
}
private Argument validArgument(ArgumentType type) {
Argument a = new Argument();
a.setRefEntityKey(new ReferencedEntityKey("key", type, null));
return a;
}
private AggMetric validMetric() {
AggMetric metric = new AggMetric();
metric.setInput(new AggKeyInput("k"));
return metric;
}
}

29
common/data/src/test/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/interval/AggIntervalTest.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.cf.configuration.aggregation.single.interval;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@ -29,11 +30,39 @@ import java.util.function.LongFunction;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class AggIntervalTest {
private static final String TZ = "Europe/Kiev";
@Test
void validateShouldThrowWhenInvalidTimZone() {
AggInterval interval = new HourInterval("TimeZone", null);
assertThatThrownBy(interval::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Invalid timezone in interval: ");
}
@Test
void validateShouldThrowWhenOffsetIsNegative() {
AggInterval interval = new CustomInterval(TZ, -100L, TimeUnit.HOURS.toSeconds(2));
assertThatThrownBy(interval::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Offset cannot be negative.");
}
@Test
void validateShouldThrowWhenOffsetGreaterThanIntervalDuration() {
AggInterval interval = new CustomInterval(TZ, TimeUnit.HOURS.toSeconds(2), TimeUnit.HOURS.toSeconds(2));
assertThatThrownBy(interval::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Offset must be greater than interval duration.");
}
@ParameterizedTest
@MethodSource("intervals")
void testGetStartAndEndWithoutOffset(LongFunction<AggInterval> intervalCreator) {

Loading…
Cancel
Save