diff --git a/application/pom.xml b/application/pom.xml index 36dfcac42f..6903cae2e4 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -458,6 +458,7 @@ thingsboard + ${project.version} **/nosql/*Test.java diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java index b2b72db5ad..ca35b30e59 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java @@ -16,13 +16,14 @@ package org.thingsboard.server.service.edge.rpc.utils; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.edge.rpc.EdgeVersionComparator; import org.thingsboard.server.gen.edge.v1.EdgeVersion; @Slf4j public final class EdgeVersionUtils { public static boolean isEdgeVersionOlderThan(EdgeVersion currentVersion, EdgeVersion requiredVersion) { - return currentVersion.ordinal() < requiredVersion.ordinal(); + return EdgeVersionComparator.INSTANCE.compare(currentVersion, requiredVersion) < 0; } } diff --git a/application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java b/application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java new file mode 100644 index 0000000000..3852fe2ff0 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java @@ -0,0 +1,41 @@ +/** + * 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.edge; + +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.edge.rpc.EdgeVersionComparator; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; + +public class EdgeLatestVersionTest { + + @Test + public void edgeLatestVersionIsSynchronizedTest() { + EdgeVersion currentHighestEdgeVersion = EdgeVersionComparator.getNewestEdgeVersion(); + + String projectVersion = EdgeLatestVersionTest.class.getPackage().getImplementationVersion(); + if (projectVersion == null || projectVersion.isBlank()) { + projectVersion = System.getProperty("project.version", "UNKNOWN"); + } + + String projectVersionDigits = projectVersion.replaceAll("\\D", ""); + String currentHighestEdgeVersionDigits = currentHighestEdgeVersion.name().replaceAll("\\D", ""); + + String msg = "EdgeVersion enum in edge.proto is out of sync. Please add respective " + projectVersionDigits + " to EdgeVersion"; + Assert.assertEquals(msg, projectVersionDigits, currentHighestEdgeVersionDigits); + } + +} diff --git a/common/edge-api/pom.xml b/common/edge-api/pom.xml index c2d5d028b6..c903718788 100644 --- a/common/edge-api/pom.xml +++ b/common/edge-api/pom.xml @@ -106,6 +106,11 @@ com.google.protobuf protobuf-java + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java index 1b17fbe849..7e58a2cb9f 100644 --- a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java +++ b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java @@ -143,16 +143,7 @@ public class EdgeGrpcClient implements EdgeRpcClient { } public static EdgeVersion getNewestEdgeVersion() { - EdgeVersion newest = null; - for (EdgeVersion v : EdgeVersion.values()) { - if (v == EdgeVersion.V_LATEST || v == EdgeVersion.UNRECOGNIZED) { - continue; - } - if (newest == null || v.getNumber() > newest.getNumber()) { - newest = v; - } - } - return newest; + return EdgeVersionComparator.getNewestEdgeVersion(); } private StreamObserver initOutputStream(String edgeKey, diff --git a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeVersionComparator.java b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeVersionComparator.java new file mode 100644 index 0000000000..94ca577025 --- /dev/null +++ b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeVersionComparator.java @@ -0,0 +1,86 @@ +/** + * 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.edge.rpc; + +import org.thingsboard.server.gen.edge.v1.EdgeVersion; + +import java.util.Comparator; + +public class EdgeVersionComparator implements Comparator { + + public static final EdgeVersionComparator INSTANCE = new EdgeVersionComparator(); + + @Override + public int compare(EdgeVersion v1, EdgeVersion v2) { + if (v1 == v2) { + return 0; + } + // UNRECOGNIZED is less than any other version + if (v1 == EdgeVersion.UNRECOGNIZED) { + return -1; + } + if (v2 == EdgeVersion.UNRECOGNIZED) { + return 1; + } + // V_LATEST is treated as the newest version + if (v1 == EdgeVersion.V_LATEST) { + v1 = getNewestEdgeVersion(); + } + if (v2 == EdgeVersion.V_LATEST) { + v2 = getNewestEdgeVersion(); + } + return compareVersionParts(parseVersionParts(v1), parseVersionParts(v2)); + } + + public static EdgeVersion getNewestEdgeVersion() { + EdgeVersion newest = null; + for (EdgeVersion v : EdgeVersion.values()) { + if (v == EdgeVersion.V_LATEST || v == EdgeVersion.UNRECOGNIZED) { + continue; + } + if (newest == null || INSTANCE.compare(v, newest) > 0) { + newest = v; + } + } + return newest; + } + + private static int[] parseVersionParts(EdgeVersion version) { + String name = version.name(); + if (name.startsWith("V_")) { + name = name.substring(2); + } + String[] parts = name.split("_"); + int[] result = new int[parts.length]; + for (int i = 0; i < parts.length; i++) { + result[i] = Integer.parseInt(parts[i]); + } + return result; + } + + private static int compareVersionParts(int[] a, int[] b) { + int maxLen = Math.max(a.length, b.length); + for (int i = 0; i < maxLen; i++) { + int partA = i < a.length ? a[i] : 0; + int partB = i < b.length ? b[i] : 0; + if (partA != partB) { + return Integer.compare(partA, partB); + } + } + return 0; + } + +} diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index 803bb98330..435faea028 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -44,6 +44,7 @@ enum EdgeVersion { V_4_0_0 = 10; V_4_1_0 = 11; V_4_2_0 = 12; + V_4_2_1_2 = 14; V_LATEST = 999; } diff --git a/common/edge-api/src/test/java/org/thingsboard/edge/rpc/EdgeVersionComparatorTest.java b/common/edge-api/src/test/java/org/thingsboard/edge/rpc/EdgeVersionComparatorTest.java new file mode 100644 index 0000000000..eab63fc092 --- /dev/null +++ b/common/edge-api/src/test/java/org/thingsboard/edge/rpc/EdgeVersionComparatorTest.java @@ -0,0 +1,102 @@ +/** + * 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.edge.rpc; + +import org.junit.jupiter.api.Test; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; + +import static org.assertj.core.api.Assertions.assertThat; + +class EdgeVersionComparatorTest { + + @Test + void compare_sameVersion_returnsZero() { + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_3_0, EdgeVersion.V_3_3_0)).isEqualTo(0); + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_0_0, EdgeVersion.V_4_0_0)).isEqualTo(0); + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_2_1_2, EdgeVersion.V_4_2_1_2)).isEqualTo(0); + } + + @Test + void compare_majorVersionDifference_returnsCorrectOrder() { + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_3_0, EdgeVersion.V_4_0_0)).isLessThan(0); + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_0_0, EdgeVersion.V_3_3_0)).isGreaterThan(0); + } + + @Test + void compare_minorVersionDifference_returnsCorrectOrder() { + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_3_0, EdgeVersion.V_3_6_0)).isLessThan(0); + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_6_0, EdgeVersion.V_3_3_0)).isGreaterThan(0); + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_0_0, EdgeVersion.V_4_1_0)).isLessThan(0); + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_1_0, EdgeVersion.V_4_0_0)).isGreaterThan(0); + } + + @Test + void compare_patchVersionDifference_returnsCorrectOrder() { + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_6_0, EdgeVersion.V_3_6_1)).isLessThan(0); + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_6_1, EdgeVersion.V_3_6_0)).isGreaterThan(0); + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_6_1, EdgeVersion.V_3_6_2)).isLessThan(0); + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_6_2, EdgeVersion.V_3_6_4)).isLessThan(0); + } + + @Test + void compare_fourPartVersion_returnsCorrectOrder() { + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_2_0, EdgeVersion.V_4_2_1_2)).isLessThan(0); + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_2_1_2, EdgeVersion.V_4_2_0)).isGreaterThan(0); + } + + @Test + void compare_threePartVsFourPart_treatsImplicitZero() { + // V_4_2_0 should be less than V_4_2_1_2 (4.2.0.0 < 4.2.1.2) + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_2_0, EdgeVersion.V_4_2_1_2)).isLessThan(0); + } + + @Test + void getNewestEdgeVersion_excludesLatestAndUnrecognized() { + EdgeVersion newest = EdgeVersionComparator.getNewestEdgeVersion(); + assertThat(newest).isNotNull(); + assertThat(newest).isNotEqualTo(EdgeVersion.V_LATEST); + assertThat(newest).isNotEqualTo(EdgeVersion.UNRECOGNIZED); + } + + @Test + void compare_vLatest_treatedAsNewestVersion() { + EdgeVersion newest = EdgeVersionComparator.getNewestEdgeVersion(); + // V_LATEST equals the newest version + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_LATEST, newest)).isEqualTo(0); + assertThat(EdgeVersionComparator.INSTANCE.compare(newest, EdgeVersion.V_LATEST)).isEqualTo(0); + // V_LATEST is greater than older versions + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_LATEST, EdgeVersion.V_3_3_0)).isGreaterThan(0); + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_3_0, EdgeVersion.V_LATEST)).isLessThan(0); + } + + @Test + void compare_vLatest_withItself_returnsZero() { + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_LATEST, EdgeVersion.V_LATEST)).isEqualTo(0); + } + + @Test + void compare_unrecognized_isLessThanAnyVersion() { + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.UNRECOGNIZED, EdgeVersion.V_3_3_0)).isLessThan(0); + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.UNRECOGNIZED, EdgeVersion.V_LATEST)).isLessThan(0); + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_3_0, EdgeVersion.UNRECOGNIZED)).isGreaterThan(0); + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_LATEST, EdgeVersion.UNRECOGNIZED)).isGreaterThan(0); + } + + @Test + void compare_unrecognized_withItself_returnsZero() { + assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.UNRECOGNIZED, EdgeVersion.UNRECOGNIZED)).isEqualTo(0); + } +}