diff --git a/src/main/java/de/ozgcloud/admin/keycloak/KeycloakApiService.java b/src/main/java/de/ozgcloud/admin/keycloak/KeycloakApiService.java index 057d0ab9622b949f7214008b131e956d76b70629..09b865543a58b34c7a89233f6f75ee0253bc8d6c 100644 --- a/src/main/java/de/ozgcloud/admin/keycloak/KeycloakApiService.java +++ b/src/main/java/de/ozgcloud/admin/keycloak/KeycloakApiService.java @@ -20,11 +20,17 @@ class KeycloakApiService { } public String addGroup(GroupRepresentation groupRepresentation) { - try (var response = groupsResource.add(groupRepresentation)) { + try (var response = addParentOrChildGroup(groupRepresentation)) { return getAddedResourceIdFromResponse(response); } } + Response addParentOrChildGroup(GroupRepresentation groupRepresentation) { + return groupRepresentation.getParentId() == null ? + groupsResource.add(groupRepresentation) : + groupsResource.group(groupRepresentation.getParentId()).subGroup(groupRepresentation); + } + String getAddedResourceIdFromResponse(Response response) { if (response.getStatus() == Response.Status.CREATED.getStatusCode()) { return extractResourceIdFromLocationHeader(response.getHeaderString("Location")); diff --git a/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheit.java b/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheit.java index 0b6bfbbfc86229fa798b1c43671a1af904233526..012a2343ca6c64140adfd28740791b94c1ec8769 100644 --- a/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheit.java +++ b/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheit.java @@ -31,7 +31,7 @@ public class OrganisationsEinheit { @JsonIgnore private String zufiId; @JsonIgnore - private long lastSyncTimestamp; + private Long lastSyncTimestamp; @JsonIgnore private String parentId; @JsonIgnore diff --git a/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitMapper.java b/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitMapper.java index da1634ec80bf9a5169022184c50bba014dd735a4..193a6f05d8410e8733aad684fc8a4fb6761825f5 100644 --- a/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitMapper.java +++ b/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitMapper.java @@ -13,5 +13,6 @@ interface OrganisationsEinheitMapper { @Mapping(target = "zufiId", source = "id") OrganisationsEinheit fromGrpc(GrpcOrganisationsEinheit grpcOrganisationsEinheit); - AddGroupData toAddGroupData(OrganisationsEinheit organisationsEinheit); + @Mapping(target = "parentId", source = "parentKeycloakId") + AddGroupData toAddGroupData(OrganisationsEinheit organisationsEinheit, String parentKeycloakId); } diff --git a/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncScheduler.java b/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncScheduler.java index be92837909c12a4cd9c7c93c32decfa05d7e4e0a..116a7e0c61dd25b35b87dff650d47f5fc8566697 100644 --- a/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncScheduler.java +++ b/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncScheduler.java @@ -14,9 +14,11 @@ class SyncScheduler { private final SyncService syncService; - @SchedulerLock(name = "SyncScheduler_syncOrganisationsEinheitenFromKeycloak", lockAtLeastFor = "PT5M", lockAtMostFor = "PT23H") + @SchedulerLock(name = "SyncScheduler_syncOrganisationsEinheitenWithKeycloak", lockAtLeastFor = "PT5M", lockAtMostFor = "PT23H") @Scheduled(cron = "${ozgcloud.administration.sync.organisationseinheiten.cron}") - public void syncOrganisationsEinheitenFromKeycloak() { - syncService.syncOrganisationsEinheitenFromKeycloak(Instant.now().toEpochMilli()); + public void syncOrganisationsEinheitenWithKeycloak() { + var syncTimestamp = Instant.now().toEpochMilli(); + syncService.syncOrganisationsEinheitenFromKeycloak(syncTimestamp); + syncService.syncAddedOrganisationsEinheiten(syncTimestamp); } } diff --git a/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncService.java b/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncService.java index 98139f6fa92396ba563328d59275744fadc6a802..80f607a3b81ae1f6aaa283536cebd6f81cee8a6a 100644 --- a/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncService.java +++ b/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncService.java @@ -2,6 +2,7 @@ package de.ozgcloud.admin.organisationseinheit; import java.util.List; import java.util.Optional; +import java.util.stream.Stream; import org.springframework.stereotype.Service; @@ -89,11 +90,32 @@ class SyncService { } public void syncAddedOrganisationsEinheiten(long syncTimestamp) { - repository.findAllWithoutSyncResult().forEach(organisationsEinheit -> syncAddedOrganisationsEinheit(syncTimestamp, organisationsEinheit)); + sortInAdditionOrder(repository.findAllWithoutSyncResult()).forEach(organisationsEinheit -> syncAddedOrganisationsEinheit(syncTimestamp, organisationsEinheit)); + } + + Stream<OrganisationsEinheit> sortInAdditionOrder(Stream<OrganisationsEinheit> organisationsEinheiten) { + return organisationsEinheiten.sorted((org1, org2) -> { + if (org1.getParentId() == null && org2.getParentId() == null) { + return 0; + } + if (org1.getParentId() == null) { + return -1; + } + if (org2.getParentId() == null) { + return 1; + } + if (org1.getId().equals(org2.getParentId())) { + return -1; + } + if (org1.getParentId().equals(org2.getId())) { + return 1; + } + return 0; + }); } void syncAddedOrganisationsEinheit(long syncTimestamp, OrganisationsEinheit organisationsEinheit) { - var addGroupData = organisationsEinheitMapper.toAddGroupData(organisationsEinheit); + var addGroupData = organisationsEinheitMapper.toAddGroupData(organisationsEinheit, findParentKeycloakId(organisationsEinheit)); var keycloakId = addGroupInKeycloak(addGroupData); if (keycloakId.isPresent()) { var updatedOrganisationsEinheit = organisationsEinheit.toBuilder() @@ -105,6 +127,13 @@ class SyncService { } } + String findParentKeycloakId(OrganisationsEinheit organisationsEinheit) { + if (organisationsEinheit.getParentId() == null) { + return null; + } + return repository.findById(organisationsEinheit.getParentId()).map(OrganisationsEinheit::getKeycloakId).orElse(null); + } + Optional<String> addGroupInKeycloak(AddGroupData addGroupData) { try { return Optional.of(keycloakRemoteService.addGroup(addGroupData)); diff --git a/src/test/java/de/ozgcloud/admin/keycloak/GroupRepresentationTestFactory.java b/src/test/java/de/ozgcloud/admin/keycloak/GroupRepresentationTestFactory.java index 67dcc55771e8d52ae9f4d926e9568a22d47795ac..034f962513dc0ffefe78f26ab9d7755281d763a0 100644 --- a/src/test/java/de/ozgcloud/admin/keycloak/GroupRepresentationTestFactory.java +++ b/src/test/java/de/ozgcloud/admin/keycloak/GroupRepresentationTestFactory.java @@ -13,6 +13,7 @@ class GroupRepresentationTestFactory { public static final String ORGANIZATIONS_EINHEIT_ID_ATTRIBUTE = "organisationseinheitId"; public static final String ID = UUID.randomUUID().toString(); + public static final String PARENT_ID = UUID.randomUUID().toString(); public static final String NAME = LoremIpsum.getInstance().getName(); public static final String ORGANISATIONS_EINHEIT_ID = UUID.randomUUID().toString(); public static final Map<String, List<String>> ATTRIBUTES = Map.of(ORGANIZATIONS_EINHEIT_ID_ATTRIBUTE, List.of(ORGANISATIONS_EINHEIT_ID)); @@ -43,6 +44,7 @@ class GroupRepresentationTestFactory { if (withId) { groupRepresentation.setId(ID); } + groupRepresentation.setParentId(PARENT_ID); groupRepresentation.setName(NAME); groupRepresentation.setAttributes(ATTRIBUTES); groupRepresentation.setSubGroups(List.of(createSubGroup(withId))); @@ -54,6 +56,7 @@ class GroupRepresentationTestFactory { if (withId) { groupRepresentation.setId(SUB_GROUP_ID); } + groupRepresentation.setParentId(ID); groupRepresentation.setName(SUB_GROUP_NAME); groupRepresentation.setAttributes(SUB_GROUP_ATTRIBUTES); return groupRepresentation; diff --git a/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceITCase.java b/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceITCase.java index 848bc65b7ba24f6fd02eb89ea90d6afe441a4141..f4bbd5036da9a34a15704c19209e2d4571eb9705 100644 --- a/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceITCase.java +++ b/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceITCase.java @@ -8,6 +8,7 @@ import java.util.Optional; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.keycloak.admin.client.resource.GroupsResource; import org.keycloak.representations.idm.GroupRepresentation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; @@ -25,6 +26,8 @@ class KeycloakApiServiceITCase { private KeycloakApiService service; @Autowired private KeycloakApiProperties properties; + @Autowired + private GroupsResource groupsResource; @Nested class TestGetAllGroups { @@ -146,7 +149,7 @@ class KeycloakApiServiceITCase { } @Test - void shouldNotAddSubgroups() { + void shouldNotAddSubgroupsOfAddedGroup() { var groupToAdd = createUniqueGroupRepresentation("shouldNotAddSubgroups"); var groupId = service.addGroup(groupToAdd); @@ -155,10 +158,26 @@ class KeycloakApiServiceITCase { .asList().isEmpty(); } - private GroupRepresentation createUniqueGroupRepresentation(String suffix) { + @Test + void shouldAddSubgroupToParent() { + var parentToAdd = createUniqueGroupRepresentation("shouldAddSubgroupToParent-parent"); + var parentId = service.addGroup(parentToAdd); + var childToAdd = createUniqueGroupRepresentation("shouldAddSubgroupToParent-child"); + childToAdd.setParentId(parentId); + + var childId = service.addGroup(childToAdd); + + var subgroupsInKc = groupsResource.group(parentId).getSubGroups(0, Integer.MAX_VALUE, true); + assertThat(subgroupsInKc).hasSize(1).first().extracting(GroupRepresentation::getId, GroupRepresentation::getParentId) + .contains(childId, parentId); + } + + private GroupRepresentation createUniqueGroupRepresentation(String nameSuffix) { // LoremIpsum does not guarantee unique results when called repeatedly - var groupName = "%s (%s)".formatted(LoremIpsum.getInstance().getName(), suffix); - return GroupRepresentationTestFactory.createWithoutId(groupName); + var groupName = "%s (%s)".formatted(LoremIpsum.getInstance().getName(), nameSuffix); + var group = GroupRepresentationTestFactory.createWithoutId(groupName); + group.setParentId(null); + return group; } private Optional<GroupRepresentation> findGroupInKeycloak(String groupId) { diff --git a/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceTest.java b/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceTest.java index ffded7cebae2a78c00c622ba341eb840eec4f760..f04a47581dc3c2d7c67a79edb6f4ec3f1c69572b 100644 --- a/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceTest.java +++ b/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceTest.java @@ -6,6 +6,7 @@ import static org.mockito.Mockito.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.keycloak.admin.client.resource.GroupResource; import org.keycloak.admin.client.resource.GroupsResource; import org.keycloak.representations.idm.GroupRepresentation; import org.mockito.InjectMocks; @@ -34,15 +35,15 @@ class KeycloakApiServiceTest { @BeforeEach void init() { - when(groupsResource.add(groupRepresentation)).thenReturn(response); + doReturn(response).when(service).addParentOrChildGroup(groupRepresentation); doReturn(RESOURCE_ID).when(service).getAddedResourceIdFromResponse(response); } @Test - void shouldCallGroupsResource() { + void shouldAddParentOrChildGroup() { callService(); - verify(groupsResource).add(groupRepresentation); + verify(service).addParentOrChildGroup(groupRepresentation); } @Test @@ -64,6 +65,69 @@ class KeycloakApiServiceTest { } } + @Nested + class TestAddParentOrChildGroup { + + private GroupRepresentation groupRepresentation; + + @Nested + class OnParentGroup { + + @BeforeEach + void init() { + groupRepresentation = GroupRepresentationTestFactory.create(); + groupRepresentation.setParentId(null); + when(groupsResource.add(groupRepresentation)).thenReturn(response); + } + + @Test + void shouldCallGroupsResource() { + callService(); + + verify(groupsResource).add(groupRepresentation); + } + + @Test + void shouldReturnResponse() { + var serviceResponse = callService(); + + assertThat(serviceResponse).isEqualTo(response); + } + } + + @Nested + class OnChildGroup { + + @Mock + private GroupResource groupResource; + + @BeforeEach + void init() { + groupRepresentation = GroupRepresentationTestFactory.create(); + when(groupsResource.group(GroupRepresentationTestFactory.PARENT_ID)).thenReturn(groupResource); + when(groupResource.subGroup(groupRepresentation)).thenReturn(response); + } + + @Test + void shouldCallGroupResource() { + callService(); + + verify(groupResource).subGroup(groupRepresentation); + } + + @Test + void shouldReturnResponse() { + var serviceResponse = callService(); + + assertThat(serviceResponse).isEqualTo(response); + } + } + + private Response callService() { + return service.addParentOrChildGroup(groupRepresentation); + } + } + @Nested class TestGetAddedResourceIdFromResponse { diff --git a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitMapperTest.java b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitMapperTest.java index 10ee3a2de7fd8c740093d84f0444b90cf7b9d19a..3298e01121a50ad9858bec40e0828472e894e4a2 100644 --- a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitMapperTest.java +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitMapperTest.java @@ -3,6 +3,7 @@ package de.ozgcloud.admin.organisationseinheit; import static org.assertj.core.api.Assertions.*; import java.util.Collections; +import java.util.UUID; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -44,16 +45,18 @@ class OrganisationsEinheitMapperTest { @Nested class TestToAddGroupData { + public static final String PARENT_KEYCLOAK_ID = UUID.randomUUID().toString(); + @Test void shouldMap() { - var mapped = mapper.toAddGroupData(OrganisationsEinheitTestFactory.create()); + var mapped = mapper.toAddGroupData(OrganisationsEinheitTestFactory.create(), PARENT_KEYCLOAK_ID); assertThat(mapped).extracting( AddGroupData::getParentId, AddGroupData::getName, AddGroupData::getOrganisationsEinheitId ).containsExactly( - OrganisationsEinheitTestFactory.PARENT_ID, + PARENT_KEYCLOAK_ID, OrganisationsEinheitTestFactory.NAME, OrganisationsEinheitTestFactory.ORGANISATIONS_EINHEIT_ID ); diff --git a/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncSchedulerTest.java b/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncSchedulerTest.java index a37a4ef8e34d65490814140497c972e90b57d0d3..c78510c4c8c77dabfe5bf774382775c6959af0fa 100644 --- a/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncSchedulerTest.java +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncSchedulerTest.java @@ -5,7 +5,7 @@ import static org.mockito.Mockito.*; import java.time.Instant; -import org.assertj.core.data.Offset; +import org.assertj.core.data.*; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -22,18 +22,34 @@ class SyncSchedulerTest { private SyncService syncService; @Nested - class TestSyncOrganisationsEinheitenFromKeycloak { + class TestSyncOrganisationsEinheitenWithKeycloak { @Captor private ArgumentCaptor<Long> syncTimestampArgumentCaptor; @Test - void shouldCallService() { - scheduler.syncOrganisationsEinheitenFromKeycloak(); + void shouldSyncOrganisationsEinheitenFromKeycloak() { + callService(); verify(syncService).syncOrganisationsEinheitenFromKeycloak(syncTimestampArgumentCaptor.capture()); + assertThatSyncTimestampIsCloseToNow(); + } + + @Test + void shouldSyncAddedOrganisationsEinheiten() { + callService(); + + verify(syncService).syncAddedOrganisationsEinheiten(syncTimestampArgumentCaptor.capture()); + assertThatSyncTimestampIsCloseToNow(); + } + + private void assertThatSyncTimestampIsCloseToNow() { assertThat(syncTimestampArgumentCaptor.getValue()).isCloseTo(Instant.now().toEpochMilli(), Offset.offset(5000L)); } + + private void callService() { + scheduler.syncOrganisationsEinheitenWithKeycloak(); + } } } \ No newline at end of file diff --git a/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncServiceTest.java b/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncServiceTest.java index 401216f163eb0aadda47be61874496c5ccd57ba8..875324ebf97e9efc9133b313ea8b41eda2a981fc 100644 --- a/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncServiceTest.java +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncServiceTest.java @@ -5,6 +5,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.time.Instant; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -416,15 +417,21 @@ class SyncServiceTest { } @Nested - class TestsyncAddedOrganisationsEinheiten { + class TestSyncAddedOrganisationsEinheiten { 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}; + + @Captor + private ArgumentCaptor<Stream<OrganisationsEinheit>> streamArgumentCaptor; @BeforeEach void setUp() { - when(repository.findAllWithoutSyncResult()).thenReturn(Stream.of(withoutSyncResult1, withoutSyncResult2)); + when(repository.findAllWithoutSyncResult()).thenReturn(Stream.of(unsortedOrganisationsEinheiten)); + doReturn(Stream.of(sortedOrganisationsEinheiten)).when(service).sortInAdditionOrder(any()); doNothing().when(service).syncAddedOrganisationsEinheit(anyLong(), any()); } @@ -435,6 +442,14 @@ class SyncServiceTest { verify(repository).findAllWithoutSyncResult(); } + @Test + void shouldSortInAdditionOrder() { + callService(); + + verify(service).sortInAdditionOrder(streamArgumentCaptor.capture()); + assertThat(streamArgumentCaptor.getValue()).containsExactly(unsortedOrganisationsEinheiten); + } + @Test void shouldSynchronizeFirstOrganisationsEinheit() { callService(); @@ -449,11 +464,55 @@ class SyncServiceTest { verify(service).syncAddedOrganisationsEinheit(syncTimestamp, withoutSyncResult2); } + @Test + void shouldSynchronizeInOrder() { + var inOrder = inOrder(service); + + callService(); + + Arrays.stream(sortedOrganisationsEinheiten).forEach(organisationsEinheit -> + inOrder.verify(service).syncAddedOrganisationsEinheit(syncTimestamp, organisationsEinheit)); + } + private void callService() { service.syncAddedOrganisationsEinheiten(syncTimestamp); } } + @Nested + class TestSortInAdditionOrder { + + @Test + void shouldTopLevelOrgComeFirst() { + var orgWithParent = OrganisationsEinheitTestFactory.create(); + var orgWithoutParent = OrganisationsEinheitTestFactory.createBuilder().parentId(null).build(); + + var sorted = service.sortInAdditionOrder(Stream.of(orgWithParent, orgWithoutParent)); + + assertThat(sorted).containsExactly(orgWithoutParent, orgWithParent); + } + + @Test + void shouldParentComeBeforeChild() { + var parent = OrganisationsEinheitTestFactory.create(); + var child = OrganisationsEinheitTestFactory.createBuilder().parentId(parent.getId()).build(); + + var sorted = service.sortInAdditionOrder(Stream.of(child, parent)); + + assertThat(sorted).containsExactly(parent, child); + } + + @Test + void shouldPreserveOrderForTopLevelGroups() { + var orgWithoutParent1 = OrganisationsEinheitTestFactory.createBuilder().parentId(null).build(); + var orgWithoutParent2 = OrganisationsEinheitTestFactory.createBuilder().parentId(null).build(); + + var sorted = service.sortInAdditionOrder(Stream.of(orgWithoutParent1, orgWithoutParent2)); + + assertThat(sorted).containsExactly(orgWithoutParent1, orgWithoutParent2); + } + } + @Nested class TestSyncAddedOrganisationsEinheit { @@ -461,61 +520,82 @@ class SyncServiceTest { private final OrganisationsEinheit organisationsEinheit = OrganisationsEinheitTestFactory.create(); private final AddGroupData addGroupData = AddGroupDataTestFactory.create(); private final String keycloakId = GroupTestFactory.ID; - - @Captor - private ArgumentCaptor<OrganisationsEinheit> savedOrganisationsEinheitArgumentCaptor; + private final String parentKeycloakId = UUID.randomUUID().toString(); @BeforeEach void setUp() { - when(organisationsEinheitMapper.toAddGroupData(organisationsEinheit)).thenReturn(addGroupData); + doReturn(parentKeycloakId).when(service).findParentKeycloakId(organisationsEinheit); + when(organisationsEinheitMapper.toAddGroupData(organisationsEinheit, parentKeycloakId)).thenReturn(addGroupData); } @Test - void shouldCreateAddGroupData() { - givenAddGroupInKeycloakSuccessful(); + void shouldFindParentKeycloakId() { + mockAddGroupInKeycloak(); callService(); - verify(organisationsEinheitMapper).toAddGroupData(organisationsEinheit); + verify(service).findParentKeycloakId(organisationsEinheit); } @Test - void shouldAddGroupInKeycloak() { - givenAddGroupInKeycloakSuccessful(); + void shouldCreateAddGroupData() { + mockAddGroupInKeycloak(); callService(); - verify(service).addGroupInKeycloak(addGroupData); + verify(organisationsEinheitMapper).toAddGroupData(organisationsEinheit, parentKeycloakId); } @Test - void shouldSaveIfAddGroupSuccessful() { - givenAddGroupInKeycloakSuccessful(); + void shouldAddGroupInKeycloak() { + mockAddGroupInKeycloak(); callService(); - verify(repository).save(savedOrganisationsEinheitArgumentCaptor.capture()); - assertThat(savedOrganisationsEinheitArgumentCaptor.getValue()).extracting( - OrganisationsEinheit::getKeycloakId, - OrganisationsEinheit::getSyncResult, - OrganisationsEinheit::getLastSyncTimestamp - ).containsExactly(keycloakId, SyncResult.OK, syncTimestamp); + verify(service).addGroupInKeycloak(addGroupData); } - @Test - void shouldNotSaveIfAddGroupFailed() { - givenAddGroupInKeycloakFailed(); + @Nested + class OnAddGroupInKeycloakSuccessful { - callService(); + @Captor + private ArgumentCaptor<OrganisationsEinheit> savedOrganisationsEinheitArgumentCaptor; + + @BeforeEach + void init() { + doReturn(Optional.of(keycloakId)).when(service).addGroupInKeycloak(addGroupData); + } + + @Test + void shouldSaveIfAddGroupSuccessful() { + callService(); - verifyNoInteractions(repository); + verify(repository).save(savedOrganisationsEinheitArgumentCaptor.capture()); + assertThat(savedOrganisationsEinheitArgumentCaptor.getValue()).extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly(keycloakId, SyncResult.OK, syncTimestamp); + } } - private void givenAddGroupInKeycloakSuccessful() { - doReturn(Optional.of(keycloakId)).when(service).addGroupInKeycloak(addGroupData); + @Nested + class OnAddGroupInKeycloakFailed { + + @BeforeEach + void init() { + doReturn(Optional.empty()).when(service).addGroupInKeycloak(addGroupData); + } + + @Test + void shouldNotSaveIfAddGroupFailed() { + callService(); + + verify(repository, never()).save(any()); + } } - private void givenAddGroupInKeycloakFailed() { + private void mockAddGroupInKeycloak() { doReturn(Optional.empty()).when(service).addGroupInKeycloak(addGroupData); } @@ -524,6 +604,49 @@ class SyncServiceTest { } } + @Nested + class TestFindParentKeycloakId { + + @Nested + class OnParentIdIsNull { + @Test + void shouldReturnNull() { + var parentKeycloakId = service.findParentKeycloakId(OrganisationsEinheitTestFactory.createBuilder().parentId(null).build()); + + assertThat(parentKeycloakId).isNull(); + } + } + + @Nested + class OnParentIdIsNotNull { + + private final String PARENT_KEYCLOAK_ID = UUID.randomUUID().toString(); + private final OrganisationsEinheit organisationsEinheit = OrganisationsEinheitTestFactory.create(); + private final OrganisationsEinheit parent = OrganisationsEinheitTestFactory.createBuilder().keycloakId(PARENT_KEYCLOAK_ID).build(); + + @BeforeEach + void init() { + when(repository.findById(OrganisationsEinheitTestFactory.PARENT_ID)).thenReturn(Optional.of(parent)); + } + + @Test + void shouldFindById() { + service.findParentKeycloakId(organisationsEinheit); + + verify(repository).findById(OrganisationsEinheitTestFactory.PARENT_ID); + } + + @Test + void shouldReturnParentKeycloakId() { + var parentKeycloakId = service.findParentKeycloakId(organisationsEinheit); + + assertThat(parentKeycloakId).isEqualTo(PARENT_KEYCLOAK_ID); + } + } + + + } + @Nested class TestAddGroupInKeycloak {