Save time series strategies: add tests for changes in telemetry service
This commit is contained in:
		
							parent
							
								
									631d314fce
								
							
						
					
					
						commit
						d1acba40a2
					
				@ -350,6 +350,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
 | 
			
		||||
                        .entries(latestValues)
 | 
			
		||||
                        .saveTimeseries(false)
 | 
			
		||||
                        .saveLatest(true)
 | 
			
		||||
                        .sendWsUpdate(true)
 | 
			
		||||
                        .callback(new FutureCallback<Void>() {
 | 
			
		||||
                            @Override
 | 
			
		||||
                            public void onSuccess(@Nullable Void tmp) {
 | 
			
		||||
 | 
			
		||||
@ -149,8 +149,8 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
 | 
			
		||||
        if (request.isSendWsUpdate()) {
 | 
			
		||||
            addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, request.getEntries()));
 | 
			
		||||
        }
 | 
			
		||||
        if (request.isSaveTimeseries() && request.isSaveLatest()) {
 | 
			
		||||
            addEntityViewCallback(tenantId, entityId, request.getEntries());
 | 
			
		||||
        if (request.isSaveLatest()) {
 | 
			
		||||
            copyLatestToEntityViews(tenantId, entityId, request.getEntries());
 | 
			
		||||
        }
 | 
			
		||||
        return saveFuture;
 | 
			
		||||
    }
 | 
			
		||||
@ -205,7 +205,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void addEntityViewCallback(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts) {
 | 
			
		||||
    private void copyLatestToEntityViews(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts) {
 | 
			
		||||
        if (EntityType.DEVICE.equals(entityId.getEntityType()) || EntityType.ASSET.equals(entityId.getEntityType())) {
 | 
			
		||||
            Futures.addCallback(this.tbEntityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId),
 | 
			
		||||
                    new FutureCallback<>() {
 | 
			
		||||
@ -238,6 +238,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
 | 
			
		||||
                                                .entries(entityViewLatest)
 | 
			
		||||
                                                .saveTimeseries(false)
 | 
			
		||||
                                                .saveLatest(true)
 | 
			
		||||
                                                .sendWsUpdate(true)
 | 
			
		||||
                                                .callback(new FutureCallback<>() {
 | 
			
		||||
                                                    @Override
 | 
			
		||||
                                                    public void onSuccess(@Nullable Void tmp) {}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,387 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2024 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.service.telemetry;
 | 
			
		||||
 | 
			
		||||
import com.google.common.util.concurrent.FutureCallback;
 | 
			
		||||
import com.google.common.util.concurrent.MoreExecutors;
 | 
			
		||||
import com.google.common.util.concurrent.SettableFuture;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.junit.jupiter.api.AfterEach;
 | 
			
		||||
import org.junit.jupiter.api.BeforeEach;
 | 
			
		||||
import org.junit.jupiter.api.Test;
 | 
			
		||||
import org.junit.jupiter.api.extension.ExtendWith;
 | 
			
		||||
import org.junit.jupiter.params.ParameterizedTest;
 | 
			
		||||
import org.junit.jupiter.params.provider.Arguments;
 | 
			
		||||
import org.junit.jupiter.params.provider.MethodSource;
 | 
			
		||||
import org.mockito.Mock;
 | 
			
		||||
import org.mockito.junit.jupiter.MockitoExtension;
 | 
			
		||||
import org.springframework.test.util.ReflectionTestUtils;
 | 
			
		||||
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
 | 
			
		||||
import org.thingsboard.server.cluster.TbClusterService;
 | 
			
		||||
import org.thingsboard.server.common.data.ApiUsageRecordKey;
 | 
			
		||||
import org.thingsboard.server.common.data.ApiUsageState;
 | 
			
		||||
import org.thingsboard.server.common.data.ApiUsageStateValue;
 | 
			
		||||
import org.thingsboard.server.common.data.EntityView;
 | 
			
		||||
import org.thingsboard.server.common.data.id.CustomerId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.DeviceId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.EntityId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.EntityViewId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
 | 
			
		||||
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
 | 
			
		||||
import org.thingsboard.server.common.data.kv.KvEntry;
 | 
			
		||||
import org.thingsboard.server.common.data.kv.TsKvEntry;
 | 
			
		||||
import org.thingsboard.server.common.data.objects.AttributesEntityView;
 | 
			
		||||
import org.thingsboard.server.common.data.objects.TelemetryEntityView;
 | 
			
		||||
import org.thingsboard.server.common.msg.queue.ServiceType;
 | 
			
		||||
import org.thingsboard.server.common.msg.queue.TbCallback;
 | 
			
		||||
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
 | 
			
		||||
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
 | 
			
		||||
import org.thingsboard.server.dao.attributes.AttributesService;
 | 
			
		||||
import org.thingsboard.server.dao.timeseries.TimeseriesService;
 | 
			
		||||
import org.thingsboard.server.queue.discovery.PartitionService;
 | 
			
		||||
import org.thingsboard.server.queue.discovery.QueueKey;
 | 
			
		||||
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
 | 
			
		||||
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
 | 
			
		||||
import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService;
 | 
			
		||||
import org.thingsboard.server.service.subscription.SubscriptionManagerService;
 | 
			
		||||
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.ExecutorService;
 | 
			
		||||
import java.util.stream.LongStream;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
import static com.google.common.util.concurrent.Futures.immediateFuture;
 | 
			
		||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
			
		||||
import static org.mockito.BDDMockito.given;
 | 
			
		||||
import static org.mockito.BDDMockito.then;
 | 
			
		||||
import static org.mockito.Mockito.lenient;
 | 
			
		||||
 | 
			
		||||
@ExtendWith(MockitoExtension.class)
 | 
			
		||||
class DefaultTelemetrySubscriptionServiceTest {
 | 
			
		||||
 | 
			
		||||
    final TenantId tenantId = TenantId.fromUUID(UUID.fromString("a00ec470-c6b4-11ef-8c88-63b5533fb5bc"));
 | 
			
		||||
    final CustomerId customerId = new CustomerId(UUID.fromString("7bdc9750-c775-11ef-8e03-ff69ed8da327"));
 | 
			
		||||
    final EntityId entityId = DeviceId.fromString("cc51e450-53e1-11ee-883e-e56b48fd2088");
 | 
			
		||||
 | 
			
		||||
    final long sampleTtl = 10_000L;
 | 
			
		||||
 | 
			
		||||
    final List<TsKvEntry> sampleTelemetry = List.of(
 | 
			
		||||
            new BasicTsKvEntry(100L, new DoubleDataEntry("temperature", 65.2)),
 | 
			
		||||
            new BasicTsKvEntry(100L, new DoubleDataEntry("humidity", 33.1))
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    ApiUsageState apiUsageState;
 | 
			
		||||
 | 
			
		||||
    final TopicPartitionInfo tpi = TopicPartitionInfo.builder()
 | 
			
		||||
            .tenantId(tenantId)
 | 
			
		||||
            .myPartition(true)
 | 
			
		||||
            .build();
 | 
			
		||||
 | 
			
		||||
    final FutureCallback<Void> emptyCallback = new FutureCallback<>() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onSuccess(Void result) {}
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onFailure(@NonNull Throwable t) {}
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ExecutorService wsCallBackExecutor;
 | 
			
		||||
    ExecutorService tsCallBackExecutor;
 | 
			
		||||
 | 
			
		||||
    @Mock
 | 
			
		||||
    TbClusterService clusterService;
 | 
			
		||||
    @Mock
 | 
			
		||||
    PartitionService partitionService;
 | 
			
		||||
    @Mock
 | 
			
		||||
    SubscriptionManagerService subscriptionManagerService;
 | 
			
		||||
    @Mock
 | 
			
		||||
    AttributesService attrService;
 | 
			
		||||
    @Mock
 | 
			
		||||
    TimeseriesService tsService;
 | 
			
		||||
    @Mock
 | 
			
		||||
    TbEntityViewService tbEntityViewService;
 | 
			
		||||
    @Mock
 | 
			
		||||
    TbApiUsageReportClient apiUsageClient;
 | 
			
		||||
    @Mock
 | 
			
		||||
    TbApiUsageStateService apiUsageStateService;
 | 
			
		||||
 | 
			
		||||
    DefaultTelemetrySubscriptionService telemetryService;
 | 
			
		||||
 | 
			
		||||
    @BeforeEach
 | 
			
		||||
    void setup() {
 | 
			
		||||
        telemetryService = new DefaultTelemetrySubscriptionService(attrService, tsService, tbEntityViewService, apiUsageClient, apiUsageStateService);
 | 
			
		||||
        ReflectionTestUtils.setField(telemetryService, "clusterService", clusterService);
 | 
			
		||||
        ReflectionTestUtils.setField(telemetryService, "partitionService", partitionService);
 | 
			
		||||
        ReflectionTestUtils.setField(telemetryService, "subscriptionManagerService", Optional.of(subscriptionManagerService));
 | 
			
		||||
 | 
			
		||||
        wsCallBackExecutor = MoreExecutors.newDirectExecutorService();
 | 
			
		||||
        ReflectionTestUtils.setField(telemetryService, "wsCallBackExecutor", wsCallBackExecutor);
 | 
			
		||||
 | 
			
		||||
        tsCallBackExecutor = MoreExecutors.newDirectExecutorService();
 | 
			
		||||
        ReflectionTestUtils.setField(telemetryService, "tsCallBackExecutor", tsCallBackExecutor);
 | 
			
		||||
 | 
			
		||||
        apiUsageState = new ApiUsageState();
 | 
			
		||||
        apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED);
 | 
			
		||||
        lenient().when(apiUsageStateService.getApiUsageState(tenantId)).thenReturn(apiUsageState);
 | 
			
		||||
 | 
			
		||||
        lenient().when(partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId)).thenReturn(tpi);
 | 
			
		||||
 | 
			
		||||
        lenient().when(tsService.save(tenantId, entityId, sampleTelemetry, sampleTtl)).thenReturn(immediateFuture(sampleTelemetry.size()));
 | 
			
		||||
        lenient().when(tsService.saveWithoutLatest(tenantId, entityId, sampleTelemetry, sampleTtl)).thenReturn(immediateFuture(sampleTelemetry.size()));
 | 
			
		||||
        lenient().when(tsService.saveLatest(tenantId, entityId, sampleTelemetry)).thenReturn(immediateFuture(listOfNNumbers(sampleTelemetry.size())));
 | 
			
		||||
 | 
			
		||||
        // mock no entity views
 | 
			
		||||
        lenient().when(tbEntityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId)).thenReturn(immediateFuture(Collections.emptyList()));
 | 
			
		||||
 | 
			
		||||
        // send partition change event so currentPartitions set is populated
 | 
			
		||||
        telemetryService.onTbApplicationEvent(new PartitionChangeEvent(this, ServiceType.TB_CORE, Map.of(new QueueKey(ServiceType.TB_CORE), Set.of(tpi))));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @AfterEach
 | 
			
		||||
    void cleanup() {
 | 
			
		||||
        wsCallBackExecutor.shutdownNow();
 | 
			
		||||
        tsCallBackExecutor.shutdownNow();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void shouldReportStorageDataPointsApiUsageWhenTimeSeriesIsSaved() {
 | 
			
		||||
        // GIVEN
 | 
			
		||||
        var request = TimeseriesSaveRequest.builder()
 | 
			
		||||
                .tenantId(tenantId)
 | 
			
		||||
                .customerId(customerId)
 | 
			
		||||
                .entityId(entityId)
 | 
			
		||||
                .entries(sampleTelemetry)
 | 
			
		||||
                .ttl(sampleTtl)
 | 
			
		||||
                .saveTimeseries(true)
 | 
			
		||||
                .saveLatest(false)
 | 
			
		||||
                .sendWsUpdate(false)
 | 
			
		||||
                .callback(emptyCallback)
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        // WHEN
 | 
			
		||||
        telemetryService.saveTimeseries(request);
 | 
			
		||||
 | 
			
		||||
        // THEN
 | 
			
		||||
        then(apiUsageClient).should().report(tenantId, customerId, ApiUsageRecordKey.STORAGE_DP_COUNT, sampleTelemetry.size());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void shouldNotReportStorageDataPointsApiUsageWhenTimeSeriesIsNotSaved() {
 | 
			
		||||
        // GIVEN
 | 
			
		||||
        var request = TimeseriesSaveRequest.builder()
 | 
			
		||||
                .tenantId(tenantId)
 | 
			
		||||
                .customerId(customerId)
 | 
			
		||||
                .entityId(entityId)
 | 
			
		||||
                .entries(sampleTelemetry)
 | 
			
		||||
                .ttl(sampleTtl)
 | 
			
		||||
                .saveTimeseries(false)
 | 
			
		||||
                .saveLatest(true)
 | 
			
		||||
                .sendWsUpdate(true)
 | 
			
		||||
                .callback(emptyCallback)
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        // WHEN
 | 
			
		||||
        telemetryService.saveTimeseries(request);
 | 
			
		||||
 | 
			
		||||
        // THEN
 | 
			
		||||
        then(apiUsageClient).shouldHaveNoInteractions();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void shouldThrowStorageDisabledWhenTimeSeriesIsSavedAndStorageIsDisabled() {
 | 
			
		||||
        // GIVEN
 | 
			
		||||
        apiUsageState.setDbStorageState(ApiUsageStateValue.DISABLED);
 | 
			
		||||
 | 
			
		||||
        SettableFuture<Void> future = SettableFuture.create();
 | 
			
		||||
        var request = TimeseriesSaveRequest.builder()
 | 
			
		||||
                .tenantId(tenantId)
 | 
			
		||||
                .customerId(customerId)
 | 
			
		||||
                .entityId(entityId)
 | 
			
		||||
                .entries(sampleTelemetry)
 | 
			
		||||
                .ttl(sampleTtl)
 | 
			
		||||
                .saveTimeseries(true)
 | 
			
		||||
                .saveLatest(true)
 | 
			
		||||
                .sendWsUpdate(true)
 | 
			
		||||
                .future(future)
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        // WHEN
 | 
			
		||||
        telemetryService.saveTimeseries(request);
 | 
			
		||||
 | 
			
		||||
        // THEN
 | 
			
		||||
        assertThat(future).failsWithin(Duration.ofSeconds(5))
 | 
			
		||||
                .withThrowableOfType(ExecutionException.class)
 | 
			
		||||
                .withCauseInstanceOf(RuntimeException.class)
 | 
			
		||||
                .withMessageContaining("DB storage writes are disabled due to API limits!");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void shouldNotThrowStorageDisabledWhenTimeSeriesIsNotSavedAndStorageIsDisabled() {
 | 
			
		||||
        // GIVEN
 | 
			
		||||
        apiUsageState.setDbStorageState(ApiUsageStateValue.DISABLED);
 | 
			
		||||
 | 
			
		||||
        SettableFuture<Void> future = SettableFuture.create();
 | 
			
		||||
        var request = TimeseriesSaveRequest.builder()
 | 
			
		||||
                .tenantId(tenantId)
 | 
			
		||||
                .customerId(customerId)
 | 
			
		||||
                .entityId(entityId)
 | 
			
		||||
                .entries(sampleTelemetry)
 | 
			
		||||
                .ttl(sampleTtl)
 | 
			
		||||
                .saveTimeseries(false)
 | 
			
		||||
                .saveLatest(true)
 | 
			
		||||
                .sendWsUpdate(true)
 | 
			
		||||
                .future(future)
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        // WHEN
 | 
			
		||||
        telemetryService.saveTimeseries(request);
 | 
			
		||||
 | 
			
		||||
        // THEN
 | 
			
		||||
        assertThat(future).succeedsWithin(Duration.ofSeconds(5));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void shouldCopyLatestToEntityViewWhenLatestIsSavedOnMainEntity() {
 | 
			
		||||
        // GIVEN
 | 
			
		||||
        var entityView = new EntityView(new EntityViewId(UUID.randomUUID()));
 | 
			
		||||
        entityView.setTenantId(tenantId);
 | 
			
		||||
        entityView.setCustomerId(customerId);
 | 
			
		||||
        entityView.setEntityId(entityId);
 | 
			
		||||
        entityView.setKeys(new TelemetryEntityView(sampleTelemetry.stream().map(KvEntry::getKey).toList(), new AttributesEntityView()));
 | 
			
		||||
 | 
			
		||||
        // mock that there is one entity view
 | 
			
		||||
        given(tbEntityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId)).willReturn(immediateFuture(List.of(entityView)));
 | 
			
		||||
        // mock that save latest call for entity view is successful
 | 
			
		||||
        given(tsService.saveLatest(tenantId, entityView.getId(), sampleTelemetry)).willReturn(immediateFuture(listOfNNumbers(sampleTelemetry.size())));
 | 
			
		||||
        // mock TPI for entity view
 | 
			
		||||
        given(partitionService.resolve(ServiceType.TB_CORE, tenantId, entityView.getId())).willReturn(tpi);
 | 
			
		||||
 | 
			
		||||
        var request = TimeseriesSaveRequest.builder()
 | 
			
		||||
                .tenantId(tenantId)
 | 
			
		||||
                .customerId(customerId)
 | 
			
		||||
                .entityId(entityId)
 | 
			
		||||
                .entries(sampleTelemetry)
 | 
			
		||||
                .ttl(sampleTtl)
 | 
			
		||||
                .saveTimeseries(false)
 | 
			
		||||
                .saveLatest(true)
 | 
			
		||||
                .sendWsUpdate(false)
 | 
			
		||||
                .callback(emptyCallback)
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        // WHEN
 | 
			
		||||
        telemetryService.saveTimeseries(request);
 | 
			
		||||
 | 
			
		||||
        // THEN
 | 
			
		||||
        // should save latest to both the main entity and it's entity view
 | 
			
		||||
        then(tsService).should().saveLatest(tenantId, entityId, sampleTelemetry);
 | 
			
		||||
        then(tsService).should().saveLatest(tenantId, entityView.getId(), sampleTelemetry);
 | 
			
		||||
        then(tsService).shouldHaveNoMoreInteractions();
 | 
			
		||||
 | 
			
		||||
        // should send WS update only for entity view (WS update for the main entity is disabled in the save request)
 | 
			
		||||
        then(subscriptionManagerService).should().onTimeSeriesUpdate(tenantId, entityView.getId(), sampleTelemetry, TbCallback.EMPTY);
 | 
			
		||||
        then(subscriptionManagerService).shouldHaveNoMoreInteractions();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void shouldNotCopyLatestToEntityViewWhenLatestIsNotSavedOnMainEntity() {
 | 
			
		||||
        // GIVEN
 | 
			
		||||
        var request = TimeseriesSaveRequest.builder()
 | 
			
		||||
                .tenantId(tenantId)
 | 
			
		||||
                .customerId(customerId)
 | 
			
		||||
                .entityId(entityId)
 | 
			
		||||
                .entries(sampleTelemetry)
 | 
			
		||||
                .ttl(sampleTtl)
 | 
			
		||||
                .saveTimeseries(true)
 | 
			
		||||
                .saveLatest(false)
 | 
			
		||||
                .sendWsUpdate(false)
 | 
			
		||||
                .callback(emptyCallback)
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        // WHEN
 | 
			
		||||
        telemetryService.saveTimeseries(request);
 | 
			
		||||
 | 
			
		||||
        // THEN
 | 
			
		||||
        // should save only time series for the main entity
 | 
			
		||||
        then(tsService).should().saveWithoutLatest(tenantId, entityId, sampleTelemetry, sampleTtl);
 | 
			
		||||
        then(tsService).shouldHaveNoMoreInteractions();
 | 
			
		||||
 | 
			
		||||
        // should not send any WS updates
 | 
			
		||||
        then(subscriptionManagerService).shouldHaveNoInteractions();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @ParameterizedTest
 | 
			
		||||
    @MethodSource("booleanCombinations")
 | 
			
		||||
    void shouldCallCorrectApiBasedOnBooleanFlagsInTheSaveRequest(boolean saveTimeseries, boolean saveLatest, boolean sendWsUpdate) {
 | 
			
		||||
        // GIVEN
 | 
			
		||||
        var request = TimeseriesSaveRequest.builder()
 | 
			
		||||
                .tenantId(tenantId)
 | 
			
		||||
                .customerId(customerId)
 | 
			
		||||
                .entityId(entityId)
 | 
			
		||||
                .entries(sampleTelemetry)
 | 
			
		||||
                .ttl(sampleTtl)
 | 
			
		||||
                .saveTimeseries(saveTimeseries)
 | 
			
		||||
                .saveLatest(saveLatest)
 | 
			
		||||
                .sendWsUpdate(sendWsUpdate)
 | 
			
		||||
                .callback(emptyCallback)
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        // WHEN
 | 
			
		||||
        telemetryService.saveTimeseries(request);
 | 
			
		||||
 | 
			
		||||
        // THEN
 | 
			
		||||
        if (saveTimeseries && saveLatest) {
 | 
			
		||||
            then(tsService).should().save(tenantId, entityId, sampleTelemetry, sampleTtl);
 | 
			
		||||
        } else if (saveLatest) {
 | 
			
		||||
            then(tsService).should().saveLatest(tenantId, entityId, sampleTelemetry);
 | 
			
		||||
        } else if (saveTimeseries) {
 | 
			
		||||
            then(tsService).should().saveWithoutLatest(tenantId, entityId, sampleTelemetry, sampleTtl);
 | 
			
		||||
        }
 | 
			
		||||
        then(tsService).shouldHaveNoMoreInteractions();
 | 
			
		||||
 | 
			
		||||
        if (sendWsUpdate) {
 | 
			
		||||
            then(subscriptionManagerService).should().onTimeSeriesUpdate(tenantId, entityId, sampleTelemetry, TbCallback.EMPTY);
 | 
			
		||||
        } else {
 | 
			
		||||
            then(subscriptionManagerService).shouldHaveNoInteractions();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Stream<Arguments> booleanCombinations() {
 | 
			
		||||
        return Stream.of(
 | 
			
		||||
                Arguments.of(true, true, true),
 | 
			
		||||
                Arguments.of(true, true, false),
 | 
			
		||||
                Arguments.of(true, false, true),
 | 
			
		||||
                Arguments.of(true, false, false),
 | 
			
		||||
                Arguments.of(false, true, true),
 | 
			
		||||
                Arguments.of(false, true, false),
 | 
			
		||||
                Arguments.of(false, false, true),
 | 
			
		||||
                Arguments.of(false, false, false)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // used to emulate sequence numbers returned by save latest API
 | 
			
		||||
    private static List<Long> listOfNNumbers(int N) {
 | 
			
		||||
        return LongStream.range(0, N).boxed().toList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,33 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2024 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.rule.engine.api;
 | 
			
		||||
 | 
			
		||||
import org.junit.jupiter.api.Test;
 | 
			
		||||
 | 
			
		||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
			
		||||
 | 
			
		||||
class TimeseriesSaveRequestTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void testBooleanFlagsDefaultToTrue() {
 | 
			
		||||
        var request = TimeseriesSaveRequest.builder().build();
 | 
			
		||||
 | 
			
		||||
        assertThat(request.isSaveTimeseries()).isTrue();
 | 
			
		||||
        assertThat(request.isSaveLatest()).isTrue();
 | 
			
		||||
        assertThat(request.isSendWsUpdate()).isTrue();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user