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 55fb4cfd9f844b25c61417807fe9ee10eb95759d Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 5 Feb 2026 13:01:43 +0200 Subject: [PATCH 3/4] Update echarts and ngx-flowchart versions --- ui-ngx/package.json | 4 ++-- ui-ngx/patches/echarts+5.5.1-TB.patch | 15 --------------- ui-ngx/yarn.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 23 deletions(-) delete mode 100644 ui-ngx/patches/echarts+5.5.1-TB.patch diff --git a/ui-ngx/package.json b/ui-ngx/package.json index ef9b296bc2..738051f3d6 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -46,7 +46,7 @@ "canvas-gauges": "^2.1.7", "core-js": "^3.48.0", "dayjs": "1.11.19", - "echarts": "https://github.com/thingsboard/echarts/archive/5.5.1-TB.tar.gz", + "echarts": "https://github.com/thingsboard/echarts/archive/5.5.2-TB.tar.gz", "flot": "https://github.com/thingsboard/flot.git#0.9-work", "flot.curvedlines": "https://github.com/MichaelZinsmaier/CurvedLines.git#master", "font-awesome": "^4.7.0", @@ -71,7 +71,7 @@ "ngx-clipboard": "^16.0.0", "ngx-daterangepicker-material": "^6.0.4", "ngx-drag-drop": "^20.0.1", - "ngx-flowchart": "https://github.com/thingsboard/ngx-flowchart.git#release/3.0.0", + "ngx-flowchart": "https://github.com/thingsboard/ngx-flowchart.git#release/4.0.0", "ngx-hm-carousel": "^19.0.0", "ngx-markdown": "^20.1.0", "ngx-sharebuttons": "^17.0.0", diff --git a/ui-ngx/patches/echarts+5.5.1-TB.patch b/ui-ngx/patches/echarts+5.5.1-TB.patch deleted file mode 100644 index 0ca9ff80c0..0000000000 --- a/ui-ngx/patches/echarts+5.5.1-TB.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/node_modules/echarts/index.d.ts b/node_modules/echarts/index.d.ts -index 811908a..5f77c60 100644 ---- a/node_modules/echarts/index.d.ts -+++ b/node_modules/echarts/index.d.ts -@@ -17,7 +17,7 @@ - * under the License. - */ - --import * as echarts from './types/dist/echarts'; -+// import * as echarts from './types/dist/echarts'; - // Export for UMD module. - export as namespace echarts; --export = echarts; -\ No newline at end of file -+export * from './types/dist/echarts'; diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index ad057cd78b..d36c9c8684 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -5405,9 +5405,9 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -"echarts@https://github.com/thingsboard/echarts/archive/5.5.1-TB.tar.gz": - version "5.5.1-TB" - resolved "https://github.com/thingsboard/echarts/archive/5.5.1-TB.tar.gz#8cf0cbb1b4c6161f0b587a1a649ff4f8eecbbf42" +"echarts@https://github.com/thingsboard/echarts/archive/5.5.2-TB.tar.gz": + version "5.5.2-TB" + resolved "https://github.com/thingsboard/echarts/archive/5.5.2-TB.tar.gz#779494c24fb5448ecaf79315bf609bb52b431df9" dependencies: tslib "2.3.0" zrender "https://github.com/thingsboard/zrender/archive/5.5.0-TB.tar.gz" @@ -8098,9 +8098,9 @@ ngx-drag-drop@^20.0.1: dependencies: tslib "^2.3.0" -"ngx-flowchart@https://github.com/thingsboard/ngx-flowchart.git#release/3.0.0": - version "3.0.0" - resolved "https://github.com/thingsboard/ngx-flowchart.git#0160a1356129c2a3d4dcf7e064e4ba0a39ff668d" +"ngx-flowchart@https://github.com/thingsboard/ngx-flowchart.git#release/4.0.0": + version "4.0.0" + resolved "https://github.com/thingsboard/ngx-flowchart.git#735bc818ef218a169ac50e87edc6a093b3e97715" dependencies: tslib "^2.3.0" From 2264a40285b9d05d79f79b34308a1c7072e219b6 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 6 Feb 2026 09:02:44 +0200 Subject: [PATCH 4/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());