From a3ba22d6e15baf8d3c6d27173f1783ba21da2e9e Mon Sep 17 00:00:00 2001 From: OZGCloud <ozgcloud@mgm-tp.com> Date: Thu, 17 Oct 2024 11:51:28 +0200 Subject: [PATCH] OZG-6867 OZG-6895 Return groups with children --- pom.xml | 2 + .../de/ozgcloud/admin/keycloak/Group.java | 2 + .../ozgcloud/admin/keycloak/GroupMapper.java | 3 +- .../admin/keycloak/KeycloakApiFacade.java | 20 ++++ .../admin/keycloak/KeycloakRemoteService.java | 23 +--- .../admin/keycloak/GroupMapperTest.java | 12 +- .../GroupRepresentationTestFactory.java | 20 +++- .../admin/keycloak/GroupTestFactory.java | 7 +- .../keycloak/KeycloakApiFacadeITCase.java | 103 ++++++++++++++++++ .../keycloak/KeycloakRemoteServiceITCase.java | 93 ---------------- .../keycloak/KeycloakRemoteServiceTest.java | 63 +++++++++++ 11 files changed, 221 insertions(+), 127 deletions(-) create mode 100644 src/main/java/de/ozgcloud/admin/keycloak/KeycloakApiFacade.java create mode 100644 src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiFacadeITCase.java delete mode 100644 src/test/java/de/ozgcloud/admin/keycloak/KeycloakRemoteServiceITCase.java create mode 100644 src/test/java/de/ozgcloud/admin/keycloak/KeycloakRemoteServiceTest.java diff --git a/pom.xml b/pom.xml index cf1ce94e..6772822c 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,7 @@ <build.number>SET_BY_JENKINS</build.number> <spring-cloud-config-server.version>4.1.2</spring-cloud-config-server.version> <testcontainers-keycloak.version>3.4.0</testcontainers-keycloak.version> + <keycloak-admin-client.version>25.0.1</keycloak-admin-client.version> <mongock.version>5.4.0</mongock.version> <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version> <mapstruct-processor.version>${mapstruct.version}</mapstruct-processor.version> @@ -76,6 +77,7 @@ <dependency> <groupId>org.keycloak</groupId> <artifactId>keycloak-admin-client</artifactId> + <version>${keycloak-admin-client.version}</version> </dependency> <!-- tools --> diff --git a/src/main/java/de/ozgcloud/admin/keycloak/Group.java b/src/main/java/de/ozgcloud/admin/keycloak/Group.java index 4234dff3..177340cd 100644 --- a/src/main/java/de/ozgcloud/admin/keycloak/Group.java +++ b/src/main/java/de/ozgcloud/admin/keycloak/Group.java @@ -4,6 +4,7 @@ import java.util.List; import lombok.Builder; import lombok.Getter; +import lombok.Singular; @Getter @Builder @@ -11,5 +12,6 @@ public class Group { private String name; private String organisationsEinheitId; + @Singular private List<Group> subGroups; } diff --git a/src/main/java/de/ozgcloud/admin/keycloak/GroupMapper.java b/src/main/java/de/ozgcloud/admin/keycloak/GroupMapper.java index 6a8b4996..63793265 100644 --- a/src/main/java/de/ozgcloud/admin/keycloak/GroupMapper.java +++ b/src/main/java/de/ozgcloud/admin/keycloak/GroupMapper.java @@ -19,8 +19,7 @@ abstract class GroupMapper { public abstract List<Group> fromGroupRepresentations(List<GroupRepresentation> groupRepresentations); @Mapping(target = "organisationsEinheitId", source = "attributes") - @Mapping(target = "subGroups", expression = "java(new ArrayList<Group>())") - abstract Group fromGroupRepresentation(GroupRepresentation groupRepresentation); + public abstract Group fromGroupRepresentation(GroupRepresentation groupRepresentation); String getOrganisationsEinheitId(Map<String, List<String>> attributes) { if (attributes == null) { diff --git a/src/main/java/de/ozgcloud/admin/keycloak/KeycloakApiFacade.java b/src/main/java/de/ozgcloud/admin/keycloak/KeycloakApiFacade.java new file mode 100644 index 00000000..90640f73 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/keycloak/KeycloakApiFacade.java @@ -0,0 +1,20 @@ +package de.ozgcloud.admin.keycloak; + +import java.util.List; + +import org.keycloak.admin.client.resource.GroupsResource; +import org.keycloak.representations.idm.GroupRepresentation; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +class KeycloakApiFacade { + + private final GroupsResource groupsResource; + + public List<GroupRepresentation> getAllGroups() { + return groupsResource.groups("", 0, Integer.MAX_VALUE, false); + } +} diff --git a/src/main/java/de/ozgcloud/admin/keycloak/KeycloakRemoteService.java b/src/main/java/de/ozgcloud/admin/keycloak/KeycloakRemoteService.java index 623e04f4..780e197f 100644 --- a/src/main/java/de/ozgcloud/admin/keycloak/KeycloakRemoteService.java +++ b/src/main/java/de/ozgcloud/admin/keycloak/KeycloakRemoteService.java @@ -1,11 +1,7 @@ package de.ozgcloud.admin.keycloak; -import java.util.List; import java.util.stream.Stream; -import org.keycloak.admin.client.resource.GroupResource; -import org.keycloak.admin.client.resource.GroupsResource; -import org.keycloak.representations.idm.GroupRepresentation; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; @@ -14,23 +10,10 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class KeycloakRemoteService { - private final GroupsResource groupsResource; + private KeycloakApiFacade apiFacade; + private GroupMapper groupMapper; public Stream<Group> getGroups() { -// realmResource.groups().groups(); - return Stream.empty(); - } - - // TODO: only for research - public List<GroupRepresentation> getGroupRepresentations() { - return groupsResource.groups(); - } - - public GroupResource getGroupResource(String id) { - return groupsResource.group(id); - } - - public List<GroupRepresentation> getSubGroups(GroupResource parentResource) { - return parentResource.getSubGroups(0, parentResource.toRepresentation().getSubGroupCount().intValue(), false); + return groupMapper.fromGroupRepresentations(apiFacade.getAllGroups()).stream(); } } diff --git a/src/test/java/de/ozgcloud/admin/keycloak/GroupMapperTest.java b/src/test/java/de/ozgcloud/admin/keycloak/GroupMapperTest.java index e5389ed1..232c6a03 100644 --- a/src/test/java/de/ozgcloud/admin/keycloak/GroupMapperTest.java +++ b/src/test/java/de/ozgcloud/admin/keycloak/GroupMapperTest.java @@ -62,7 +62,8 @@ class GroupMapperTest { @BeforeEach void init() { - doReturn(GroupRepresentationTestFactory.ORGANISATIONS_EINHEIT_ID).when(mapper).getOrganisationsEinheitId(groupRepresentation.getAttributes()); + doReturn(GroupRepresentationTestFactory.ORGANISATIONS_EINHEIT_ID).when(mapper) + .getOrganisationsEinheitId(groupRepresentation.getAttributes()); } @Test @@ -87,10 +88,11 @@ class GroupMapperTest { } @Test - void shouldSetSubGroupsToEmptyList() { + void shouldSetSubGroups() { var group = callMapper(); - assertThat(group.getSubGroups()).isEmpty(); + assertThat(group.getSubGroups()).usingRecursiveFieldByFieldElementComparator() + .containsExactlyElementsOf(GroupTestFactory.create().getSubGroups()); } private Group callMapper() { @@ -101,8 +103,8 @@ class GroupMapperTest { @Nested class TestFromGroupRepresentations { - private GroupRepresentation groupRepresentation = GroupRepresentationTestFactory.create(); - private Group group = GroupTestFactory.createWithEmptySubGroups(); + private final GroupRepresentation groupRepresentation = GroupRepresentationTestFactory.create(); + private final Group group = GroupTestFactory.createWithEmptySubGroups(); @BeforeEach void init() { diff --git a/src/test/java/de/ozgcloud/admin/keycloak/GroupRepresentationTestFactory.java b/src/test/java/de/ozgcloud/admin/keycloak/GroupRepresentationTestFactory.java index 9d9df97a..bfa6ba52 100644 --- a/src/test/java/de/ozgcloud/admin/keycloak/GroupRepresentationTestFactory.java +++ b/src/test/java/de/ozgcloud/admin/keycloak/GroupRepresentationTestFactory.java @@ -1,5 +1,6 @@ package de.ozgcloud.admin.keycloak; +import java.util.List; import java.util.UUID; import org.keycloak.representations.idm.GroupRepresentation; @@ -11,10 +12,25 @@ class GroupRepresentationTestFactory { public static final String NAME = LoremIpsum.getInstance().getName(); public static final String ORGANISATIONS_EINHEIT_ID = UUID.randomUUID().toString(); + public static final String SUB_GROUP_NAME = LoremIpsum.getInstance().getName(); + public static final String SUB_GROUP_ORGANISATIONS_EINHEIT_ID = UUID.randomUUID().toString(); + public static GroupRepresentation create() { + var groupRepresentation = create(NAME, ORGANISATIONS_EINHEIT_ID); + groupRepresentation.setSubGroups(List.of(create(SUB_GROUP_NAME, SUB_GROUP_ORGANISATIONS_EINHEIT_ID))); + return groupRepresentation; + } + + public static GroupRepresentation create(String name) { + var groupRepresentation = create(); + groupRepresentation.setName(name); + return groupRepresentation; + } + + private static GroupRepresentation create(String name, String organisationsEinheitId) { var groupRepresentation = new GroupRepresentation() - .singleAttribute(GroupMapper.ORGANIZATIONS_EINHEIT_ID_ATTRIBUTE, ORGANISATIONS_EINHEIT_ID); - groupRepresentation.setName(NAME); + .singleAttribute(GroupMapper.ORGANIZATIONS_EINHEIT_ID_ATTRIBUTE, organisationsEinheitId); + groupRepresentation.setName(name); return groupRepresentation; } } diff --git a/src/test/java/de/ozgcloud/admin/keycloak/GroupTestFactory.java b/src/test/java/de/ozgcloud/admin/keycloak/GroupTestFactory.java index bd8413d0..4cef5a38 100644 --- a/src/test/java/de/ozgcloud/admin/keycloak/GroupTestFactory.java +++ b/src/test/java/de/ozgcloud/admin/keycloak/GroupTestFactory.java @@ -1,17 +1,14 @@ package de.ozgcloud.admin.keycloak; import java.util.List; -import java.util.UUID; - -import com.thedeanda.lorem.LoremIpsum; class GroupTestFactory { public static final String NAME = GroupRepresentationTestFactory.NAME; public static final String ORGANISATIONS_EINHEIT_ID = GroupRepresentationTestFactory.ORGANISATIONS_EINHEIT_ID; - public static final String SUB_GROUP_NAME = LoremIpsum.getInstance().getName(); - public static final String SUB_GROUP_ORGANISATIONS_EINHEIT_ID = UUID.randomUUID().toString(); + public static final String SUB_GROUP_NAME = GroupRepresentationTestFactory.SUB_GROUP_NAME; + public static final String SUB_GROUP_ORGANISATIONS_EINHEIT_ID = GroupRepresentationTestFactory.SUB_GROUP_ORGANISATIONS_EINHEIT_ID; public static Group create() { return createBuilder().build(); diff --git a/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiFacadeITCase.java b/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiFacadeITCase.java new file mode 100644 index 00000000..31c51a7c --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiFacadeITCase.java @@ -0,0 +1,103 @@ +package de.ozgcloud.admin.keycloak; + +import static de.ozgcloud.admin.keycloak.GroupMapper.*; +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.groups.Tuple.tuple; + +import java.util.List; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.keycloak.representations.idm.GroupRepresentation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; + +import de.ozgcloud.admin.common.KeycloakInitializer; +import de.ozgcloud.common.test.ITCase; + +@ITCase +@ContextConfiguration(initializers = KeycloakInitializer.class) +class KeycloakApiFacadeITCase { + + @Autowired + private KeycloakApiFacade apiFacade; + + @Nested + class TestGetAllGroups { + + @Test + void shouldReturnAllTopLevelGroups() { + var groups = apiFacade.getAllGroups(); + + assertThat(groups).hasSize(3).extracting(GroupRepresentation::getName) + .containsExactlyInAnyOrder("GroupWithChild", "GroupWithoutOid", "GroupWithoutOidWithChild"); + } + + @Test + void shouldTopLevelGroupsHaveAttributes() { + var groups = apiFacade.getAllGroups(); + + assertThat(groups).extracting(GroupRepresentation::getName, this::getOrganisationsEinheitId) + .containsExactlyInAnyOrder(tuple("GroupWithChild", "GroupWithChild-oid"), tuple("GroupWithoutOid", null), + tuple("GroupWithoutOidWithChild", null)); + } + + @Test + void shouldGroupWithChildHaveLevel1Children() { + var groups = apiFacade.getAllGroups(); + var groupWithChild = getTopLevelGroup(groups, "GroupWithChild"); + + assertThat(groupWithChild.getSubGroups()).hasSize(2).extracting(GroupRepresentation::getName) + .containsExactlyInAnyOrder("ChildLevel1WithChild", "ChildLevel1WithoutOid"); + } + + @Test + void shouldLevel1ChildrenOfGroupWithChildHaveAttributes() { + var groups = apiFacade.getAllGroups(); + var groupWithChild = getTopLevelGroup(groups, "GroupWithChild"); + + assertThat(groupWithChild.getSubGroups()).extracting(GroupRepresentation::getName, this::getOrganisationsEinheitId) + .containsExactlyInAnyOrder(tuple("ChildLevel1WithChild", "ChildLevel1WithChild-oid"), tuple("ChildLevel1WithoutOid", null)); + } + + @Test + void shouldLevel1ChildHaveLevel2Children() { + var groups = apiFacade.getAllGroups(); + var level1Children = getTopLevelGroup(groups, "GroupWithChild").getSubGroups(); + var level1Child = getTopLevelGroup(level1Children, "ChildLevel1WithChild"); + + assertThat(level1Child.getSubGroups()).hasSize(1).extracting(GroupRepresentation::getName).contains("ChildLevel2"); + } + + @Test + void shouldGroupWithoutOidHaveLevel1Children() { + var groups = apiFacade.getAllGroups(); + var groupWithoutOid = getTopLevelGroup(groups, "GroupWithoutOidWithChild"); + + assertThat(groupWithoutOid.getSubGroups()).hasSize(1).extracting(GroupRepresentation::getName) + .containsExactlyInAnyOrder("ChildLevel1"); + } + + @Test + void shouldLevel1ChildrenOfGroupWithoutOidHaveAttributes() { + var groups = apiFacade.getAllGroups(); + var groupWithoutOid = getTopLevelGroup(groups, "GroupWithoutOidWithChild"); + + assertThat(groupWithoutOid.getSubGroups()).extracting(GroupRepresentation::getName, this::getOrganisationsEinheitId) + .containsExactlyInAnyOrder(tuple("ChildLevel1", "ChildLevel1-oid")); + } + + private GroupRepresentation getTopLevelGroup(List<GroupRepresentation> groups, String groupName) { + var foundGroup = groups.stream().filter(group -> group.getName().equals(groupName)).findFirst(); + assertThat(foundGroup).isPresent(); + return foundGroup.get(); + } + + private String getOrganisationsEinheitId(GroupRepresentation group) { + var attributes = group.getAttributes(); + return attributes.containsKey(ORGANIZATIONS_EINHEIT_ID_ATTRIBUTE) ? + attributes.get(ORGANIZATIONS_EINHEIT_ID_ATTRIBUTE).getFirst() : + null; + } + } +} diff --git a/src/test/java/de/ozgcloud/admin/keycloak/KeycloakRemoteServiceITCase.java b/src/test/java/de/ozgcloud/admin/keycloak/KeycloakRemoteServiceITCase.java deleted file mode 100644 index bb72d655..00000000 --- a/src/test/java/de/ozgcloud/admin/keycloak/KeycloakRemoteServiceITCase.java +++ /dev/null @@ -1,93 +0,0 @@ -package de.ozgcloud.admin.keycloak; - -import static org.assertj.core.api.Assertions.*; - -import java.util.List; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.keycloak.representations.idm.GroupRepresentation; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; - -import de.ozgcloud.admin.common.KeycloakInitializer; -import de.ozgcloud.common.test.ITCase; - -@ITCase -@ContextConfiguration(initializers = KeycloakInitializer.class) -class KeycloakRemoteServiceITCase { - - @Autowired - private KeycloakRemoteService service; - - @Nested - class TestGetGroupRepresentations { - - @Test - void shouldReturnGroupRepresentations() { - var result = service.getGroupRepresentations(); - - assertThat(result).hasSize(3); - } - - @Test - void shouldNotHaveAttributes() { - var result = service.getGroupRepresentations(); - - assertThat(result.getFirst()).extracting(GroupRepresentation::getName, GroupRepresentation::getAttributes) - .containsExactly("GroupWithChild", null); - } - - @Test - void shouldHaveSubGroupCount() { - var result = service.getGroupRepresentations(); - - assertThat(result.getFirst()).extracting(GroupRepresentation::getName, GroupRepresentation::getSubGroupCount) - .containsExactly("GroupWithChild", 2L); - } - - @Test - void shouldNotHaveSubGroups() { - var result = service.getGroupRepresentations(); - - assertThat(result.getFirst()).extracting(GroupRepresentation::getName, GroupRepresentation::getSubGroups) - .containsExactly("GroupWithChild", List.of()); - } - } - - @Nested - class TestGetGroupResource { - - @Test - void shouldHaveAttributes() { - var result = service.getGroupResource("GroupWithChild-id").toRepresentation(); - - assertThat(result.getAttributes().get("organisationseinheitId").getFirst()).isEqualTo("GroupWithChild-oid"); - } - - @Test - void shouldHaveSubGroupCount() { - var result = service.getGroupResource("GroupWithChild-id").toRepresentation(); - - assertThat(result.getSubGroupCount()).isEqualTo(2); - } - - @Test - void shouldNotHaveSubGroups() { - var result = service.getGroupResource("GroupWithChild-id").toRepresentation(); - - assertThat(result.getSubGroups()).isEmpty(); - } - } - - @Nested - class TestGetSubGroups { - - @Test - void shouldReturnSubGroups() { - var result = service.getSubGroups(service.getGroupResource("GroupWithChild-id")); - - assertThat(result).hasSize(2); - } - } -} diff --git a/src/test/java/de/ozgcloud/admin/keycloak/KeycloakRemoteServiceTest.java b/src/test/java/de/ozgcloud/admin/keycloak/KeycloakRemoteServiceTest.java new file mode 100644 index 00000000..bbc2a6c1 --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/keycloak/KeycloakRemoteServiceTest.java @@ -0,0 +1,63 @@ +package de.ozgcloud.admin.keycloak; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.keycloak.representations.idm.GroupRepresentation; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; + +class KeycloakRemoteServiceTest { + + @Mock + private KeycloakApiFacade apiFacade; + @Mock + private GroupMapper mapper; + @Spy + @InjectMocks + private KeycloakRemoteService service; + + @Nested + class TestGetGroups { + + private final List<GroupRepresentation> groupRepresentations = List.of( + GroupRepresentationTestFactory.create("A"), GroupRepresentationTestFactory.create("B") + ); + private final List<Group> mappedGroups = List.of( + GroupTestFactory.createBuilder().name("A").build(), GroupTestFactory.createBuilder().name("B").build() + ); + + @BeforeEach + void init() { + when(apiFacade.getAllGroups()).thenReturn(groupRepresentations); + when(mapper.fromGroupRepresentations(groupRepresentations)).thenReturn(mappedGroups); + } + + @Test + void shouldGetAllGroups() { + service.getGroups(); + + verify(apiFacade).getAllGroups(); + } + + @Test + void shouldMapGroups() { + service.getGroups(); + + verify(mapper).fromGroupRepresentations(groupRepresentations); + } + + @Test + void shouldReturnMappedGroups() { + var groups = service.getGroups(); + + assertThat(groups).containsExactlyElementsOf(mappedGroups); + } + } +} -- GitLab