diff --git a/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncService.java b/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncService.java index 80f607a3b81ae1f6aaa283536cebd6f81cee8a6a..751d4767ff3075d6b5b8f2e06c2be4810365c2e4 100644 --- a/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncService.java +++ b/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncService.java @@ -90,7 +90,8 @@ class SyncService { } public void syncAddedOrganisationsEinheiten(long syncTimestamp) { - sortInAdditionOrder(repository.findAllWithoutSyncResult()).forEach(organisationsEinheit -> syncAddedOrganisationsEinheit(syncTimestamp, organisationsEinheit)); + sortInAdditionOrder(repository.findAllWithoutSyncResult()).forEach( + organisationsEinheit -> syncAddedOrganisationsEinheit(syncTimestamp, organisationsEinheit)); } Stream<OrganisationsEinheit> sortInAdditionOrder(Stream<OrganisationsEinheit> organisationsEinheiten) { @@ -115,23 +116,30 @@ class SyncService { } void syncAddedOrganisationsEinheit(long syncTimestamp, OrganisationsEinheit organisationsEinheit) { - var addGroupData = organisationsEinheitMapper.toAddGroupData(organisationsEinheit, findParentKeycloakId(organisationsEinheit)); - var keycloakId = addGroupInKeycloak(addGroupData); - if (keycloakId.isPresent()) { - var updatedOrganisationsEinheit = organisationsEinheit.toBuilder() - .keycloakId(keycloakId.get()) - .syncResult(SyncResult.OK) - .lastSyncTimestamp(syncTimestamp) - .build(); - repository.save(updatedOrganisationsEinheit); + var parent = findParent(organisationsEinheit); + if (parent.isEmpty() || parent.get().getKeycloakId() != null) { + var parentKeycloakId = parent.map(OrganisationsEinheit::getKeycloakId).orElse(null); + addAsGroupInKeycloak(organisationsEinheit, parentKeycloakId).ifPresent( + addedGroupId -> updateAfterSuccessfulGroupCreation(organisationsEinheit, syncTimestamp, addedGroupId)); } } - String findParentKeycloakId(OrganisationsEinheit organisationsEinheit) { - if (organisationsEinheit.getParentId() == null) { - return null; - } - return repository.findById(organisationsEinheit.getParentId()).map(OrganisationsEinheit::getKeycloakId).orElse(null); + Optional<OrganisationsEinheit> findParent(OrganisationsEinheit organisationsEinheit) { + return Optional.ofNullable(organisationsEinheit.getParentId()).flatMap(repository::findById); + } + + Optional<String> addAsGroupInKeycloak(OrganisationsEinheit organisationsEinheit, String parentKeycloakId) { + var addGroupData = organisationsEinheitMapper.toAddGroupData(organisationsEinheit, parentKeycloakId); + return addGroupInKeycloak(addGroupData); + } + + void updateAfterSuccessfulGroupCreation(OrganisationsEinheit organisationsEinheit, long syncTimestamp, String keycloakId) { + var updatedOrganisationsEinheit = organisationsEinheit.toBuilder() + .keycloakId(keycloakId) + .syncResult(SyncResult.OK) + .lastSyncTimestamp(syncTimestamp) + .build(); + repository.save(updatedOrganisationsEinheit); } Optional<String> addGroupInKeycloak(AddGroupData addGroupData) { diff --git a/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncAddedOrganisationsEinheitenITCase.java b/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncAddedOrganisationsEinheitenITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..9204b58dc98768fb140ab4479ab7fe9651324ad5 --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncAddedOrganisationsEinheitenITCase.java @@ -0,0 +1,109 @@ +package de.ozgcloud.admin.organisationseinheit; + +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.groups.Tuple.tuple; +import static org.mockito.Mockito.*; + +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.test.context.ContextConfiguration; + +import de.ozgcloud.admin.common.KeycloakInitializer; +import de.ozgcloud.admin.keycloak.Group; +import de.ozgcloud.admin.keycloak.KeycloakRemoteService; +import de.ozgcloud.admin.keycloak.ResourceCreationException; +import de.ozgcloud.common.test.DbInitializer; +import de.ozgcloud.common.test.ITCase; + +@ITCase +@ContextConfiguration(initializers = { DbInitializer.class, KeycloakInitializer.class }) +class SyncAddedOrganisationsEinheitenITCase { + + @Autowired + private SyncService service; + @SpyBean + private KeycloakRemoteService keycloakRemoteService; + @Autowired + private MongoOperations operations; + + private final long syncTimestamp = Instant.now().toEpochMilli(); + + @BeforeEach + void clearDatabase() { + operations.dropCollection(OrganisationsEinheit.class); + } + + @Test + void shouldSynchronizeAddedTopLevelGroup() { + var topLevel = topLevel("shouldSynchronizeAddedTopLevelGroup"); + operations.save(topLevel); + + service.syncAddedOrganisationsEinheiten(syncTimestamp); + + assertThat(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()) + .extracting(Group::getName, Group::getOrganisationsEinheitId, Group::getSubGroups) + .contains(tuple(topLevel.getName(), topLevel.getOrganisationsEinheitId(), List.of())); + } + + @Test + void shouldSynchronizeAddedTopLevelGroupWithChild() { + var topLevel = topLevel("shouldSynchronizeAddedTopLevelGroupWithChild"); + var childLevel1 = childLevel("shouldSynchronizeAddedTopLevelGroupWithChild", topLevel); + operations.save(topLevel); + operations.save(childLevel1); + + service.syncAddedOrganisationsEinheiten(syncTimestamp); + + assertThat(findGroupInKeycloak(topLevel.getName())).isPresent().get() + .extracting(Group::getSubGroups).asList().extracting("name") + .containsExactly(childLevel1.getName()); + } + + @Test + void shouldNotAddChildIfParentWasNotSynchronized() { + var topLevel = topLevel("shouldNotAddChildIfParentWasNotSynchronized"); + var childLevel1 = childLevel("shouldNotAddChildIfParentWasNotSynchronized", topLevel); + operations.save(topLevel); + operations.save(childLevel1); + doThrow(new ResourceCreationException("OMG!")).when(keycloakRemoteService) + .addGroup(argThat(addGroupData -> addGroupData.getName().equals(topLevel.getName()))); + + service.syncAddedOrganisationsEinheiten(syncTimestamp); + + assertThat(findGroupInKeycloak(childLevel1.getName())).isEmpty(); + } + + private static OrganisationsEinheit topLevel(String nameSuffix) { + return OrganisationsEinheitTestFactory.createBuilder() + .id(UUID.randomUUID().toString()) + .parentId(null) + .keycloakId(null) + .syncResult(null) + .name("topLevel (%s)".formatted(nameSuffix)) + .build(); + } + + private static OrganisationsEinheit childLevel(String nameSuffix, OrganisationsEinheit parent) { + return OrganisationsEinheitTestFactory.createBuilder() + .id(UUID.randomUUID().toString()) + .parentId(parent.getId()) + .keycloakId(null) + .syncResult(null) + .name("childLevel1 (%s)".formatted(nameSuffix)) + .build(); + } + + private Optional<Group> findGroupInKeycloak(String groupName) { + return keycloakRemoteService.getGroupsWithOrganisationsEinheitId() + .filter(group -> groupName.equals(group.getName())) + .findFirst(); + } +} diff --git a/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncServiceTest.java b/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncServiceTest.java index 875324ebf97e9efc9133b313ea8b41eda2a981fc..c448edf7fc37374623679fb861e5cc062bc6058f 100644 --- a/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncServiceTest.java +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncServiceTest.java @@ -422,8 +422,8 @@ class SyncServiceTest { private final long syncTimestamp = Instant.now().toEpochMilli(); private final OrganisationsEinheit withoutSyncResult1 = OrganisationsEinheitTestFactory.createBuilder().id("A").build(); private final OrganisationsEinheit withoutSyncResult2 = OrganisationsEinheitTestFactory.createBuilder().id("B").build(); - private final OrganisationsEinheit[] unsortedOrganisationsEinheiten = new OrganisationsEinheit[]{withoutSyncResult2, withoutSyncResult1}; - private final OrganisationsEinheit[] sortedOrganisationsEinheiten = new OrganisationsEinheit[]{withoutSyncResult1, withoutSyncResult2}; + private final OrganisationsEinheit[] unsortedOrganisationsEinheiten = new OrganisationsEinheit[] { withoutSyncResult2, withoutSyncResult1 }; + private final OrganisationsEinheit[] sortedOrganisationsEinheiten = new OrganisationsEinheit[] { withoutSyncResult1, withoutSyncResult2 }; @Captor private ArgumentCaptor<Stream<OrganisationsEinheit>> streamArgumentCaptor; @@ -517,86 +517,109 @@ class SyncServiceTest { class TestSyncAddedOrganisationsEinheit { private final long syncTimestamp = Instant.now().toEpochMilli(); - private final OrganisationsEinheit organisationsEinheit = OrganisationsEinheitTestFactory.create(); - private final AddGroupData addGroupData = AddGroupDataTestFactory.create(); - private final String keycloakId = GroupTestFactory.ID; + private final String addedGroupId = UUID.randomUUID().toString(); private final String parentKeycloakId = UUID.randomUUID().toString(); - - @BeforeEach - void setUp() { - doReturn(parentKeycloakId).when(service).findParentKeycloakId(organisationsEinheit); - when(organisationsEinheitMapper.toAddGroupData(organisationsEinheit, parentKeycloakId)).thenReturn(addGroupData); - } + private final OrganisationsEinheit organisationsEinheit = OrganisationsEinheitTestFactory.create(); + private final OrganisationsEinheit parentWithoutKeycloakId = OrganisationsEinheitTestFactory.createBuilder().keycloakId(null).build(); + private final OrganisationsEinheit parentWithKeycloakId = OrganisationsEinheitTestFactory.createBuilder().keycloakId(parentKeycloakId).build(); @Test void shouldFindParentKeycloakId() { - mockAddGroupInKeycloak(); + doReturn(Optional.of(parentWithoutKeycloakId)).when(service).findParent(organisationsEinheit); callService(); - verify(service).findParentKeycloakId(organisationsEinheit); + verify(service).findParent(organisationsEinheit); } - @Test - void shouldCreateAddGroupData() { - mockAddGroupInKeycloak(); - - callService(); - - verify(organisationsEinheitMapper).toAddGroupData(organisationsEinheit, parentKeycloakId); - } + @Nested + class OnParentHasNoKeycloakId { - @Test - void shouldAddGroupInKeycloak() { - mockAddGroupInKeycloak(); + @BeforeEach + void init() { + doReturn(Optional.of(parentWithoutKeycloakId)).when(service).findParent(organisationsEinheit); + } - callService(); + @Test + void shouldNotAddAsGroupInKeycloak() { + callService(); - verify(service).addGroupInKeycloak(addGroupData); + verify(service, never()).addAsGroupInKeycloak(any(), any()); + } } @Nested - class OnAddGroupInKeycloakSuccessful { - - @Captor - private ArgumentCaptor<OrganisationsEinheit> savedOrganisationsEinheitArgumentCaptor; + class OnOrganisationsEinheitIsTopLevel { @BeforeEach void init() { - doReturn(Optional.of(keycloakId)).when(service).addGroupInKeycloak(addGroupData); + doReturn(Optional.empty()).when(service).findParent(organisationsEinheit); } @Test - void shouldSaveIfAddGroupSuccessful() { + void shouldAddAsGroupInKeycloakWithNullParent() { + doReturn(Optional.empty()).when(service).addAsGroupInKeycloak(organisationsEinheit, null); + callService(); - verify(repository).save(savedOrganisationsEinheitArgumentCaptor.capture()); - assertThat(savedOrganisationsEinheitArgumentCaptor.getValue()).extracting( - OrganisationsEinheit::getKeycloakId, - OrganisationsEinheit::getSyncResult, - OrganisationsEinheit::getLastSyncTimestamp - ).containsExactly(keycloakId, SyncResult.OK, syncTimestamp); + verify(service).addAsGroupInKeycloak(organisationsEinheit, null); + } + + @Test + void shouldUpdateAfterSuccessfulGroupCreation() { + doReturn(Optional.of(addedGroupId)).when(service).addAsGroupInKeycloak(organisationsEinheit, null); + doNothing().when(service).updateAfterSuccessfulGroupCreation(organisationsEinheit, syncTimestamp, addedGroupId); + + callService(); + + verify(service).updateAfterSuccessfulGroupCreation(organisationsEinheit, syncTimestamp, addedGroupId); + } + + @Test + void shouldNotUpdate() { + doReturn(Optional.empty()).when(service).addAsGroupInKeycloak(organisationsEinheit, null); + + callService(); + + verify(service, never()).updateAfterSuccessfulGroupCreation(any(), anyLong(), any()); } } @Nested - class OnAddGroupInKeycloakFailed { + class OnParentHasKeycloakId { @BeforeEach void init() { - doReturn(Optional.empty()).when(service).addGroupInKeycloak(addGroupData); + doReturn(Optional.of(parentWithKeycloakId)).when(service).findParent(organisationsEinheit); } @Test - void shouldNotSaveIfAddGroupFailed() { + void shouldAddAsGroupInKeycloakWithNullParent() { + doReturn(Optional.empty()).when(service).addAsGroupInKeycloak(organisationsEinheit, parentKeycloakId); + callService(); - verify(repository, never()).save(any()); + verify(service).addAsGroupInKeycloak(organisationsEinheit, parentKeycloakId); } - } - private void mockAddGroupInKeycloak() { - doReturn(Optional.empty()).when(service).addGroupInKeycloak(addGroupData); + @Test + void shouldUpdateAfterSuccessfulGroupCreation() { + doReturn(Optional.of(addedGroupId)).when(service).addAsGroupInKeycloak(organisationsEinheit, parentKeycloakId); + doNothing().when(service).updateAfterSuccessfulGroupCreation(organisationsEinheit, syncTimestamp, addedGroupId); + + callService(); + + verify(service).updateAfterSuccessfulGroupCreation(organisationsEinheit, syncTimestamp, addedGroupId); + } + + @Test + void shouldNotUpdate() { + doReturn(Optional.empty()).when(service).addAsGroupInKeycloak(organisationsEinheit, parentKeycloakId); + + callService(); + + verify(service, never()).updateAfterSuccessfulGroupCreation(any(), anyLong(), any()); + } } private void callService() { @@ -605,15 +628,15 @@ class SyncServiceTest { } @Nested - class TestFindParentKeycloakId { + class TestFindParent { @Nested class OnParentIdIsNull { @Test - void shouldReturnNull() { - var parentKeycloakId = service.findParentKeycloakId(OrganisationsEinheitTestFactory.createBuilder().parentId(null).build()); + void shouldReturnEmpty() { + var parent = service.findParent(OrganisationsEinheitTestFactory.createBuilder().parentId(null).build()); - assertThat(parentKeycloakId).isNull(); + assertThat(parent).isEmpty(); } } @@ -631,20 +654,87 @@ class SyncServiceTest { @Test void shouldFindById() { - service.findParentKeycloakId(organisationsEinheit); + callService(); verify(repository).findById(OrganisationsEinheitTestFactory.PARENT_ID); } @Test - void shouldReturnParentKeycloakId() { - var parentKeycloakId = service.findParentKeycloakId(organisationsEinheit); + void shouldReturnParent() { + var found = callService(); - assertThat(parentKeycloakId).isEqualTo(PARENT_KEYCLOAK_ID); + assertThat(found).isPresent().get().isEqualTo(parent); } + + private Optional<OrganisationsEinheit> callService() { + return service.findParent(organisationsEinheit); + } + } + } + + @Nested + class TestAddAsGroupInKeycloak { + + private final OrganisationsEinheit organisationsEinheit = OrganisationsEinheitTestFactory.create(); + private final String parentKeycloakId = UUID.randomUUID().toString(); + private final AddGroupData addGroupData = AddGroupDataTestFactory.create(); + private final String addedGroupId = UUID.randomUUID().toString(); + + @BeforeEach + void init() { + when(organisationsEinheitMapper.toAddGroupData(organisationsEinheit, parentKeycloakId)).thenReturn(addGroupData); + doReturn(Optional.of(addedGroupId)).when(service).addGroupInKeycloak(addGroupData); } + @Test + void shouldCreateAddGroupData() { + callService(); + + verify(organisationsEinheitMapper).toAddGroupData(organisationsEinheit, parentKeycloakId); + } + @Test + void shouldAddGroupInKeycloak() { + callService(); + + verify(service).addGroupInKeycloak(addGroupData); + } + + @Test + void shouldReturnAddedGroupId() { + var returnedGroupId = callService(); + + assertThat(returnedGroupId).get().isEqualTo(addedGroupId); + } + + private Optional<String> callService() { + return service.addAsGroupInKeycloak(organisationsEinheit, parentKeycloakId); + } + } + + @Nested + class TestUpdateAfterSuccessfulGroupCreation { + + private final OrganisationsEinheit organisationsEinheit = OrganisationsEinheitTestFactory.createBuilder() + .keycloakId(null) + .syncResult(null) + .lastSyncTimestamp(null) + .build(); + private final long syncTimestamp = Instant.now().toEpochMilli(); + private final String keycloakId = UUID.randomUUID().toString(); + + @Captor + private ArgumentCaptor<OrganisationsEinheit> organisationsEinheitArgumentCaptor; + + @Test + void shouldSaveUpdatedOrganisationsEinheit() { + service.updateAfterSuccessfulGroupCreation(organisationsEinheit, syncTimestamp, keycloakId); + + verify(repository).save(organisationsEinheitArgumentCaptor.capture()); + assertThat(organisationsEinheitArgumentCaptor.getValue()) + .extracting(OrganisationsEinheit::getKeycloakId, OrganisationsEinheit::getSyncResult, OrganisationsEinheit::getLastSyncTimestamp) + .containsExactly(keycloakId, SyncResult.OK, syncTimestamp); + } } @Nested