From 8599008ff82a12ba27baa3898fbbb09270575497 Mon Sep 17 00:00:00 2001 From: Morten Svanaes Date: Sun, 19 Jul 2026 20:30:54 +0800 Subject: [PATCH] test: fix flaky AuditIntegrationTest await conditions [DHIS2-21858] (2.43) The await conditions used countAudits(query) >= 0 which is vacuously true, so the tests never actually waited for the async Artemis audit consumer and raced it instead (expected 1 audit but was 0). testSaveRoute additionally reused the shared BASE_UID for every AuthScheme parameter run. Audit rows are written outside the test transaction and are not rolled back, so a late audit from a previous run leaked into the next run's query (expected 1 but was 2). Await >= 1 and use a unique UID per parameterized run. AI Assisted --- .../hisp/dhis/audit/AuditIntegrationTest.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/audit/AuditIntegrationTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/audit/AuditIntegrationTest.java index 2297583aacca..086601c2cc4f 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/audit/AuditIntegrationTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/audit/AuditIntegrationTest.java @@ -45,6 +45,7 @@ import java.util.Properties; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; +import org.hisp.dhis.common.CodeGenerator; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.UID; import org.hisp.dhis.common.auth.ApiHeadersAuthScheme; @@ -142,7 +143,7 @@ void testSaveMetadata() { DataElement dataElement = createDataElement('A'); dataElementService.addDataElement(dataElement); AuditQuery query = AuditQuery.builder().uid(Sets.newHashSet(dataElement.getUid())).build(); - await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> auditService.countAudits(query) >= 0); + await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> auditService.countAudits(query) >= 1); List audits = auditService.getAudits(query); assertEquals(1, audits.size()); Audit audit = audits.get(0); @@ -155,7 +156,9 @@ void testSaveMetadata() { @MethodSource("provideAuthSchemes") void testSaveRoute(AuthScheme authScheme) { Route route = new Route(); - route.setUid(BASE_UID); + // each parameterized run needs its own UID: audits are written asynchronously + // outside the test transaction, so audits for a shared UID leak between runs + route.setUid(CodeGenerator.generateUid()); route.setName("foo"); route.setAuth(authScheme); route.setUrl("http://stub"); @@ -167,7 +170,7 @@ void testSaveRoute(AuthScheme authScheme) { return null; }); AuditQuery query = AuditQuery.builder().uid(Sets.newHashSet(route.getUid())).build(); - await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> auditService.countAudits(query) >= 0); + await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> auditService.countAudits(query) >= 1); List audits = auditService.getAudits(query); assertEquals(1, audits.size()); assertFalse(audits.get(0).getData().contains("passw0rd")); @@ -185,7 +188,7 @@ void testSaveTrackedEntity() { TrackedEntity trackedEntity = createTrackedEntity('A', ou, attribute, trackedEntityType); manager.save(trackedEntity); AuditQuery query = AuditQuery.builder().uid(Sets.newHashSet(trackedEntity.getUid())).build(); - await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> auditService.countAudits(query) >= 0); + await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> auditService.countAudits(query) >= 1); List audits = auditService.getAudits(query); assertEquals(1, audits.size()); Audit audit = audits.get(0); @@ -214,7 +217,7 @@ void testSaveTrackedAttributeValue() { attributes.put("attribute", attribute.getUid()); attributes.put("trackedEntity", trackedEntity.getUid()); AuditQuery query = AuditQuery.builder().auditAttributes(attributes).build(); - await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> auditService.countAudits(query) >= 0); + await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> auditService.countAudits(query) >= 1); List audits = auditService.getAudits(query); assertEquals(1, audits.size()); Audit audit = audits.get(0); @@ -283,7 +286,7 @@ void testSaveAggregateDataValue() throws ConflictException { AuditAttributes attributes = new AuditAttributes(); attributes.put("dataElement", dataElementA.getUid()); AuditQuery query = AuditQuery.builder().auditAttributes(attributes).build(); - await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> auditService.countAudits(query) >= 0); + await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> auditService.countAudits(query) >= 1); List audits = auditService.getAudits(query); assertEquals(1, audits.size()); Audit audit = audits.get(0); @@ -308,7 +311,7 @@ void testSaveProgram() throws IOException { programStage.addDataElement(dataElement, 0); manager.save(programStage); AuditQuery query = AuditQuery.builder().uid(Sets.newHashSet(programStage.getUid())).build(); - await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> auditService.countAudits(query) >= 0); + await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> auditService.countAudits(query) >= 1); List audits = auditService.getAudits(query); assertEquals(1, audits.size()); Audit audit = audits.get(0); @@ -344,7 +347,7 @@ void testSaveDataSet() throws JsonProcessingException { return null; }); AuditQuery query = AuditQuery.builder().uid(Sets.newHashSet(dataSet.getUid())).build(); - await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> auditService.countAudits(query) >= 0); + await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> auditService.countAudits(query) >= 1); List audits = auditService.getAudits(query); assertEquals(1, audits.size()); Audit audit = audits.get(0);