From da12ee6dc03abbe9dc9ba72fa112e8e82f0306a9 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 10 Mar 2026 12:10:26 +0100 Subject: [PATCH 1/4] Fix flaky AuditLogControllerTest: use Awaitility for async audit log assertions Audit logs are saved asynchronously via executor.submit() in AuditLogServiceImpl. Three tests were asserting log counts immediately after HTTP API calls, creating a race condition where the last audit log write may not have completed yet. Replace bare assertions with Awaitility.await().atMost(TIMEOUT, ...).untilAsserted() in testAuditLogs, testAuditLogs_byTenantIdAndEntityId, and testAuditLogs_byTenantIdAndEntityId_Sysadmin (confirmed broken: expected 2, got 1). Also replace the hardcoded 10s timeout in testAuditLogsSysAdmin with the TIMEOUT constant. Co-Authored-By: Claude Sonnet 4.6 --- .../controller/AuditLogControllerTest.java | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java index 728dc1ec0e..56c3ad5619 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java @@ -113,17 +113,14 @@ public class AuditLogControllerTest extends AbstractControllerTest { doPost("/api/device", device, Device.class); } - List loadedAuditLogs = getAuditLogs(5, "/api/audit/logs?"); + Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> + assertThat(getAuditLogs(5, "/api/audit/logs?")).hasSize(11 + 1)); - Assert.assertEquals(11 + 1, loadedAuditLogs.size()); + Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> + assertThat(getAuditLogs(5, "/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?")).hasSize(11 + 1)); - loadedAuditLogs = getAuditLogs(5, "/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?"); - - Assert.assertEquals(11 + 1, loadedAuditLogs.size()); - - loadedAuditLogs = getAuditLogs(5, "/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?"); - - Assert.assertEquals(11 + 1, loadedAuditLogs.size()); + Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> + assertThat(getAuditLogs(5, "/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?")).hasSize(11 + 1)); } @Test @@ -138,7 +135,7 @@ public class AuditLogControllerTest extends AbstractControllerTest { } int expectedSize = loadedAuditLogsBefore.size() + 3; - Awaitility.await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> + Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> Assert.assertEquals("Have X audit log before this test + New tenant profiles in the test", expectedSize, getAuditLogs(100, "/api/audit/logs?").size())); } @@ -170,9 +167,8 @@ public class AuditLogControllerTest extends AbstractControllerTest { savedDevice = doPost("/api/device", savedDevice, Device.class); } - List loadedAuditLogs = getAuditLogs(5, "/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?"); - - Assert.assertEquals(11 + 1, loadedAuditLogs.size()); + Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> + assertThat(getAuditLogs(5, "/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?")).hasSize(11 + 1)); } @Test @@ -188,9 +184,9 @@ public class AuditLogControllerTest extends AbstractControllerTest { tenantProfile.setName(tenantProfile.getName() + "(old)"); tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); - List loadedAuditLogs = getAuditLogs(5, "/api/audit/logs/entity/" +tenantProfile.getId().getEntityType()+ "/" + tenantProfile.getId().getId() + "?"); - - Assert.assertEquals("Audit logs count by Tenant Profile entity", 2, loadedAuditLogs.size()); + Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> + assertThat(getAuditLogs(5, "/api/audit/logs/entity/" + tenantProfile.getId().getEntityType() + "/" + tenantProfile.getId().getId() + "?")) + .as("Audit logs count by Tenant Profile entity").hasSize(2)); //cleanup doDelete("/api/tenantProfile/" + tenantProfile.getId().getId().toString()); From feae24fac8098a3392449f30e07182cabe8d8f79 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 10 Mar 2026 12:22:07 +0100 Subject: [PATCH 2/4] Extract PAGE_SIZE constant in AuditLogControllerTest Replace magic number 5 with a named constant that explains intent: small enough to force multiple pages and verify pagination loop correctness. Co-Authored-By: Claude Sonnet 4.6 --- .../server/controller/AuditLogControllerTest.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java index 56c3ad5619..44e9980f52 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java @@ -62,6 +62,9 @@ import static org.mockito.Mockito.verify; @DaoSqlTest public class AuditLogControllerTest extends AbstractControllerTest { + // Small enough to force multiple pages, verifying pagination loop correctness + private static final int PAGE_SIZE = 5; + private Tenant savedTenant; private User tenantAdmin; @@ -114,13 +117,13 @@ public class AuditLogControllerTest extends AbstractControllerTest { } Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> - assertThat(getAuditLogs(5, "/api/audit/logs?")).hasSize(11 + 1)); + assertThat(getAuditLogs(PAGE_SIZE, "/api/audit/logs?")).hasSize(11 + 1)); Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> - assertThat(getAuditLogs(5, "/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?")).hasSize(11 + 1)); + assertThat(getAuditLogs(PAGE_SIZE, "/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?")).hasSize(11 + 1)); Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> - assertThat(getAuditLogs(5, "/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?")).hasSize(11 + 1)); + assertThat(getAuditLogs(PAGE_SIZE, "/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?")).hasSize(11 + 1)); } @Test @@ -168,7 +171,7 @@ public class AuditLogControllerTest extends AbstractControllerTest { } Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> - assertThat(getAuditLogs(5, "/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?")).hasSize(11 + 1)); + assertThat(getAuditLogs(PAGE_SIZE, "/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?")).hasSize(11 + 1)); } @Test @@ -185,7 +188,7 @@ public class AuditLogControllerTest extends AbstractControllerTest { tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> - assertThat(getAuditLogs(5, "/api/audit/logs/entity/" + tenantProfile.getId().getEntityType() + "/" + tenantProfile.getId().getId() + "?")) + assertThat(getAuditLogs(PAGE_SIZE, "/api/audit/logs/entity/" + tenantProfile.getId().getEntityType() + "/" + tenantProfile.getId().getId() + "?")) .as("Audit logs count by Tenant Profile entity").hasSize(2)); //cleanup From 611004327dab9b5a39b313ce109de6e50368def1 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 10 Mar 2026 12:22:55 +0100 Subject: [PATCH 3/4] Rename PAGE_SIZE to SMALL_PAGE_SIZE for clarity Co-Authored-By: Claude Sonnet 4.6 --- .../server/controller/AuditLogControllerTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java index 44e9980f52..35804c1bd4 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java @@ -63,7 +63,7 @@ import static org.mockito.Mockito.verify; public class AuditLogControllerTest extends AbstractControllerTest { // Small enough to force multiple pages, verifying pagination loop correctness - private static final int PAGE_SIZE = 5; + private static final int SMALL_PAGE_SIZE = 5; private Tenant savedTenant; private User tenantAdmin; @@ -117,13 +117,13 @@ public class AuditLogControllerTest extends AbstractControllerTest { } Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> - assertThat(getAuditLogs(PAGE_SIZE, "/api/audit/logs?")).hasSize(11 + 1)); + assertThat(getAuditLogs(SMALL_PAGE_SIZE, "/api/audit/logs?")).hasSize(11 + 1)); Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> - assertThat(getAuditLogs(PAGE_SIZE, "/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?")).hasSize(11 + 1)); + assertThat(getAuditLogs(SMALL_PAGE_SIZE, "/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?")).hasSize(11 + 1)); Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> - assertThat(getAuditLogs(PAGE_SIZE, "/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?")).hasSize(11 + 1)); + assertThat(getAuditLogs(SMALL_PAGE_SIZE, "/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?")).hasSize(11 + 1)); } @Test @@ -171,7 +171,7 @@ public class AuditLogControllerTest extends AbstractControllerTest { } Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> - assertThat(getAuditLogs(PAGE_SIZE, "/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?")).hasSize(11 + 1)); + assertThat(getAuditLogs(SMALL_PAGE_SIZE, "/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?")).hasSize(11 + 1)); } @Test @@ -188,7 +188,7 @@ public class AuditLogControllerTest extends AbstractControllerTest { tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> - assertThat(getAuditLogs(PAGE_SIZE, "/api/audit/logs/entity/" + tenantProfile.getId().getEntityType() + "/" + tenantProfile.getId().getId() + "?")) + assertThat(getAuditLogs(SMALL_PAGE_SIZE, "/api/audit/logs/entity/" + tenantProfile.getId().getEntityType() + "/" + tenantProfile.getId().getId() + "?")) .as("Audit logs count by Tenant Profile entity").hasSize(2)); //cleanup From 26acf60768f816cd2d6bde75b622089b3e7b6ac7 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 10 Mar 2026 13:20:40 +0100 Subject: [PATCH 4/4] Fix compilation error: use effectively final variables in lambda expressions Variables reassigned in a loop cannot be captured in lambda expressions. Introduce finalSavedDevice and finalTenantProfile as effectively final copies before use in Awaitility.await() lambdas. Co-Authored-By: Claude Sonnet 4.6 --- .../server/controller/AuditLogControllerTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java index 35804c1bd4..95582cf261 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java @@ -170,8 +170,9 @@ public class AuditLogControllerTest extends AbstractControllerTest { savedDevice = doPost("/api/device", savedDevice, Device.class); } + Device finalSavedDevice = savedDevice; Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> - assertThat(getAuditLogs(SMALL_PAGE_SIZE, "/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?")).hasSize(11 + 1)); + assertThat(getAuditLogs(SMALL_PAGE_SIZE, "/api/audit/logs/entity/DEVICE/" + finalSavedDevice.getId().getId() + "?")).hasSize(11 + 1)); } @Test @@ -187,8 +188,9 @@ public class AuditLogControllerTest extends AbstractControllerTest { tenantProfile.setName(tenantProfile.getName() + "(old)"); tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); + TenantProfile finalTenantProfile = tenantProfile; Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> - assertThat(getAuditLogs(SMALL_PAGE_SIZE, "/api/audit/logs/entity/" + tenantProfile.getId().getEntityType() + "/" + tenantProfile.getId().getId() + "?")) + assertThat(getAuditLogs(SMALL_PAGE_SIZE, "/api/audit/logs/entity/" + finalTenantProfile.getId().getEntityType() + "/" + finalTenantProfile.getId().getId() + "?")) .as("Audit logs count by Tenant Profile entity").hasSize(2)); //cleanup