From adeacba23f62f01a0340bc0ae253135beddbe015 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Mon, 23 Mar 2026 16:56:05 +0200 Subject: [PATCH] Add EDQS unit tests for OR filter evaluation in RepositoryUtils - Add testCheckKeyFiltersWithOrOperation: OR short-circuits on first match - Add testCheckKeyFiltersWithOrOperationNoMatch: OR returns false when nothing matches - Add testCheckKeyFiltersWithOrOperationSameKey: same-key OR works correctly - Add testCheckKeyFiltersWithAndOperationUnchanged: AND behavior unchanged - Add testCheckKeyFiltersWithNullOperationDefaultsToAnd: null defaults to AND - Add testCheckKeyFiltersWithOrEmptyFilters: OR with empty filters returns true --- .../server/edqs/repo/RepositoryUtilsTest.java | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/edqs/src/test/java/org/thingsboard/server/edqs/repo/RepositoryUtilsTest.java b/edqs/src/test/java/org/thingsboard/server/edqs/repo/RepositoryUtilsTest.java index 557cfb7a4a..a7846c0674 100644 --- a/edqs/src/test/java/org/thingsboard/server/edqs/repo/RepositoryUtilsTest.java +++ b/edqs/src/test/java/org/thingsboard/server/edqs/repo/RepositoryUtilsTest.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.edqs.repo; +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; @@ -44,6 +45,7 @@ import org.thingsboard.server.edqs.query.DataKey; import org.thingsboard.server.edqs.query.EdqsFilter; import org.thingsboard.server.edqs.util.RepositoryUtils; +import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.stream.Stream; @@ -469,4 +471,118 @@ public class RepositoryUtilsTest { return complexFilterPredicate; } + // --- OR operation tests for checkKeyFilters --- + + @Test + public void testCheckKeyFiltersWithOrOperation() { + // Entity with temperature=60; filters: temperature>50 (match) and humidity>80 (no data) + DeviceData deviceData = new DeviceData(UUID.randomUUID()); + deviceData.setCustomerId(UUID.randomUUID()); + deviceData.setFields(DeviceFields.builder().name("orDevice1").build()); + deviceData.putTs(5, new DoubleDataPoint(System.currentTimeMillis(), 60.0)); + + EdqsFilter tempGt50 = getTemperatureFilter(NumericOperation.GREATER, 50); + EdqsFilter humidityGt80 = getHumidityFilter(NumericOperation.GREATER, 80); + + // OR: temperature>50 matches, short-circuits to true + assertThat(RepositoryUtils.checkKeyFilters(deviceData, List.of(tempGt50, humidityGt80), ComplexOperation.OR)).isTrue(); + } + + @Test + public void testCheckKeyFiltersWithOrOperationNoMatch() { + // Entity with temperature=30; filters: temperature>50 (no match) and temperature<10 (no match) + DeviceData deviceData = new DeviceData(UUID.randomUUID()); + deviceData.setCustomerId(UUID.randomUUID()); + deviceData.setFields(DeviceFields.builder().name("orDevice2").build()); + deviceData.putTs(5, new DoubleDataPoint(System.currentTimeMillis(), 30.0)); + + EdqsFilter tempGt50 = getTemperatureFilter(NumericOperation.GREATER, 50); + EdqsFilter tempLt10 = getTemperatureFilter(NumericOperation.LESS, 10); + + // OR: neither filter matches + assertThat(RepositoryUtils.checkKeyFilters(deviceData, List.of(tempGt50, tempLt10), ComplexOperation.OR)).isFalse(); + } + + @Test + public void testCheckKeyFiltersWithOrOperationSameKey() { + // Entity with temperature=5; filters: temperature>50 (no match) and temperature<10 (match) + DeviceData deviceData = new DeviceData(UUID.randomUUID()); + deviceData.setCustomerId(UUID.randomUUID()); + deviceData.setFields(DeviceFields.builder().name("orDevice3").build()); + deviceData.putTs(5, new DoubleDataPoint(System.currentTimeMillis(), 5.0)); + + EdqsFilter tempGt50 = getTemperatureFilter(NumericOperation.GREATER, 50); + EdqsFilter tempLt10 = getTemperatureFilter(NumericOperation.LESS, 10); + + // OR on same key: second filter matches + assertThat(RepositoryUtils.checkKeyFilters(deviceData, List.of(tempGt50, tempLt10), ComplexOperation.OR)).isTrue(); + } + + @Test + public void testCheckKeyFiltersWithAndOperationUnchanged() { + // Entity with temperature=60 and humidity=90 + DeviceData deviceData = new DeviceData(UUID.randomUUID()); + deviceData.setCustomerId(UUID.randomUUID()); + deviceData.setFields(DeviceFields.builder().name("andDevice1").build()); + deviceData.putTs(5, new DoubleDataPoint(System.currentTimeMillis(), 60.0)); + deviceData.putTs(6, new LongDataPoint(System.currentTimeMillis(), 90)); + + EdqsFilter tempGt50 = getTemperatureFilter(NumericOperation.GREATER, 50); + EdqsFilter humidityGt80 = getHumidityFilter(NumericOperation.GREATER, 80); + + // AND: both match + assertThat(RepositoryUtils.checkKeyFilters(deviceData, List.of(tempGt50, humidityGt80), ComplexOperation.AND)).isTrue(); + + // Now with humidity=70 -- humidity>80 fails + DeviceData deviceData2 = new DeviceData(UUID.randomUUID()); + deviceData2.setCustomerId(UUID.randomUUID()); + deviceData2.setFields(DeviceFields.builder().name("andDevice2").build()); + deviceData2.putTs(5, new DoubleDataPoint(System.currentTimeMillis(), 60.0)); + deviceData2.putTs(6, new LongDataPoint(System.currentTimeMillis(), 70)); + + // AND: humidity>80 fails + assertThat(RepositoryUtils.checkKeyFilters(deviceData2, List.of(tempGt50, humidityGt80), ComplexOperation.AND)).isFalse(); + } + + @Test + public void testCheckKeyFiltersWithNullOperationDefaultsToAnd() { + // Entity with temperature=60 and humidity=90 + DeviceData deviceData = new DeviceData(UUID.randomUUID()); + deviceData.setCustomerId(UUID.randomUUID()); + deviceData.setFields(DeviceFields.builder().name("nullOpDevice").build()); + deviceData.putTs(5, new DoubleDataPoint(System.currentTimeMillis(), 60.0)); + deviceData.putTs(6, new LongDataPoint(System.currentTimeMillis(), 90)); + + EdqsFilter tempGt50 = getTemperatureFilter(NumericOperation.GREATER, 50); + EdqsFilter humidityGt80 = getHumidityFilter(NumericOperation.GREATER, 80); + + // null operation should behave like AND + boolean nullResult = RepositoryUtils.checkKeyFilters(deviceData, List.of(tempGt50, humidityGt80), null); + boolean andResult = RepositoryUtils.checkKeyFilters(deviceData, List.of(tempGt50, humidityGt80), ComplexOperation.AND); + assertThat(nullResult).isEqualTo(andResult); + assertThat(nullResult).isTrue(); + + // Entity with temperature=60 and humidity=70 -- humidity>80 fails + DeviceData deviceData2 = new DeviceData(UUID.randomUUID()); + deviceData2.setCustomerId(UUID.randomUUID()); + deviceData2.setFields(DeviceFields.builder().name("nullOpDevice2").build()); + deviceData2.putTs(5, new DoubleDataPoint(System.currentTimeMillis(), 60.0)); + deviceData2.putTs(6, new LongDataPoint(System.currentTimeMillis(), 70)); + + boolean nullResult2 = RepositoryUtils.checkKeyFilters(deviceData2, List.of(tempGt50, humidityGt80), null); + boolean andResult2 = RepositoryUtils.checkKeyFilters(deviceData2, List.of(tempGt50, humidityGt80), ComplexOperation.AND); + assertThat(nullResult2).isEqualTo(andResult2); + assertThat(nullResult2).isFalse(); + } + + @Test + public void testCheckKeyFiltersWithOrEmptyFilters() { + DeviceData deviceData = new DeviceData(UUID.randomUUID()); + deviceData.setCustomerId(UUID.randomUUID()); + deviceData.setFields(DeviceFields.builder().name("emptyFilterDevice").build()); + + // OR with empty filters = include all (matches SQL behavior) + assertThat(RepositoryUtils.checkKeyFilters(deviceData, Collections.emptyList(), ComplexOperation.OR)).isTrue(); + } + }