From 74ef364f33e775199465592f524267ade19833b3 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Mon, 2 Feb 2026 14:10:39 +0200 Subject: [PATCH 1/4] fix: respect forward_headers_strategy for client IP in audit logs Remove redundant manual X-Forwarded-For header parsing in RestAuthenticationDetails. The getClientIP() method duplicated functionality already provided by Spring's ForwardedHeaderFilter when server.forward_headers_strategy is configured. Now uses request.getRemoteAddr() directly, which respects the configured forward_headers_strategy setting (default: framework). Co-Authored-By: Claude Opus 4.5 --- .../security/auth/rest/RestAuthenticationDetails.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetails.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetails.java index aa084a7968..e75a49ccb2 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetails.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetails.java @@ -29,18 +29,10 @@ public class RestAuthenticationDetails implements Serializable { private final Client userAgent; public RestAuthenticationDetails(HttpServletRequest request) { - this.clientAddress = getClientIP(request); + this.clientAddress = request.getRemoteAddr(); this.userAgent = getUserAgent(request); } - private static String getClientIP(HttpServletRequest request) { - String xfHeader = request.getHeader("X-Forwarded-For"); - if (xfHeader == null) { - return request.getRemoteAddr(); - } - return xfHeader.split(",")[0]; - } - private static Client getUserAgent(HttpServletRequest request) { Parser uaParser = new Parser(); return uaParser.parse(request.getHeader("User-Agent")); From aaf4f35208b7a14ac9ce71fb109aa928c2546402 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 3 Feb 2026 14:53:20 +0100 Subject: [PATCH 2/4] mvn test with -Dtest ignores -Dsurefire.excludes so the exclusions should be inlined like -Dtest='!**/nosql/**,org.thingsboard.server.controller.**' --- TEST_FAST.md | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/TEST_FAST.md b/TEST_FAST.md index c0f1174584..4d1e9dc9a7 100644 --- a/TEST_FAST.md +++ b/TEST_FAST.md @@ -10,22 +10,43 @@ mvn clean install -T6 -DskipTests mvn test -pl='!application,!dao,!ui-ngx,!msa/js-executor,!msa/web-ui' -T4 mvn test -pl dao -Dparallel=packages -DforkCount=4 -mvn test -pl application -Dsurefire.excludes='**/nosql/*Test.java' -Dtest='org.thingsboard.server.controller.**' -DforkCount=6 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 -mvn test -pl application -Dsurefire.excludes='**/nosql/*Test.java' -Dtest='org.thingsboard.server.edge.**' -DforkCount=4 -Dparallel=packages -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 -mvn test -pl application -Dsurefire.excludes='**/nosql/*Test.java' -Dtest='org.thingsboard.server.service.**' -DforkCount=6 -Dparallel=packages -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 -mvn test -pl application -Dsurefire.excludes='**/nosql/*Test.java' -Dtest='org.thingsboard.server.transport.mqtt.**' -DforkCount=6 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 -mvn test -pl application -Dsurefire.excludes='**/nosql/*Test.java' -Dtest='org.thingsboard.server.transport.coap.**' -DforkCount=6 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 -mvn test -pl application -Dsurefire.excludes='**/nosql/*Test.java' -Dtest='org.thingsboard.server.transport.lwm2m.**' -DforkCount=6 -Dparallel=packages -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 -mvn test -pl application -Dsurefire.excludes='**/nosql/*Test.java' -Dtest='**/*TestSuite.java' -DforkCount=4 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 +mvn test -pl application -Dtest='!**/nosql/**,org.thingsboard.server.controller.**' -DforkCount=6 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 +mvn test -pl application -Dtest='!**/nosql/**,org.thingsboard.server.edge.**' -DforkCount=4 -Dparallel=packages -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 +mvn test -pl application -Dtest='!**/nosql/**,org.thingsboard.server.service.**' -DforkCount=6 -Dparallel=packages -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 +mvn test -pl application -Dtest='!**/nosql/**,org.thingsboard.server.transport.mqtt.**' -DforkCount=6 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 +mvn test -pl application -Dtest='!**/nosql/**,org.thingsboard.server.transport.coap.**' -DforkCount=6 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 +mvn test -pl application -Dtest='!**/nosql/**,org.thingsboard.server.transport.lwm2m.**' -DforkCount=6 -Dparallel=packages -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 +mvn test -pl application -Dtest='**/*TestSuite.java' -DforkCount=4 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 #the rest of application tests mvn test -pl application -Dtest=' -!**/nosql/*Test.java, +!**/nosql/**, !org.thingsboard.server.controller.**, !org.thingsboard.server.edge.**, !org.thingsboard.server.service.**, !org.thingsboard.server.transport.mqtt.**, !org.thingsboard.server.transport.coap.**, -!org.thingsboard.server.transport.lwm2m.** +!org.thingsboard.server.transport.lwm2m.**, +!**/*TestSuite.java ' -DforkCount=6 -Dparallel=packages -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 ``` + +## Testcontainers compatibility with the Docker API workaround + +In case your tests failed to run testcontainers due to unsupported Docker API version + +:coffee: testcontainers (Docker API 1.32) + :whale: docker 29 (min API 1.44) workaround + +Add to /etc/docker/daemon.json and restart docker +```json +{ + "min-api-version": "1.32" +} +``` + +Same works on Mac, except `daemon.json` are located in another folder and required to be edited from Docker Desktop UI. + +Tip: If your testcontainer are struggling to find any Docker. You can try to remove the testcontainers property file. It will be recreated on the next testcontainers run. +```bash +rm ~/.testcontainers.properties +``` From 2264a40285b9d05d79f79b34308a1c7072e219b6 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 6 Feb 2026 09:02:44 +0200 Subject: [PATCH 3/4] fix: resolve race condition in TbRestApiCallNodeTest The deleteRequestWithBody and deleteRequestWithoutBody tests used time-based synchronization (Thread.sleep) to wait for the async WebClient response callback. Under CI load, the callback could fire after the verify() check, causing flaky failures. Replace the sleep-based approach with Mockito's timeout() on verify, which properly polls for the async interaction. Co-Authored-By: Claude Opus 4.6 --- .../engine/rest/TbRestApiCallNodeTest.java | 31 +++---------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java index 5ad0bccf03..a93417275f 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java @@ -56,6 +56,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) @@ -115,18 +116,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { assertTrue(request.containsHeader("Foo"), "Custom header included"); assertEquals("Bar", request.getFirstHeader("Foo").getValue(), "Custom header value"); response.setStatusCode(200); - new Thread(new Runnable() { - @Override - public void run() { - try { - Thread.sleep(1000L); - } catch (InterruptedException e) { - // ignore - } finally { - latch.countDown(); - } - } - }).start(); + latch.countDown(); } catch (Exception e) { System.out.println("Exception handling request: " + e.toString()); e.printStackTrace(); @@ -158,7 +148,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); - verify(ctx).transformMsg(msgCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + verify(ctx, timeout(10_000)).transformMsg(msgCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); assertNotSame(metaData, metadataCaptor.getValue()); assertEquals(TbMsg.EMPTY_JSON_OBJECT, dataCaptor.getValue()); @@ -184,18 +174,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { assertTrue(request.containsHeader("Foo"), "Custom header included"); assertEquals("Bar", request.getFirstHeader("Foo").getValue(), "Custom header value"); response.setStatusCode(200); - new Thread(new Runnable() { - @Override - public void run() { - try { - Thread.sleep(1000L); - } catch (InterruptedException e) { - // ignore - } finally { - latch.countDown(); - } - } - }).start(); + latch.countDown(); } catch (Exception e) { System.out.println("Exception handling request: " + e.toString()); e.printStackTrace(); @@ -227,7 +206,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); - verify(ctx).transformMsg(msgCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + verify(ctx, timeout(10_000)).transformMsg(msgCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); assertNotSame(metaData, metadataCaptor.getValue()); assertEquals(TbMsg.EMPTY_JSON_OBJECT, dataCaptor.getValue()); From 20afefc33cd1179ea6e9ede140f637ca460626f4 Mon Sep 17 00:00:00 2001 From: Nikita Mazurenko Date: Mon, 9 Feb 2026 10:15:51 +0200 Subject: [PATCH 4/4] Set next 4.2 EdgeVersion proto value to canonical one --- common/edge-api/src/main/proto/edge.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index 1998789d91..8e14926fbb 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -45,9 +45,9 @@ enum EdgeVersion { V_4_1_0 = 11; V_4_2_0 = 12; V_4_2_1_2 = 14; - V_4_2_1_3 = 420; + V_4_2_1_3 = 4213; - V_LATEST = 999; + V_LATEST = 99999; } /**