From d5fe1db7d4b64729391937491e629a146d7eade2 Mon Sep 17 00:00:00 2001 From: Nikita Mazurenko Date: Fri, 23 Jan 2026 13:00:53 +0200 Subject: [PATCH 1/8] Add test to verify that enum proto EdgeVersion is synchronized with maven project.version --- application/pom.xml | 1 + .../server/edge/EdgeLatestVersionTest.java | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java 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/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..079cea1b35 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java @@ -0,0 +1,31 @@ +package org.thingsboard.server.edge; + +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; + +import java.util.Arrays; +import java.util.Comparator; + +public class EdgeLatestVersionTest { + + @Test + public void edgeLatestVersionIsSynchronizedTest() { + EdgeVersion currentHighestEdgeVersion = Arrays.stream(EdgeVersion.values()) + .filter(v -> v != EdgeVersion.UNRECOGNIZED) + .filter(v -> v != EdgeVersion.V_LATEST) + .max(Comparator.comparingInt(EdgeVersion::getNumber)) + .orElseThrow(); + + 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", ""); + + Assert.assertEquals(projectVersionDigits, currentHighestEdgeVersionDigits); + } + +} From 3d7be726929895220cedb207fab9ed55a86f647f Mon Sep 17 00:00:00 2001 From: Nikita Mazurenko Date: Fri, 23 Jan 2026 13:14:27 +0200 Subject: [PATCH 2/8] Add error message to EdgeLatestVersionTest --- .../org/thingsboard/server/edge/EdgeLatestVersionTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java b/application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java index 079cea1b35..384af66d01 100644 --- a/application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java @@ -25,7 +25,8 @@ public class EdgeLatestVersionTest { String projectVersionDigits = projectVersion.replaceAll("\\D", ""); String currentHighestEdgeVersionDigits = currentHighestEdgeVersion.name().replaceAll("\\D", ""); - Assert.assertEquals(projectVersionDigits, currentHighestEdgeVersionDigits); + String msg = "EdgeVersion enum in edge.proto is out of sync. Please add respective " + projectVersionDigits + " to EdgeVersion"; + Assert.assertEquals(msg, projectVersionDigits, currentHighestEdgeVersionDigits); } } From 10aa63f78f5af1073f1690c83224ba97ed8d4014 Mon Sep 17 00:00:00 2001 From: Nikita Mazurenko Date: Fri, 23 Jan 2026 16:40:18 +0200 Subject: [PATCH 3/8] Add license format in EdgeLatestVersionTest --- .../server/edge/EdgeLatestVersionTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java b/application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java index 384af66d01..84c90f6b63 100644 --- a/application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java @@ -1,3 +1,18 @@ +/** + * 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; From 25e6464837b33913f22597949d8ede7ff32b17fe Mon Sep 17 00:00:00 2001 From: Nikita Mazurenko Date: Mon, 2 Feb 2026 14:01:41 +0200 Subject: [PATCH 4/8] Set EdgeVersion V_4_2_1_2 = 13 in edge.proto --- common/edge-api/src/main/proto/edge.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index 803bb98330..783c4ab394 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 = 13; V_LATEST = 999; } From 5058379186c02624b4bae37aea11fa96f9d80f5c Mon Sep 17 00:00:00 2001 From: Nikita Mazurenko Date: Mon, 2 Feb 2026 14:29:01 +0200 Subject: [PATCH 5/8] Fix EdgeVersion for CE version --- common/edge-api/src/main/proto/edge.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index 783c4ab394..435faea028 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -44,7 +44,7 @@ enum EdgeVersion { V_4_0_0 = 10; V_4_1_0 = 11; V_4_2_0 = 12; - V_4_2_1_2 = 13; + V_4_2_1_2 = 14; V_LATEST = 999; } From 22d6225ad6fb6b3942e2a91400c09687afeed4b0 Mon Sep 17 00:00:00 2001 From: Nikita Mazurenko Date: Mon, 2 Feb 2026 15:27:32 +0200 Subject: [PATCH 6/8] Add EdgeVersionComparator and related test --- common/edge-api/pom.xml | 5 ++ .../thingsboard/edge/rpc/EdgeGrpcClient.java | 11 +-- .../edge/rpc/EdgeVersionComparator.java | 69 ++++++++++++++++++ .../edge/rpc/EdgeVersionComparatorTest.java | 73 +++++++++++++++++++ 4 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeVersionComparator.java create mode 100644 common/edge-api/src/test/java/org/thingsboard/edge/rpc/EdgeVersionComparatorTest.java 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..6d3dae49ca --- /dev/null +++ b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeVersionComparator.java @@ -0,0 +1,69 @@ +/** + * 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) { + 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/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..f88665e9c6 --- /dev/null +++ b/common/edge-api/src/test/java/org/thingsboard/edge/rpc/EdgeVersionComparatorTest.java @@ -0,0 +1,73 @@ +/** + * 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); + } +} From f82f4017fd5057e7167a761ae388a9312ac307ba Mon Sep 17 00:00:00 2001 From: Nikita Mazurenko Date: Mon, 2 Feb 2026 15:52:35 +0200 Subject: [PATCH 7/8] Fix version comparison in EdgeVersionUtils --- .../service/edge/rpc/utils/EdgeVersionUtils.java | 3 ++- .../thingsboard/server/edge/EdgeLatestVersionTest.java | 10 ++-------- 2 files changed, 4 insertions(+), 9 deletions(-) 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 index 84c90f6b63..3852fe2ff0 100644 --- a/application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java @@ -17,20 +17,14 @@ 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; -import java.util.Arrays; -import java.util.Comparator; - public class EdgeLatestVersionTest { @Test public void edgeLatestVersionIsSynchronizedTest() { - EdgeVersion currentHighestEdgeVersion = Arrays.stream(EdgeVersion.values()) - .filter(v -> v != EdgeVersion.UNRECOGNIZED) - .filter(v -> v != EdgeVersion.V_LATEST) - .max(Comparator.comparingInt(EdgeVersion::getNumber)) - .orElseThrow(); + EdgeVersion currentHighestEdgeVersion = EdgeVersionComparator.getNewestEdgeVersion(); String projectVersion = EdgeLatestVersionTest.class.getPackage().getImplementationVersion(); if (projectVersion == null || projectVersion.isBlank()) { From c7c20ae9fdd2adb3b713ed8c5b310c1e2f42723a Mon Sep 17 00:00:00 2001 From: Nikita Mazurenko Date: Mon, 2 Feb 2026 17:21:56 +0200 Subject: [PATCH 8/8] Handle EdgeVersion comparison of non-numeric values --- .../edge/rpc/EdgeVersionComparator.java | 17 +++++++++++ .../edge/rpc/EdgeVersionComparatorTest.java | 29 +++++++++++++++++++ 2 files changed, 46 insertions(+) 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 index 6d3dae49ca..94ca577025 100644 --- 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 @@ -25,6 +25,23 @@ public class EdgeVersionComparator implements Comparator { @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)); } 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 index f88665e9c6..eab63fc092 100644 --- 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 @@ -70,4 +70,33 @@ class EdgeVersionComparatorTest { 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); + } }