diff --git a/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/events/importer/EventManager.java b/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/events/importer/EventManager.java index 3fa69655b41d..97d993d0c401 100644 --- a/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/events/importer/EventManager.java +++ b/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/events/importer/EventManager.java @@ -114,6 +114,8 @@ public ImportSummary updateEventDataValues( eventPersistenceService.updateEventDataValues(de, event, context); } + auditTrackedEntityDataValueHistory(event, context, new Date()); + eventPersistenceService.updateTrackedEntityInstances(context, List.of(event)); executorsByPhase.get(EventProcessorPhase.UPDATE_POST).execute(context, List.of(event)); diff --git a/dhis-2/dhis-services/dhis-service-dxf2/src/test/java/org/hisp/dhis/dxf2/events/event/EventManagerTest.java b/dhis-2/dhis-services/dhis-service-dxf2/src/test/java/org/hisp/dhis/dxf2/events/event/EventManagerTest.java index 88bd287e4a7c..fd9e481d86a6 100644 --- a/dhis-2/dhis-services/dhis-service-dxf2/src/test/java/org/hisp/dhis/dxf2/events/event/EventManagerTest.java +++ b/dhis-2/dhis-services/dhis-service-dxf2/src/test/java/org/hisp/dhis/dxf2/events/event/EventManagerTest.java @@ -45,6 +45,7 @@ import org.hisp.dhis.dxf2.events.importer.EventProcessorExecutor; import org.hisp.dhis.dxf2.events.importer.EventProcessorPhase; import org.hisp.dhis.dxf2.events.importer.context.WorkContext; +import org.hisp.dhis.program.ProgramStageInstance; import org.hisp.dhis.trackedentitydatavalue.TrackedEntityDataValueAuditService; import org.hisp.dhis.user.CurrentUserService; import org.junit.jupiter.api.Test; @@ -69,6 +70,8 @@ class EventManagerTest { @Mock TrackedEntityDataValueAuditService entityDataValueAuditService; + @Mock WorkContext workContext; + @Mock List checkersRunOnDelete; @Mock CurrentUserService currentUserService; @@ -82,7 +85,7 @@ void shouldTriggerUpdatePostProcessorsWhenEventDataValuesUpdated() throws JsonProcessingException { Event event = new Event(); - WorkContext workContext = WorkContext.builder().build(); + event.setUid("id"); doNothing() .when(eventPersistenceService) .updateTrackedEntityInstances(any(WorkContext.class), anyList()); @@ -90,7 +93,8 @@ void shouldTriggerUpdatePostProcessorsWhenEventDataValuesUpdated() doNothing().when(eventProcessorExecutor).execute(any(WorkContext.class), anyList()); when(executorsByPhase.get(any(EventProcessorPhase.class))).thenReturn(eventProcessorExecutor); - + when(workContext.getPersistedProgramStageInstanceMap()) + .thenReturn(Map.of("id", new ProgramStageInstance())); subject.updateEventDataValues(event, Set.of(), workContext); verify(eventPersistenceService, times(1)) diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dxf2/events/EventImportTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dxf2/events/EventImportTest.java index c9f4318cf2a1..5e310fe8d42d 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dxf2/events/EventImportTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dxf2/events/EventImportTest.java @@ -30,9 +30,11 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; +import static org.hisp.dhis.tracker.Assertions.assertTrackedEntityDataValueAudit; import static org.hisp.dhis.user.UserRole.AUTHORITY_ALL; import static org.hisp.dhis.util.DateUtils.getIso8601NoTz; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -58,6 +60,7 @@ import org.hisp.dhis.category.Category; import org.hisp.dhis.category.CategoryCombo; import org.hisp.dhis.category.CategoryOption; +import org.hisp.dhis.common.AuditType; import org.hisp.dhis.common.CodeGenerator; import org.hisp.dhis.common.DataDimensionType; import org.hisp.dhis.common.IdentifiableObjectManager; @@ -92,8 +95,11 @@ import org.hisp.dhis.program.ProgramType; import org.hisp.dhis.program.UserInfoSnapshot; import org.hisp.dhis.test.integration.TransactionalIntegrationTest; +import org.hisp.dhis.trackedentity.TrackedEntityDataValueAuditQueryParams; import org.hisp.dhis.trackedentity.TrackedEntityType; import org.hisp.dhis.trackedentity.TrackedEntityTypeService; +import org.hisp.dhis.trackedentitydatavalue.TrackedEntityDataValueAudit; +import org.hisp.dhis.trackedentitydatavalue.TrackedEntityDataValueAuditService; import org.hisp.dhis.user.User; import org.hisp.dhis.user.UserService; import org.hisp.dhis.util.DateUtils; @@ -116,6 +122,8 @@ class EventImportTest extends TransactionalIntegrationTest { @Autowired private TrackedEntityInstanceService trackedEntityInstanceService; + @Autowired private TrackedEntityDataValueAuditService entityDataValueAuditService; + @Autowired private ProgramStageDataElementService programStageDataElementService; @Autowired private EnrollmentService enrollmentService; @@ -271,9 +279,10 @@ void shouldUpdateEventDataValuesWhenAddingDataValuesToEvent() throws IOException "10"); String uid = eventService.addEventsJson(is, null).getImportSummaries().get(0).getReference(); - Event event = createEvent(uid); + Event newEvent = createEvent(uid); - ProgramStageInstance ev = programStageInstanceService.getProgramStageInstance(event.getUid()); + ProgramStageInstance ev = + programStageInstanceService.getProgramStageInstance(newEvent.getUid()); assertNotNull(ev); assertEquals(1, ev.getEventDataValues().size()); @@ -290,15 +299,15 @@ void shouldUpdateEventDataValuesWhenAddingDataValuesToEvent() throws IOException dataValueB.setDataElement(dataElementB.getUid()); dataValueB.setStoredBy(superUser.getName()); - event.setDataValues(Set.of(dataValueA, dataValueB)); + newEvent.setDataValues(Set.of(dataValueA, dataValueB)); Date now = new Date(); - eventService.updateEventDataValues(event); + eventService.updateEventDataValues(newEvent); manager.clear(); - ev = programStageInstanceService.getProgramStageInstance(event.getUid()); + ev = programStageInstanceService.getProgramStageInstance(newEvent.getUid()); assertNotNull(ev); assertNotNull(ev.getEventDataValues()); @@ -338,6 +347,116 @@ void shouldUpdateEventDataValuesWhenAddingDataValuesToEvent() throws IOException assertTrue(trackedEntityInstance.getLastUpdated().compareTo(getIso8601NoTz(now)) > 0); } + @Test + void shouldAuditChangelogWhenUpdatingEventDataValues() throws IOException { + String previousValueB = "10"; + String newValueB = "15"; + String newValueA = "20"; + InputStream is = + createEventJsonInputStream( + programB.getUid(), + programStageB.getUid(), + organisationUnitB.getUid(), + trackedEntityInstanceMaleA.getTrackedEntityInstance(), + dataElementB, + previousValueB); + String uid = eventService.addEventsJson(is, null).getImportSummaries().get(0).getReference(); + + Event newEvent = createEvent(uid); + + ProgramStageInstance ev = + programStageInstanceService.getProgramStageInstance(newEvent.getUid()); + + assertNotNull(ev); + assertEquals(1, ev.getEventDataValues().size()); + + // add a new data value and update an existing one + + DataValue dataValueA = new DataValue(); + dataValueA.setValue(newValueA); + dataValueA.setDataElement(dataElementA.getUid()); + dataValueA.setStoredBy(superUser.getName()); + + DataValue dataValueB = new DataValue(); + dataValueB.setValue(newValueB); + dataValueB.setDataElement(dataElementB.getUid()); + dataValueB.setStoredBy(superUser.getName()); + + newEvent.setDataValues(Set.of(dataValueA, dataValueB)); + + eventService.updateEventDataValues(newEvent); + + List createdAudits = + entityDataValueAuditService.getTrackedEntityDataValueAudits( + new TrackedEntityDataValueAuditQueryParams() + .setDataElements(List.of(dataElementA)) + .setProgramStageInstances(List.of(ev)) + .setAuditTypes(List.of(AuditType.CREATE))); + + List updatedAudits = + entityDataValueAuditService.getTrackedEntityDataValueAudits( + new TrackedEntityDataValueAuditQueryParams() + .setDataElements(List.of(dataElementB)) + .setProgramStageInstances(List.of(ev)) + .setAuditTypes(List.of(AuditType.UPDATE))); + + assertFalse(createdAudits.isEmpty()); + assertFalse(updatedAudits.isEmpty()); + assertEquals(1, createdAudits.size()); + assertEquals(1, updatedAudits.size()); + + assertTrackedEntityDataValueAudit( + createdAudits.get(0), dataElementA, AuditType.CREATE, newValueA); + assertTrackedEntityDataValueAudit( + updatedAudits.get(0), dataElementB, AuditType.UPDATE, previousValueB); + } + + @Test + void shouldAuditChangelogWhenDeletingEventDataValue() throws IOException { + String previousValueB = "10"; + InputStream is = + createEventJsonInputStream( + programB.getUid(), + programStageB.getUid(), + organisationUnitB.getUid(), + trackedEntityInstanceMaleA.getTrackedEntityInstance(), + dataElementB, + "10"); + String uid = eventService.addEventsJson(is, null).getImportSummaries().get(0).getReference(); + + Event createdEvent = createEvent(uid); + + ProgramStageInstance ev = + programStageInstanceService.getProgramStageInstance(createdEvent.getUid()); + + assertNotNull(ev); + assertEquals(1, ev.getEventDataValues().size()); + + // delete data Element in Event Data Values by setting its value to null + + DataValue dataValueB = new DataValue(); + dataValueB.setValue(null); + dataValueB.setDataElement(dataElementB.getUid()); + dataValueB.setStoredBy(superUser.getName()); + + createdEvent.setDataValues(Set.of(dataValueB)); + + eventService.updateEventDataValues(createdEvent); + + List deleteAudits = + entityDataValueAuditService.getTrackedEntityDataValueAudits( + new TrackedEntityDataValueAuditQueryParams() + .setDataElements(List.of(dataElementB)) + .setProgramStageInstances(List.of(ev)) + .setAuditTypes(List.of(AuditType.DELETE))); + + assertFalse(deleteAudits.isEmpty()); + assertEquals(1, deleteAudits.size()); + + assertTrackedEntityDataValueAudit( + deleteAudits.get(0), dataElementB, AuditType.DELETE, previousValueB); + } + @Test void shouldDeleteDataElementFromEventDataValuesWhenSetDataValueToNull() throws IOException { InputStream is = diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/Assertions.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/Assertions.java index e024f959482b..2774899d608e 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/Assertions.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/Assertions.java @@ -37,6 +37,9 @@ import java.util.Arrays; import java.util.Date; import java.util.function.Supplier; +import org.hisp.dhis.common.AuditType; +import org.hisp.dhis.dataelement.DataElement; +import org.hisp.dhis.trackedentitydatavalue.TrackedEntityDataValueAudit; import org.hisp.dhis.tracker.report.TrackerErrorCode; import org.hisp.dhis.tracker.report.TrackerImportReport; import org.hisp.dhis.tracker.report.TrackerStatus; @@ -198,6 +201,47 @@ public static void assertHasTimeStamp(String date) { String.format("Supported format is %s but found %s", DATE_WITH_TIMESTAMP_PATTERN, date)); } + /** + * assertTrackedEntityDataValueAudit asserts a TrackedEntityDataValueAudit obtained from the db + * and compares it with the expected value, auditType and dataElement. + * + * @param audit The TrackedEntityDataValueAudit entity obtained from persistence + * @param expectedDataElement The audit object is expected to be for this dataElement + * @param expectedAuditType The audit object is expected to have this auditType + * @param expectedValue The audit object is expected to have this value + */ + public static void assertTrackedEntityDataValueAudit( + TrackedEntityDataValueAudit audit, + DataElement expectedDataElement, + AuditType expectedAuditType, + String expectedValue) { + assertAll( + () -> assertNotNull(audit), + () -> + assertEquals( + expectedAuditType, + audit.getAuditType(), + () -> + "Expected audit type is " + + expectedAuditType + + " but found " + + audit.getAuditType()), + () -> + assertEquals( + audit.getDataElement().getUid(), + expectedDataElement.getUid(), + () -> + "Expected dataElement is " + + expectedDataElement.getUid() + + " but found " + + audit.getDataElement().getUid()), + () -> + assertEquals( + expectedValue, + audit.getValue(), + () -> "Expected value is " + expectedValue + " but found " + audit.getValue())); + } + private static boolean hasTimeStamp(Date date) { try { diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/bundle/TrackedEntityDataValueAuditTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/bundle/TrackedEntityDataValueAuditTest.java index d700114c8973..1973642dd46f 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/bundle/TrackedEntityDataValueAuditTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/bundle/TrackedEntityDataValueAuditTest.java @@ -28,8 +28,8 @@ package org.hisp.dhis.tracker.bundle; import static org.hisp.dhis.tracker.Assertions.assertNoErrors; +import static org.hisp.dhis.tracker.Assertions.assertTrackedEntityDataValueAudit; import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -114,42 +114,15 @@ void testTrackedEntityDataValueAuditCreate() throws IOException { assertAll( () -> assertNotNull(createdAudit), () -> assertNotNull(updatedAudit), - () -> assertNotNull(deletedAudit)); - assertAuditCollection(createdAudit, AuditType.CREATE, ORIGINAL_VALUE); - assertAuditCollection(updatedAudit, AuditType.UPDATE, ORIGINAL_VALUE); - assertAuditCollection(deletedAudit, AuditType.DELETE, UPDATED_VALUE); - } - - private void assertAuditCollection( - List audits, AuditType auditType, String expectedValue) { - assertAll( - () -> assertFalse(audits.isEmpty()), - () -> - assertEquals( - auditType, - audits.get(0).getAuditType(), - () -> - "Expected audit type is " - + auditType - + " but found " - + audits.get(0).getAuditType()), - () -> - assertEquals( - audits.get(0).getDataElement().getUid(), - dataElement.getUid(), - () -> - "Expected dataElement is " - + dataElement.getUid() - + " but found " - + audits.get(0).getDataElement().getUid()), - () -> - assertEquals( - expectedValue, - audits.get(0).getValue(), - () -> - "Expected value is " - + expectedValue - + " but found " - + audits.get(0).getValue())); + () -> assertNotNull(deletedAudit), + () -> assertFalse(createdAudit.isEmpty()), + () -> assertFalse(updatedAudit.isEmpty()), + () -> assertFalse(deletedAudit.isEmpty())); + assertTrackedEntityDataValueAudit( + createdAudit.get(0), dataElement, AuditType.CREATE, ORIGINAL_VALUE); + assertTrackedEntityDataValueAudit( + updatedAudit.get(0), dataElement, AuditType.UPDATE, ORIGINAL_VALUE); + assertTrackedEntityDataValueAudit( + deletedAudit.get(0), dataElement, AuditType.DELETE, UPDATED_VALUE); } }