diff --git a/pom.xml b/pom.xml index ccb8de3bfd4dcbb34e522ade41b81e7bbe5125cd..915ccea3e9f6dfbca7d73078bb0a325dc625d97e 100644 --- a/pom.xml +++ b/pom.xml @@ -89,10 +89,6 @@ <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> - <dependency> - <groupId>net.devh</groupId> - <artifactId>grpc-client-spring-boot-starter</artifactId> - </dependency> <!-- ShedLock --> <dependency> diff --git a/src/main/helm/templates/deployment.yaml b/src/main/helm/templates/deployment.yaml index 7962926bad3084bd5537279b6c4fdc166e046db6..cf4720b88a773d2153428371fc018d1d8638e72d 100644 --- a/src/main/helm/templates/deployment.yaml +++ b/src/main/helm/templates/deployment.yaml @@ -104,6 +104,10 @@ spec: - name: ozgcloud_administration_sync_organisationseinheiten_cron value: {{ .Values.ozgcloud.sync.organisationseinheiten.cron | quote }} {{- end }} + - name: grpc_client_zufi-manager_address + value: {{ .Values.zufiManager.address }} + - name: grpc_client_zufi-manager_negotiationType + value: {{ (.Values.zufiManager).grpcClientNegotiationType | default "TLS" }} envFrom: {{- if (.Values.database).useExternal }} - secretRef: diff --git a/src/main/helm/templates/network_policy.yaml b/src/main/helm/templates/network_policy.yaml index 1c8c51e0be101348d8e999960f0e0e98124d85c8..ba64d59e4918597d31d38d4598d5309a9287e3ff 100644 --- a/src/main/helm/templates/network_policy.yaml +++ b/src/main/helm/templates/network_policy.yaml @@ -28,6 +28,16 @@ spec: {{ toYaml . | indent 2 }} {{- end }} egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: "zufi" + podSelector: + matchLabels: + component: "zufi-server" + ports: + - port: 9090 + protocol: TCP - to: - podSelector: matchLabels: diff --git a/src/main/helm/templates/service_account.yaml b/src/main/helm/templates/service_account.yaml index 0e13e6bcabf1933117c29487473453b63265922a..3bac8e223d1fd108b386d1f06ed4e9fb2284a67c 100644 --- a/src/main/helm/templates/service_account.yaml +++ b/src/main/helm/templates/service_account.yaml @@ -1,5 +1,5 @@ # -# Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den +# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den # Ministerpräsidenten des Landes Schleswig-Holstein # Staatskanzlei # Abteilung Digitalisierung und zentrales IT-Management der Landesregierung diff --git a/src/main/helm/values.yaml b/src/main/helm/values.yaml index 65c0a551f00d862d2432233e89c37a7051d3ee1d..6d9f58a3cdce518e86f0bca90594972ee87f2fd9 100644 --- a/src/main/helm/values.yaml +++ b/src/main/helm/values.yaml @@ -33,6 +33,9 @@ ozgcloud: api: user: administrationApiUser +zufiManager: + address: zufi-server.zufi:9090 + image: repo: docker.ozg-sh.de name: administration diff --git a/src/main/java/de/ozgcloud/admin/keycloak/AddGroupData.java b/src/main/java/de/ozgcloud/admin/keycloak/AddGroupData.java index 2845e10babe0bed5eaf22a1b5d5bb043d0532a6b..bfef1afa5a70b06618372fcb6e7c1eb035f3e872 100644 --- a/src/main/java/de/ozgcloud/admin/keycloak/AddGroupData.java +++ b/src/main/java/de/ozgcloud/admin/keycloak/AddGroupData.java @@ -9,7 +9,6 @@ import lombok.ToString; @ToString public class AddGroupData { - private String parentId; private String name; private String organisationsEinheitId; } diff --git a/src/main/java/de/ozgcloud/admin/keycloak/Group.java b/src/main/java/de/ozgcloud/admin/keycloak/Group.java index 0bf5a9e19b39f24637d9cdd8ef037f6110247609..005a7ce23d0709ade874e9fad35e7be17906ceee 100644 --- a/src/main/java/de/ozgcloud/admin/keycloak/Group.java +++ b/src/main/java/de/ozgcloud/admin/keycloak/Group.java @@ -7,7 +7,7 @@ import lombok.Getter; import lombok.Singular; @Getter -@Builder +@Builder(toBuilder = true) public class Group { private String id; diff --git a/src/main/java/de/ozgcloud/admin/keycloak/GroupMapper.java b/src/main/java/de/ozgcloud/admin/keycloak/GroupMapper.java index 4af927fa8481a073970825559f759b9234b72c90..a18a572526e3e4da1ca1bbc66433fe17266acedc 100644 --- a/src/main/java/de/ozgcloud/admin/keycloak/GroupMapper.java +++ b/src/main/java/de/ozgcloud/admin/keycloak/GroupMapper.java @@ -23,15 +23,6 @@ abstract class GroupMapper { public abstract List<Group> fromGroupRepresentations(List<GroupRepresentation> groupRepresentations); - @AfterMapping - protected void deleteGroupsWithoutOrganisationsEinheitId(@MappingTarget List<Group> groups) { - groups.removeIf(this::isMissingOrganisationsEinheitId); - } - - protected boolean isMissingOrganisationsEinheitId(Group group) { - return StringUtils.isBlank(group.getOrganisationsEinheitId()); - } - @Mapping(target = "organisationsEinheitId", source = "attributes") public abstract Group fromGroupRepresentation(GroupRepresentation groupRepresentation); @@ -44,15 +35,24 @@ abstract class GroupMapper { return null; } if (values.size() > 1 && values.stream().distinct().count() > 1) { - LOG.warn("Group contains multiple values for {}. The first one is taken.", keycloakApiProperties.getOrganisationsEinheitIdKey()); + throw new GroupRepresentationMappingException("Group contains multiple values for organisationsEinheitId: %s".formatted(values)); } return values.getFirst(); } - @Mapping(target = "attributes", expression = "java(getAttributes(groupToAdd))") + @AfterMapping + protected void deleteGroupsWithoutOrganisationsEinheitId(@MappingTarget List<Group> groups) { + groups.removeIf(this::isMissingOrganisationsEinheitId); + } + + protected boolean isMissingOrganisationsEinheitId(Group group) { + return StringUtils.isBlank(group.getOrganisationsEinheitId()); + } + + @Mapping(target = "attributes", expression = "java(buildGroupAttributes(groupToAdd))") public abstract GroupRepresentation toGroupRepresentation(AddGroupData groupToAdd); - Map<String, List<String>> getAttributes(AddGroupData groupToAdd) { + Map<String, List<String>> buildGroupAttributes(AddGroupData groupToAdd) { var organisationsEinheitId = groupToAdd.getOrganisationsEinheitId(); return StringUtils.isNotBlank(organisationsEinheitId) ? Map.of(keycloakApiProperties.getOrganisationsEinheitIdKey(), List.of(organisationsEinheitId)) : diff --git a/src/main/java/de/ozgcloud/admin/keycloak/GroupRepresentationMappingException.java b/src/main/java/de/ozgcloud/admin/keycloak/GroupRepresentationMappingException.java new file mode 100644 index 0000000000000000000000000000000000000000..c07743ddf481a64e3d296309fbaa556fbab31f14 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/keycloak/GroupRepresentationMappingException.java @@ -0,0 +1,8 @@ +package de.ozgcloud.admin.keycloak; + +public class GroupRepresentationMappingException extends RuntimeException { + + public GroupRepresentationMappingException(String message) { + super(message); + } +} diff --git a/src/main/java/de/ozgcloud/admin/keycloak/KeycloakApiService.java b/src/main/java/de/ozgcloud/admin/keycloak/KeycloakApiService.java index 057d0ab9622b949f7214008b131e956d76b70629..888f98c8b9acbecc3eaecf16cfbacd9dca176587 100644 --- a/src/main/java/de/ozgcloud/admin/keycloak/KeycloakApiService.java +++ b/src/main/java/de/ozgcloud/admin/keycloak/KeycloakApiService.java @@ -21,11 +21,11 @@ class KeycloakApiService { public String addGroup(GroupRepresentation groupRepresentation) { try (var response = groupsResource.add(groupRepresentation)) { - return getAddedResourceIdFromResponse(response); + return getCreatedResourceIdFromResponse(response); } } - String getAddedResourceIdFromResponse(Response response) { + String getCreatedResourceIdFromResponse(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/OrganisationsEinheitRepository.java b/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitRepository.java index 3f8e3c38963297d397cb05542479f4a8a1d99984..f32cbadfe751ab671151de99142112f22ff02671 100644 --- a/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitRepository.java +++ b/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitRepository.java @@ -25,6 +25,6 @@ interface OrganisationsEinheitRepository extends MongoRepository<OrganisationsEi @Update("{'$set': {'syncResult': 'DELETED'}}") void setUnsyncedAsDeleted(long lastSyncTimestamp); - @Query("{'syncResult': { $eq: null }}") + @Query("{'syncResult': null }") Stream<OrganisationsEinheit> findAllWithoutSyncResult(); } diff --git a/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitSynchronizationException.java b/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitSynchronizationException.java new file mode 100644 index 0000000000000000000000000000000000000000..42944f0a41b1d5eb7cd1a724ab8e1792bc7da7de --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitSynchronizationException.java @@ -0,0 +1,12 @@ +package de.ozgcloud.admin.organisationseinheit; + +public class OrganisationsEinheitSynchronizationException extends RuntimeException { + + public OrganisationsEinheitSynchronizationException(String message) { + super(message); + } + + public OrganisationsEinheitSynchronizationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncService.java b/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncService.java index 98139f6fa92396ba563328d59275744fadc6a802..a1c911a20d16d2cb4fdbbd707417180521c0bd9f 100644 --- a/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncService.java +++ b/src/main/java/de/ozgcloud/admin/organisationseinheit/SyncService.java @@ -23,14 +23,14 @@ class SyncService { private final OrganisationsEinheitMapper organisationsEinheitMapper; public void syncOrganisationsEinheitenFromKeycloak(long syncTimestamp) { - keycloakRemoteService.getGroupsWithOrganisationsEinheitId().forEach(group -> syncGroups(group, null, syncTimestamp)); + keycloakRemoteService.getGroupsWithOrganisationsEinheitId().forEach(group -> syncGroupsWithSubGroups(group, null, syncTimestamp)); repository.setUnsyncedAsDeleted(syncTimestamp); } - void syncGroups(Group group, OrganisationsEinheit parent, long syncTimestamp) { + void syncGroupsWithSubGroups(Group group, OrganisationsEinheit parent, long syncTimestamp) { var synced = syncGroup(group, parent, syncTimestamp); var saved = saveSyncedOrganisationsEinheit(synced); - group.getSubGroups().forEach(subGroup -> syncGroups(subGroup, saved, syncTimestamp)); + group.getSubGroups().forEach(subGroup -> syncGroupsWithSubGroups(subGroup, saved, syncTimestamp)); } OrganisationsEinheit syncGroup(Group group, OrganisationsEinheit parent, long syncTimestamp) { @@ -89,28 +89,38 @@ class SyncService { } public void syncAddedOrganisationsEinheiten(long syncTimestamp) { - repository.findAllWithoutSyncResult().forEach(organisationsEinheit -> syncAddedOrganisationsEinheit(syncTimestamp, organisationsEinheit)); + repository.findAllWithoutSyncResult().forEach( + organisationsEinheit -> syncAddedOrganisationsEinheit(organisationsEinheit, syncTimestamp)); } - void syncAddedOrganisationsEinheit(long syncTimestamp, OrganisationsEinheit organisationsEinheit) { - var addGroupData = organisationsEinheitMapper.toAddGroupData(organisationsEinheit); - var keycloakId = addGroupInKeycloak(addGroupData); - if (keycloakId.isPresent()) { - var updatedOrganisationsEinheit = organisationsEinheit.toBuilder() - .keycloakId(keycloakId.get()) - .syncResult(SyncResult.OK) - .lastSyncTimestamp(syncTimestamp) - .build(); - repository.save(updatedOrganisationsEinheit); + void syncAddedOrganisationsEinheit(OrganisationsEinheit organisationsEinheit, long syncTimestamp) { + addAsGroupInKeycloak(organisationsEinheit).ifPresent( + addedGroupId -> updateAfterSuccessfulGroupCreation(organisationsEinheit, addedGroupId, syncTimestamp)); + } + + Optional<String> addAsGroupInKeycloak(OrganisationsEinheit organisationsEinheit) { + if (organisationsEinheit.getParentId() != null) { + throw new OrganisationsEinheitSynchronizationException( + "Organisationseinheit %s has parent".formatted(organisationsEinheit.getOrganisationsEinheitId())); } + var addGroupData = organisationsEinheitMapper.toAddGroupData(organisationsEinheit); + return addGroupInKeycloak(addGroupData); + } + + void updateAfterSuccessfulGroupCreation(OrganisationsEinheit organisationsEinheit, String keycloakId, long syncTimestamp) { + var updatedOrganisationsEinheit = organisationsEinheit.toBuilder() + .keycloakId(keycloakId) + .syncResult(SyncResult.OK) + .lastSyncTimestamp(syncTimestamp) + .build(); + repository.save(updatedOrganisationsEinheit); } Optional<String> addGroupInKeycloak(AddGroupData addGroupData) { try { return Optional.of(keycloakRemoteService.addGroup(addGroupData)); } catch (ResourceCreationException e) { - LOG.error("Error adding group %s in Keycloak".formatted(addGroupData), e); - return Optional.empty(); + throw new OrganisationsEinheitSynchronizationException("Error creating group %s in Keycloak".formatted(addGroupData), e); } } } diff --git a/src/main/resources/application-remotekc.yaml b/src/main/resources/application-remotekc.yaml index a4cd02efb0e33fb73ccaf95e1478973185b12261..e64923222403088b888536380a901d46bb3fc442 100644 --- a/src/main/resources/application-remotekc.yaml +++ b/src/main/resources/application-remotekc.yaml @@ -6,4 +6,3 @@ ozgcloud: keycloak: api: user: administrationApiUser - password: 4hVe-qWRM4ZwMdQ diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index af6a93ebdb869b45fc3c0be85fa38dbe821a0683..ddb50199effa7b74f3825194c43a5780fc4706e7 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -88,5 +88,5 @@ ozgcloud: grpc: client: zufi-manager: - address: static://127.0.0.1:9190 + address: static://127.0.0.1:9090 negotiationType: TLS \ No newline at end of file diff --git a/src/test/helm/network_policy_test.yaml b/src/test/helm/network_policy_test.yaml index 427fafd41265488ffe1002a9746373156b7a9718..6db0ccf63232ae8e01f74badd5c11f29245c5f70 100644 --- a/src/test/helm/network_policy_test.yaml +++ b/src/test/helm/network_policy_test.yaml @@ -83,10 +83,20 @@ tests: - protocol: TCP port: 8081 egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: "zufi" + podSelector: + matchLabels: + component: "zufi-server" + ports: + - port: 9090 + protocol: TCP - to: - podSelector: matchLabels: - component: ozgcloud-mongodb + component: "ozgcloud-mongodb" ports: - port: 27017 protocol: TCP diff --git a/src/test/java/de/ozgcloud/admin/keycloak/AddGroupDataTestFactory.java b/src/test/java/de/ozgcloud/admin/keycloak/AddGroupDataTestFactory.java index b53d872617b553a2c708e6b653905231889af445..cea95944ffe15491d5a60ead097db8a8a29c4989 100644 --- a/src/test/java/de/ozgcloud/admin/keycloak/AddGroupDataTestFactory.java +++ b/src/test/java/de/ozgcloud/admin/keycloak/AddGroupDataTestFactory.java @@ -2,7 +2,6 @@ package de.ozgcloud.admin.keycloak; public class AddGroupDataTestFactory { - public static final String PARENT_ID = GroupRepresentationTestFactory.ID; public static final String NAME = GroupRepresentationTestFactory.NAME; public static final String ORGANISATIONS_EINHEIT_ID = GroupRepresentationTestFactory.ORGANISATIONS_EINHEIT_ID; @@ -12,7 +11,6 @@ public class AddGroupDataTestFactory { public static AddGroupData.AddGroupDataBuilder createBuilder() { return new AddGroupData.AddGroupDataBuilder() - .parentId(PARENT_ID) .name(NAME) .organisationsEinheitId(ORGANISATIONS_EINHEIT_ID); } diff --git a/src/test/java/de/ozgcloud/admin/keycloak/GroupMapperTest.java b/src/test/java/de/ozgcloud/admin/keycloak/GroupMapperTest.java index adaddc58f2a88a674a6e66e2fba85c698ba81570..82b47bca310517e47c6ac5b812c4abc9aef66db9 100644 --- a/src/test/java/de/ozgcloud/admin/keycloak/GroupMapperTest.java +++ b/src/test/java/de/ozgcloud/admin/keycloak/GroupMapperTest.java @@ -4,6 +4,7 @@ import static de.ozgcloud.admin.keycloak.GroupRepresentationTestFactory.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.UUID; @@ -13,8 +14,11 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; import org.keycloak.representations.idm.GroupRepresentation; import org.mapstruct.factory.Mappers; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; @@ -28,42 +32,64 @@ class GroupMapperTest { private GroupMapper mapper = Mappers.getMapper(GroupMapper.class); @Nested - class TestGetOrganisationsEinheitId { + class TestFromGroupRepresentations { + + private final GroupRepresentation groupRepresentation = GroupRepresentationTestFactory.create(); + + @Captor + private ArgumentCaptor<List<Group>> groupsCaptor; @Test - void shouldReturnNullIfAttributesAreNull() { - var result = mapper.getOrganisationsEinheitId(null); + void shouldMapFromGroupRepresentation() { + givenMappedGroupWithOrganisationsEinheitId(); - assertThat(result).isNull(); + callMapper(); + + verify(mapper).fromGroupRepresentation(groupRepresentation); } @Test - void shouldReturnNullIfAttributeIsAbsent() { - givenOrganisationsEinheitIdProperty(); + void shouldReturnListWithMappedGroupRepresentation() { + var group = givenMappedGroupWithOrganisationsEinheitId(); - var result = mapper.getOrganisationsEinheitId(Map.of("dummy-attribute", List.of("123"))); + var groups = callMapper(); - assertThat(result).isNull(); + assertThat(groups).containsExactly(group); } - @Test - void shouldReturnOrganisationsEinheitId() { - givenOrganisationsEinheitIdProperty(); - var value = GroupRepresentationTestFactory.ORGANISATIONS_EINHEIT_ID; - var result = mapper.getOrganisationsEinheitId(Map.of(ORGANIZATIONS_EINHEIT_ID_ATTRIBUTE, List.of(value))); + @ParameterizedTest + @NullAndEmptySource + void shouldReturnEmptyList(String organisationsEinheitId) { + givenMappedGroupWithOrganisationsEinheitId(organisationsEinheitId); - assertThat(result).isEqualTo(value); + var groups = callMapper(); + + assertThat(groups).isEmpty(); } @Test - void shouldReturnFirstValueIfMultipleAreAvailable() { - givenOrganisationsEinheitIdProperty(); - var value = GroupRepresentationTestFactory.ORGANISATIONS_EINHEIT_ID; - var value2 = UUID.randomUUID().toString(); + void shouldDeleteGroupsWithoutOrganisationsEinheitId() { + var group = givenMappedGroupWithOrganisationsEinheitId(); - var result = mapper.getOrganisationsEinheitId(Map.of(ORGANIZATIONS_EINHEIT_ID_ATTRIBUTE, List.of(value, value2))); + callMapper(); - assertThat(result).isEqualTo(value); + verify(mapper).deleteGroupsWithoutOrganisationsEinheitId(groupsCaptor.capture()); + assertThat(groupsCaptor.getValue()).containsExactly(group); + } + + private Group givenMappedGroupWithOrganisationsEinheitId() { + var group = GroupTestFactory.create(); + doReturn(group).when(mapper).fromGroupRepresentation(groupRepresentation); + return group; + } + + private void givenMappedGroupWithOrganisationsEinheitId(String id) { + var group = GroupTestFactory.createBuilder().organisationsEinheitId(id).build(); + doReturn(group).when(mapper).fromGroupRepresentation(groupRepresentation); + } + + private List<Group> callMapper() { + return mapper.fromGroupRepresentations(List.of(groupRepresentation)); } } @@ -122,51 +148,101 @@ class GroupMapperTest { } @Nested - class TestFromGroupRepresentations { + class TestGetOrganisationsEinheitId { - private final GroupRepresentation groupRepresentation = GroupRepresentationTestFactory.create(); + @Test + void shouldReturnNullIfAttributesAreNull() { + var result = mapper.getOrganisationsEinheitId(null); + + assertThat(result).isNull(); + } @Test - void shouldMapFromGroupRepresentation() { - givenMappedGroupWithOrganisationsEinheitId(); + void shouldReturnNullIfAttributeIsAbsent() { + givenOrganisationsEinheitIdProperty(); - callMapper(); + var result = mapper.getOrganisationsEinheitId(Map.of("dummy-attribute", List.of("123"))); - verify(mapper).fromGroupRepresentation(groupRepresentation); + assertThat(result).isNull(); } @Test - void shouldReturnListWithMappedGroupRepresentation() { - var group = givenMappedGroupWithOrganisationsEinheitId(); + void shouldReturnOrganisationsEinheitId() { + givenOrganisationsEinheitIdProperty(); + var value = GroupRepresentationTestFactory.ORGANISATIONS_EINHEIT_ID; + var result = mapper.getOrganisationsEinheitId(Map.of(ORGANIZATIONS_EINHEIT_ID_ATTRIBUTE, List.of(value))); - var groups = callMapper(); + assertThat(result).isEqualTo(value); + } + + @Test + void shouldThrowExceptionIfMultipleValuesAreAvailable() { + givenOrganisationsEinheitIdProperty(); + var value = GroupRepresentationTestFactory.ORGANISATIONS_EINHEIT_ID; + var value2 = UUID.randomUUID().toString(); + + assertThatExceptionOfType(GroupRepresentationMappingException.class) + .isThrownBy(() -> mapper.getOrganisationsEinheitId(Map.of(ORGANIZATIONS_EINHEIT_ID_ATTRIBUTE, List.of(value, value2)))) + .withMessage("Group contains multiple values for organisationsEinheitId: %s", List.of(value, value2)); + } + } + + @Nested + class TestDeleteGroupsWithoutOrganisationsEinheitId { + + private final Group group = GroupTestFactory.create(); + private final List<Group> groups = new ArrayList<>(); + + @BeforeEach + void init() { + groups.add(group); + } + + @Test + void shouldCheckIfGroupHasOrganisationsEinheitId() { + mapper.deleteGroupsWithoutOrganisationsEinheitId(groups); + + verify(mapper).isMissingOrganisationsEinheitId(group); + } + + @Test + void shouldKeepGroupsWithOrganisationsEinheitId() { + doReturn(false).when(mapper).isMissingOrganisationsEinheitId(group); + + mapper.deleteGroupsWithoutOrganisationsEinheitId(groups); assertThat(groups).containsExactly(group); } - @ParameterizedTest - @NullAndEmptySource - void shouldReturnEmptyList(String organisationsEinheitId) { - givenMappedGroupWithOrganisationsEinheitId(organisationsEinheitId); + @Test + void shouldRemoveGroupsWithoutOrganisationsEinheitId() { + doReturn(true).when(mapper).isMissingOrganisationsEinheitId(group); - var groups = callMapper(); + mapper.deleteGroupsWithoutOrganisationsEinheitId(groups); assertThat(groups).isEmpty(); } + } - private Group givenMappedGroupWithOrganisationsEinheitId() { - var group = GroupTestFactory.create(); - doReturn(group).when(mapper).fromGroupRepresentation(groupRepresentation); - return group; - } + @Nested + class TestIsMissingOrganisationsEinheitId { - private void givenMappedGroupWithOrganisationsEinheitId(String id) { - var group = GroupTestFactory.createBuilder().organisationsEinheitId(id).build(); - doReturn(group).when(mapper).fromGroupRepresentation(groupRepresentation); + @ParameterizedTest + @ValueSource(strings = " ") + @NullAndEmptySource + void shouldReturnTrueIfIdIsBlank(String organisationsEinheitId) { + var group = GroupTestFactory.createBuilder().organisationsEinheitId(organisationsEinheitId).build(); + + var isMissing = mapper.isMissingOrganisationsEinheitId(group); + + assertThat(isMissing).isTrue(); } - private List<Group> callMapper() { - return mapper.fromGroupRepresentations(List.of(groupRepresentation)); + @Test + void shouldReturnFalseIfIdIsSet() { + var isMissing = mapper.isMissingOrganisationsEinheitId(GroupTestFactory.create()); + + assertThat(isMissing).isFalse(); } } @@ -177,14 +253,7 @@ class GroupMapperTest { @BeforeEach void init() { - doReturn(ATTRIBUTES).when(mapper).getAttributes(addGroupData); - } - - @Test - void shouldSetParentId() { - var groupRepresentation = callMapper(); - - assertThat(groupRepresentation.getParentId()).isEqualTo(addGroupData.getParentId()); + doReturn(ATTRIBUTES).when(mapper).buildGroupAttributes(addGroupData); } @Test @@ -202,10 +271,10 @@ class GroupMapperTest { } @Test - void shouldGetAttributes() { + void shouldBuildGroupAttributes() { callMapper(); - verify(mapper).getAttributes(addGroupData); + verify(mapper).buildGroupAttributes(addGroupData); } @Test @@ -221,14 +290,14 @@ class GroupMapperTest { } @Nested - class TestGetAttributes { + class TestBuildGroupAttributes { @ParameterizedTest @NullAndEmptySource void shouldReturnEmptyMap(String organisationsEinheitId) { var addGroupData = AddGroupDataTestFactory.createBuilder().organisationsEinheitId(organisationsEinheitId).build(); - var attributes = mapper.getAttributes(addGroupData); + var attributes = mapper.buildGroupAttributes(addGroupData); assertThat(attributes).isEmpty(); } @@ -237,7 +306,7 @@ class GroupMapperTest { void shouldAddOrganisationsEinheitIdToAttributes() { givenOrganisationsEinheitIdProperty(); - var attributes = mapper.getAttributes(AddGroupDataTestFactory.create()); + var attributes = mapper.buildGroupAttributes(AddGroupDataTestFactory.create()); assertThat(attributes).hasSize(1).containsEntry(ORGANIZATIONS_EINHEIT_ID_ATTRIBUTE, List.of(GroupTestFactory.ORGANISATIONS_EINHEIT_ID)); } 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..f2c7dd6c83ee9651cca09c17ad7eda8927ac94eb 100644 --- a/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceITCase.java +++ b/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceITCase.java @@ -146,7 +146,7 @@ class KeycloakApiServiceITCase { } @Test - void shouldNotAddSubgroups() { + void shouldNotAddSubgroupsOfAddedGroup() { var groupToAdd = createUniqueGroupRepresentation("shouldNotAddSubgroups"); var groupId = service.addGroup(groupToAdd); @@ -155,10 +155,12 @@ class KeycloakApiServiceITCase { .asList().isEmpty(); } - private GroupRepresentation createUniqueGroupRepresentation(String suffix) { + 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..ce9ed1256d94d81e5e04e820fe2436bfdc34b168 100644 --- a/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceTest.java +++ b/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceTest.java @@ -3,6 +3,8 @@ 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; @@ -26,16 +28,37 @@ class KeycloakApiServiceTest { @Mock private Response response; + @Nested + class TestGetAllGroups { + + @Test + void shouldCallGroupsResource() { + service.getAllGroups(); + + verify(groupsResource).groups("", 0, Integer.MAX_VALUE, false); + } + + @Test + void shouldReturnGroupRepresentations() { + var groupRepresentation = GroupRepresentationTestFactory.create(); + when(groupsResource.groups("", 0, Integer.MAX_VALUE, false)).thenReturn(List.of(groupRepresentation)); + + var gotGroupRepresentations = service.getAllGroups(); + + assertThat(gotGroupRepresentations).containsExactly(groupRepresentation); + } + } + @Nested class TestAddGroup { private final GroupRepresentation groupRepresentation = GroupRepresentationTestFactory.create(); - private final String RESOURCE_ID = GroupRepresentationTestFactory.create().getId(); + private final String resourceId = GroupRepresentationTestFactory.create().getId(); @BeforeEach void init() { when(groupsResource.add(groupRepresentation)).thenReturn(response); - doReturn(RESOURCE_ID).when(service).getAddedResourceIdFromResponse(response); + doReturn(resourceId).when(service).getCreatedResourceIdFromResponse(response); } @Test @@ -49,14 +72,14 @@ class KeycloakApiServiceTest { void shouldGetAddedResourceIdFromResponse() { callService(); - verify(service).getAddedResourceIdFromResponse(response); + verify(service).getCreatedResourceIdFromResponse(response); } @Test void shouldReturnAddedResourceId() { var id = callService(); - assertThat(id).isEqualTo(RESOURCE_ID); + assertThat(id).isEqualTo(resourceId); } private String callService() { @@ -65,10 +88,10 @@ class KeycloakApiServiceTest { } @Nested - class TestGetAddedResourceIdFromResponse { + class TestGetCreatedResourceIdFromResponse { - private final String RESOURCE_ID = GroupRepresentationTestFactory.create().getId(); - private final String LOCATION = LoremIpsum.getInstance().getUrl(); + private final String resourceId = GroupRepresentationTestFactory.create().getId(); + private final String location = LoremIpsum.getInstance().getUrl(); @Test void shouldExtractResourceIdFromLocationHeader() { @@ -76,7 +99,7 @@ class KeycloakApiServiceTest { callService(); - verify(service).extractResourceIdFromLocationHeader(LOCATION); + verify(service).extractResourceIdFromLocationHeader(location); } @Test @@ -85,7 +108,7 @@ class KeycloakApiServiceTest { var id = callService(); - assertThat(id).isEqualTo(RESOURCE_ID); + assertThat(id).isEqualTo(resourceId); } @Test @@ -98,8 +121,8 @@ class KeycloakApiServiceTest { private void givenResponseCreated() { when(response.getStatus()).thenReturn(201); - when(response.getHeaderString("Location")).thenReturn(LOCATION); - doReturn(RESOURCE_ID).when(service).extractResourceIdFromLocationHeader(LOCATION); + when(response.getHeaderString("Location")).thenReturn(location); + doReturn(resourceId).when(service).extractResourceIdFromLocationHeader(location); } private void givenResponseUnauthorized() { @@ -108,21 +131,21 @@ class KeycloakApiServiceTest { } private String callService() { - return service.getAddedResourceIdFromResponse(response); + return service.getCreatedResourceIdFromResponse(response); } } @Nested class TestExtractResourceIdFromLocationHeader { - private final String RESOURCE_ID = GroupRepresentationTestFactory.create().getId(); - private final String LOCATION = "https://keycloak-url.test/admin/realms/test/groups/" + RESOURCE_ID; + private final String resourceId = GroupRepresentationTestFactory.create().getId(); + private final String location = "https://keycloak-url.test/admin/realms/test/groups/" + resourceId; @Test void shouldReturnId() { - var id = service.extractResourceIdFromLocationHeader(LOCATION); + var id = service.extractResourceIdFromLocationHeader(location); - assertThat(id).isEqualTo(RESOURCE_ID); + assertThat(id).isEqualTo(resourceId); } } } diff --git a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitControllerTest.java b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitControllerTest.java index 06f4ce43a415aaa113d9505bcf686942a9a8be30..fbf00adebbb5aeb5e7b8c5edd6f2a6ce4d76f09f 100644 --- a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitControllerTest.java +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitControllerTest.java @@ -96,10 +96,12 @@ class OrganisationsEinheitControllerTest { class TestGetById { private final OrganisationsEinheit organisationsEinheit = OrganisationsEinheitTestFactory.create(); + private final EntityModel<OrganisationsEinheit> entityModel = EntityModel.of(organisationsEinheit); @BeforeEach void setUp() { when(organisationsEinheitService.getOrganisationsEinheitById(OrganisationsEinheitTestFactory.ID)).thenReturn(organisationsEinheit); + when(assembler.toModel(organisationsEinheit)).thenReturn(entityModel); } @Test @@ -122,6 +124,13 @@ class OrganisationsEinheitControllerTest { doRequest().andExpect(status().isOk()); } + @Test + void shouldReturnEntityModel() { + var response = controller.getById(OrganisationsEinheitTestFactory.ID); + + assertThat(response).isEqualTo(entityModel); + } + @SneakyThrows private ResultActions doRequest() { return mockMvc.perform(get(OrganisationsEinheitController.PATH + "/" + OrganisationsEinheitTestFactory.ID)); @@ -131,11 +140,15 @@ class OrganisationsEinheitControllerTest { @Nested class TestGetChildren { - private final List<OrganisationsEinheit> children = List.of(OrganisationsEinheitTestFactory.create()); + private final OrganisationsEinheit organisationsEinheit = OrganisationsEinheitTestFactory.create(); + private final List<OrganisationsEinheit> children = List.of(organisationsEinheit); + private final CollectionModel<EntityModel<OrganisationsEinheit>> collectionModel = CollectionModel.of( + List.of(EntityModel.of(organisationsEinheit))); @BeforeEach void setUp() { when(organisationsEinheitService.getChildren(OrganisationsEinheitTestFactory.ID)).thenReturn(children); + when(assembler.toChildrenCollectionModel(OrganisationsEinheitTestFactory.ID, children)).thenReturn(collectionModel); } @Test @@ -158,6 +171,13 @@ class OrganisationsEinheitControllerTest { verify(assembler).toChildrenCollectionModel(OrganisationsEinheitTestFactory.ID, children); } + @Test + void shouldReturnCollectionModel() { + var response = controller.getChildren(OrganisationsEinheitTestFactory.ID); + + assertThat(response).isEqualTo(collectionModel); + } + @SneakyThrows private ResultActions doRequest() { return mockMvc.perform(get(OrganisationsEinheitController.PATH + "/" + OrganisationsEinheitTestFactory.ID + "/children")); diff --git a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitMapperTest.java b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitMapperTest.java index 10ee3a2de7fd8c740093d84f0444b90cf7b9d19a..53ec27998c68c86ce3dcdb7b91a1a8ba77fb8c20 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.Objects; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -21,23 +22,23 @@ class OrganisationsEinheitMapperTest { void shouldMap() { var mapped = mapper.fromGrpc(GrpcOrganisationsEinheitTestFactory.create()); - assertThat(mapped).extracting( - OrganisationsEinheit::getName, - OrganisationsEinheit::getOrganisationsEinheitId, - OrganisationsEinheit::getZufiId, - OrganisationsEinheit::getParentId, - OrganisationsEinheit::getSyncResult, - OrganisationsEinheit::getSettings, - OrganisationsEinheit::getChildren - ).containsExactly( - OrganisationsEinheitTestFactory.NAME, - OrganisationsEinheitTestFactory.ORGANISATIONS_EINHEIT_ID, - OrganisationsEinheitTestFactory.ZUFI_ID, - null, - null, - null, - Collections.emptyList() - ); + assertThat(mapped) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getChildren + ).containsExactly( + OrganisationsEinheitTestFactory.NAME, + OrganisationsEinheitTestFactory.ORGANISATIONS_EINHEIT_ID, + OrganisationsEinheitTestFactory.ZUFI_ID, + null, + null, + Collections.emptyList() + ); } } @@ -49,11 +50,9 @@ class OrganisationsEinheitMapperTest { var mapped = mapper.toAddGroupData(OrganisationsEinheitTestFactory.create()); assertThat(mapped).extracting( - AddGroupData::getParentId, AddGroupData::getName, AddGroupData::getOrganisationsEinheitId ).containsExactly( - OrganisationsEinheitTestFactory.PARENT_ID, OrganisationsEinheitTestFactory.NAME, OrganisationsEinheitTestFactory.ORGANISATIONS_EINHEIT_ID ); diff --git a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitModelAssemblerTest.java b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitModelAssemblerTest.java index d22dfcc1cec283ada2166c4402f1fd20fa711b0f..51c34967555f7f69218f28da26d4e48fd1a8701e 100644 --- a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitModelAssemblerTest.java +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitModelAssemblerTest.java @@ -11,6 +11,8 @@ import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Spy; import org.springframework.hateoas.CollectionModel; @@ -82,6 +84,10 @@ class OrganisationsEinheitModelAssemblerTest { private CollectionModel<EntityModel<OrganisationsEinheit>> childrenCollectionModel; @Spy private final HalModelBuilder halModelBuilder = HalModelBuilder.emptyHalModel(); + @Captor + private ArgumentCaptor<LinkRelation> linkRelationArgumentCaptor; + @Captor + private ArgumentCaptor<Collection<EntityModel<OrganisationsEinheit>>> collectionModelContentArgumentCaptor; @BeforeEach void setUp() { @@ -89,8 +95,7 @@ class OrganisationsEinheitModelAssemblerTest { childOrganisationsEinheit = OrganisationsEinheitTestFactory.createBuilder().id(UUID.randomUUID().toString()) .parentId(parentOrganisationsEinheit.getId()).build(); parentOrganisationsEinheit = parentOrganisationsEinheit.toBuilder().child(childOrganisationsEinheit).build(); - childrenCollectionModel = CollectionModel.of( - List.of(EntityModel.of(childOrganisationsEinheit))); + childrenCollectionModel = CollectionModel.of(List.of(EntityModel.of(childOrganisationsEinheit))); doReturn(childrenCollectionModel).when(assembler) .toChildrenCollectionModel(parentOrganisationsEinheit.getId(), parentOrganisationsEinheit.getChildren()); } @@ -106,7 +111,9 @@ class OrganisationsEinheitModelAssemblerTest { void shouldEmbedChildrenModels() { assembler.embedChildOrganisationsEinheiten(parentOrganisationsEinheit, halModelBuilder); - verify(halModelBuilder).embed(any(Collection.class), any(LinkRelation.class)); + verify(halModelBuilder).embed(collectionModelContentArgumentCaptor.capture(), linkRelationArgumentCaptor.capture()); + assertThat(collectionModelContentArgumentCaptor.getValue()).usingRecursiveComparison().isEqualTo(childrenCollectionModel.getContent()); + assertThat(linkRelationArgumentCaptor.getValue().toString()).isEqualTo(REL_CHILD_ORGANISATIONS_EINHEITEN); } @Test diff --git a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitRepositoryITCase.java b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitRepositoryITCase.java index 9267fc3b7ae6a09af7d243983c56371cbe2656ee..d72ba698910c33fbdfff6ac74f2fec5dbefc0fef 100644 --- a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitRepositoryITCase.java +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitRepositoryITCase.java @@ -112,7 +112,7 @@ class OrganisationsEinheitRepositoryITCase { } @Test - void shouldReturnEmptyOnNotFoundSyncResultNull() { + void shouldReturnEmptyOnSyncResultNull() { operations.save(OrganisationsEinheitTestFactory.createBuilder().id(null).syncResult(null).build()); var organisationsEinheit = repository.findSyncedByKeycloakId(OrganisationsEinheitTestFactory.KEYCLOAK_ID); @@ -146,12 +146,12 @@ class OrganisationsEinheitRepositoryITCase { private final OrganisationsEinheit deleted1 = OrganisationsEinheitTestFactory.createBuilder() .id(null) .organisationsEinheitId(DELETED_ORGANISATIONS_EINHEIT_ID_1) - .lastSyncTimestamp(Instant.now().minusSeconds(300).toEpochMilli()) + .lastSyncTimestamp(OrganisationsEinheitTestFactory.LAST_SYNC_UPDATE - 1) .build(); private final OrganisationsEinheit deleted2 = OrganisationsEinheitTestFactory.createBuilder() .id(null) .organisationsEinheitId(DELETED_ORGANISATIONS_EINHEIT_ID_2) - .lastSyncTimestamp(Instant.now().minusSeconds(30000).toEpochMilli()) + .lastSyncTimestamp(OrganisationsEinheitTestFactory.LAST_SYNC_UPDATE - 2) .build(); private final OrganisationsEinheit synced1 = OrganisationsEinheitTestFactory.createBuilder() .id(null) @@ -236,7 +236,8 @@ class OrganisationsEinheitRepositoryITCase { void shouldFindAllWithoutSyncResult() { var allWithoutSyncResult = repository.findAllWithoutSyncResult(); - assertThat(allWithoutSyncResult).extracting(OrganisationsEinheit::getId).containsExactlyInAnyOrder(WITHOUT_SYNC_RESULT_ID_1, WITHOUT_SYNC_RESULT_ID_2); + assertThat(allWithoutSyncResult).extracting(OrganisationsEinheit::getId) + .containsExactlyInAnyOrder(WITHOUT_SYNC_RESULT_ID_1, WITHOUT_SYNC_RESULT_ID_2); } } diff --git a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitServiceTest.java b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitServiceTest.java index 822efd5c057a8b196deac785e1563848070d67c8..aaf92457a1ca0d94efb929733852e1dbf022b03b 100644 --- a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitServiceTest.java +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitServiceTest.java @@ -139,12 +139,26 @@ class OrganisationsEinheitServiceTest { @Nested class TestGetChildren { + private final OrganisationsEinheit organisationsEinheit = OrganisationsEinheitTestFactory.create(); + + @BeforeEach + void setUp() { + when(repository.findChildren(OrganisationsEinheitTestFactory.ID)).thenReturn(List.of(organisationsEinheit)); + } + @Test void shouldCallRepository() { service.getChildren(OrganisationsEinheitTestFactory.ID); verify(repository).findChildren(OrganisationsEinheitTestFactory.ID); } + + @Test + void shouldReturnChildren() { + var children = service.getChildren(OrganisationsEinheitTestFactory.ID); + + assertThat(children).containsExactly(organisationsEinheit); + } } @Nested diff --git a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitSettingsTestFactory.java b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitSettingsTestFactory.java index ababf88ba11eb1378cc4416c357098c48abd8bc7..c5028fb0e25bb8b6127e623a4e09b75794545d61 100644 --- a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitSettingsTestFactory.java +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitSettingsTestFactory.java @@ -15,4 +15,9 @@ public class OrganisationsEinheitSettingsTestFactory { .signatur(SIGNATUR); } + public static OrganisationsEinheitSettings.OrganisationsEinheitSettingsBuilder createNewBuilder() { + return OrganisationsEinheitSettings.builder() + .signatur(LoremIpsum.getInstance().getWords(3)); + } + } diff --git a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitTestFactory.java b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitTestFactory.java index 2feb3311fb010748e58beed1f48b9a132c24d015..756ec38fc6a01660b352924cebbd24176c4a9939 100644 --- a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitTestFactory.java +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitTestFactory.java @@ -34,4 +34,16 @@ public class OrganisationsEinheitTestFactory { .lastSyncTimestamp(LAST_SYNC_UPDATE); } + public static OrganisationsEinheit.OrganisationsEinheitBuilder createNewBuilder() { + return OrganisationsEinheit.builder() + .id(UUID.randomUUID().toString()) + .keycloakId(UUID.randomUUID().toString()) + .name(LoremIpsum.getInstance().getName()) + .organisationsEinheitId(UUID.randomUUID().toString()) + .parentId(UUID.randomUUID().toString()) + .zufiId(UUID.randomUUID().toString()) + .syncResult(SyncResult.DELETED) + .settings(OrganisationsEinheitSettingsTestFactory.createNewBuilder().build()) + .lastSyncTimestamp(LAST_SYNC_UPDATE - 10000); + } } 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..1af0cf1617c485ebb64936aba8e653cdafa83ed1 --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncAddedOrganisationsEinheitenITCase.java @@ -0,0 +1,62 @@ +package de.ozgcloud.admin.organisationseinheit; + +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.groups.Tuple.tuple; + +import java.time.Instant; +import java.util.List; +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.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())); + } + + 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(); + } +} diff --git a/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncServiceITCase.java b/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncServiceITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..c681785b71ea74380ac446ca736527d131579284 --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncServiceITCase.java @@ -0,0 +1,630 @@ +package de.ozgcloud.admin.organisationseinheit; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.time.Instant; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.mongodb.core.MongoOperations; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.admin.keycloak.GroupTestFactory; +import de.ozgcloud.admin.keycloak.KeycloakRemoteService; +import de.ozgcloud.common.test.DataITCase; + +@DataITCase +@EnableAutoConfiguration +class SyncServiceITCase { + + @Autowired + private MongoOperations operations; + + @Autowired + private SyncService service; + + @Autowired + private OrganisationsEinheitRepository repository; + + @MockBean + private KeycloakRemoteService keycloakRemoteService; + + @MockBean + private OrganisationsEinheitRemoteService organisationsEinheitRemoteService; + + @BeforeEach + void clearDatabase() { + operations.dropCollection(OrganisationsEinheit.class); + } + + @DisplayName("Organisationseinheit not found in PVOG") + @Test + void shouldSyncNotFoundInPvog() { + var syncTimestamp = Instant.now().toEpochMilli(); + var group = GroupTestFactory.createBuilder().clearSubGroups().build(); + when(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()).thenReturn(Stream.of(group)); + when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.ORGANISATIONS_EINHEIT_ID)).thenReturn(List.of()); + + service.syncOrganisationsEinheitenFromKeycloak(syncTimestamp); + + var synced = repository.findAll(); + + assertThat(synced). + hasSize(1) + .first() + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + group.getId(), + group.getName(), + group.getOrganisationsEinheitId(), + SyncResult.NOT_FOUND_IN_PVOG, + null, + null, + Collections.emptyList(), + syncTimestamp); + } + + @DisplayName("Organisationseinheit found in PVOG but group name is different") + @Test + void shouldSyncNameMismatch() { + var syncTimestamp = Instant.now().toEpochMilli(); + var group = GroupTestFactory.createBuilder().clearSubGroups().build(); + var pvogOranigastionsEinheit = OrganisationsEinheitTestFactory.createBuilder().organisationsEinheitId(group.getOrganisationsEinheitId()) + .build(); + when(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()).thenReturn(Stream.of(group)); + when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.ORGANISATIONS_EINHEIT_ID)).thenReturn( + List.of(pvogOranigastionsEinheit)); + + service.syncOrganisationsEinheitenFromKeycloak(syncTimestamp); + + var synced = repository.findAll(); + + assertThat(synced). + hasSize(1) + .first() + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + group.getId(), + pvogOranigastionsEinheit.getName(), + group.getOrganisationsEinheitId(), + SyncResult.NAME_MISMATCH, + pvogOranigastionsEinheit.getId(), + null, + Collections.emptyList(), + syncTimestamp); + } + + @DisplayName("Multiple Organisationseinheiten for same group found in PVOG") + @Test + void shouldSyncOrganisationsEinheitNotUnique() { + var syncTimestamp = Instant.now().toEpochMilli(); + var group = GroupTestFactory.createBuilder().clearSubGroups().build(); + var pvogOranigastionsEinheit1 = OrganisationsEinheitTestFactory.createBuilder().organisationsEinheitId(group.getOrganisationsEinheitId()) + .build(); + var pvogOranigastionsEinheit2 = OrganisationsEinheitTestFactory.createBuilder().organisationsEinheitId(group.getOrganisationsEinheitId()) + .build(); + when(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()).thenReturn(Stream.of(group)); + when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.ORGANISATIONS_EINHEIT_ID)).thenReturn( + List.of(pvogOranigastionsEinheit1, pvogOranigastionsEinheit2)); + + service.syncOrganisationsEinheitenFromKeycloak(syncTimestamp); + + var synced = repository.findAll(); + + assertThat(synced). + hasSize(1) + .first() + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + group.getId(), + group.getName(), + group.getOrganisationsEinheitId(), + SyncResult.ORGANISATIONSEINHEIT_ID_NOT_UNIQUE, + null, + null, + Collections.emptyList(), + syncTimestamp); + } + + @DisplayName("Organisationseinheit found in PVOG and have same data") + @Test + void shouldSyncOk() { + var syncTimestamp = Instant.now().toEpochMilli(); + var group = GroupTestFactory.createBuilder().clearSubGroups().build(); + var pvogOranigastionsEinheit = OrganisationsEinheitTestFactory.createBuilder().organisationsEinheitId(group.getOrganisationsEinheitId()) + .name(group.getName()).build(); + when(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()).thenReturn(Stream.of(group)); + when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.ORGANISATIONS_EINHEIT_ID)).thenReturn( + List.of(pvogOranigastionsEinheit)); + + service.syncOrganisationsEinheitenFromKeycloak(syncTimestamp); + + var synced = repository.findAll(); + + assertThat(synced). + hasSize(1) + .first() + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + group.getId(), + group.getName(), + group.getOrganisationsEinheitId(), + SyncResult.OK, + pvogOranigastionsEinheit.getId(), + null, + Collections.emptyList(), + syncTimestamp); + } + + @DisplayName("OrganisationseinheitId attribute of group changed") + @Test + void shouldSyncUpdateAttribute() { + var syncTimestamp = Instant.now().toEpochMilli(); + var group = GroupTestFactory.createBuilder().clearSubGroups().build(); + var pvogOranigastionsEinheit = OrganisationsEinheitTestFactory.createBuilder().organisationsEinheitId(group.getOrganisationsEinheitId()) + .name(group.getName()).build(); + var newOrganisationsEinheitId = UUID.randomUUID().toString(); + when(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()).thenReturn(Stream.of(group)); + when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.ORGANISATIONS_EINHEIT_ID)).thenReturn( + List.of(pvogOranigastionsEinheit)); + + service.syncOrganisationsEinheitenFromKeycloak(syncTimestamp); + when(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()).thenReturn( + Stream.of(group.toBuilder().organisationsEinheitId(newOrganisationsEinheitId).build())); + service.syncOrganisationsEinheitenFromKeycloak(syncTimestamp + 1000); + + var synced = repository.findAll(); + + assertThat(synced). + hasSize(1) + .first() + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + group.getId(), + group.getName(), + newOrganisationsEinheitId, + SyncResult.NOT_FOUND_IN_PVOG, + null, + null, + Collections.emptyList(), + syncTimestamp + 1000); + } + + @DisplayName("Group deleted in Keycloak") + @Test + void shouldSyncDeleted() { + var syncTimestamp = Instant.now().toEpochMilli(); + var group = GroupTestFactory.createBuilder().clearSubGroups().build(); + var pvogOranigastionsEinheit = OrganisationsEinheitTestFactory.createBuilder().organisationsEinheitId(group.getOrganisationsEinheitId()) + .name(group.getName()).build(); + when(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()).thenReturn(Stream.of(group)); + when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.ORGANISATIONS_EINHEIT_ID)).thenReturn( + List.of(pvogOranigastionsEinheit)); + + service.syncOrganisationsEinheitenFromKeycloak(syncTimestamp); + when(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()).thenReturn(Stream.of()); + service.syncOrganisationsEinheitenFromKeycloak(syncTimestamp + 1000); + + var synced = repository.findAll(); + + assertThat(synced). + hasSize(1) + .first() + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + group.getId(), + group.getName(), + group.getOrganisationsEinheitId(), + SyncResult.DELETED, + pvogOranigastionsEinheit.getId(), + null, + Collections.emptyList(), + syncTimestamp); + } + + @DisplayName("Organisationseinheit for parent group and sub group not found in PVOG") + @Test + void shouldSyncParentNotFoundInPvogSubGroupNotFoundInPvog() { + var syncTimestamp = Instant.now().toEpochMilli(); + var group = GroupTestFactory.createBuilder().build(); + var subGroup = group.getSubGroups().getFirst(); + when(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()).thenReturn(Stream.of(group)); + when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.ORGANISATIONS_EINHEIT_ID)).thenReturn(List.of()); + + service.syncOrganisationsEinheitenFromKeycloak(syncTimestamp); + + var parentSynced = repository.findSyncedByKeycloakId(group.getId()).get(); + var childSynced = repository.findSyncedByKeycloakId(subGroup.getId()).get(); + + assertThat(parentSynced) + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + group.getId(), + group.getName(), + group.getOrganisationsEinheitId(), + SyncResult.NOT_FOUND_IN_PVOG, + null, + null, + Collections.emptyList(), + syncTimestamp); + assertThat(childSynced) + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + subGroup.getId(), + subGroup.getName(), + subGroup.getOrganisationsEinheitId(), + SyncResult.NOT_FOUND_IN_PVOG, + null, + parentSynced.getId(), + Collections.emptyList(), + syncTimestamp); + } + + @DisplayName("Organisationseinheit for parent group and sub group found in PVOG but have different names") + @Test + void shouldSyncParentNameMismatchSubGroupNameMismatch() { + var syncTimestamp = Instant.now().toEpochMilli(); + var group = GroupTestFactory.createBuilder().build(); + var subGroup = group.getSubGroups().getFirst(); + var pvogOranigastionsEinheit = OrganisationsEinheitTestFactory.createBuilder().organisationsEinheitId(group.getOrganisationsEinheitId()) + .build(); + var pvogChildOranigastionsEinheit = OrganisationsEinheitTestFactory.createBuilder() + .organisationsEinheitId(subGroup.getOrganisationsEinheitId()).name( + LoremIpsum.getInstance().getWords(2)) + .build(); + when(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()).thenReturn(Stream.of(group)); + when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.ORGANISATIONS_EINHEIT_ID)).thenReturn( + List.of(pvogOranigastionsEinheit)); + when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.SUB_GROUP_ORGANISATIONS_EINHEIT_ID)).thenReturn( + List.of(pvogChildOranigastionsEinheit)); + + service.syncOrganisationsEinheitenFromKeycloak(syncTimestamp); + + var parentSynced = repository.findSyncedByKeycloakId(group.getId()).get(); + var childSynced = repository.findSyncedByKeycloakId(subGroup.getId()).get(); + + assertThat(parentSynced) + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + group.getId(), + pvogOranigastionsEinheit.getName(), + group.getOrganisationsEinheitId(), + SyncResult.NAME_MISMATCH, + pvogOranigastionsEinheit.getId(), + null, + Collections.emptyList(), + syncTimestamp); + assertThat(childSynced) + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + subGroup.getId(), + pvogChildOranigastionsEinheit.getName(), + subGroup.getOrganisationsEinheitId(), + SyncResult.NAME_MISMATCH, + pvogChildOranigastionsEinheit.getId(), + parentSynced.getId(), + Collections.emptyList(), + syncTimestamp); + } + + @DisplayName("Multiple Organisationseinheiten for parent group and sub group found in PVOG") + @Test + void shouldSyncParentAndChildOrganisationsEinheitNotUnique() { + var syncTimestamp = Instant.now().toEpochMilli(); + var group = GroupTestFactory.createBuilder().build(); + var subGroup = group.getSubGroups().getFirst(); + var pvogOranigastionsEinheit1 = OrganisationsEinheitTestFactory.createBuilder().organisationsEinheitId(group.getOrganisationsEinheitId()) + .build(); + var pvogOranigastionsEinheit2 = OrganisationsEinheitTestFactory.createBuilder().organisationsEinheitId(group.getOrganisationsEinheitId()) + .build(); + var pvogChildOranigastionsEinheit1 = OrganisationsEinheitTestFactory.createBuilder() + .organisationsEinheitId(subGroup.getOrganisationsEinheitId()).name( + LoremIpsum.getInstance().getWords(2)) + .build(); + var pvogChildOranigastionsEinheit2 = OrganisationsEinheitTestFactory.createBuilder() + .organisationsEinheitId(subGroup.getOrganisationsEinheitId()).name( + LoremIpsum.getInstance().getWords(2)) + .build(); + when(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()).thenReturn(Stream.of(group)); + when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.ORGANISATIONS_EINHEIT_ID)).thenReturn( + List.of(pvogOranigastionsEinheit1, pvogOranigastionsEinheit2)); + when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.SUB_GROUP_ORGANISATIONS_EINHEIT_ID)).thenReturn( + List.of(pvogChildOranigastionsEinheit1, pvogChildOranigastionsEinheit2)); + + service.syncOrganisationsEinheitenFromKeycloak(syncTimestamp); + + var parentSynced = repository.findSyncedByKeycloakId(group.getId()).get(); + var childSynced = repository.findSyncedByKeycloakId(subGroup.getId()).get(); + + assertThat(parentSynced) + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + group.getId(), + group.getName(), + group.getOrganisationsEinheitId(), + SyncResult.ORGANISATIONSEINHEIT_ID_NOT_UNIQUE, + null, + null, + Collections.emptyList(), + syncTimestamp); + assertThat(childSynced) + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + subGroup.getId(), + subGroup.getName(), + subGroup.getOrganisationsEinheitId(), + SyncResult.ORGANISATIONSEINHEIT_ID_NOT_UNIQUE, + null, + parentSynced.getId(), + Collections.emptyList(), + syncTimestamp); + } + + @DisplayName("Organisationseinheit and child Organisationseinheit found in PVOG and have same data") + @Test + void shouldSyncOkGroupWithSubGroup() { + var syncTimestamp = Instant.now().toEpochMilli(); + var group = GroupTestFactory.createBuilder().build(); + var subGroup = group.getSubGroups().getFirst(); + var pvogOranigastionsEinheit = OrganisationsEinheitTestFactory.createBuilder().organisationsEinheitId(group.getOrganisationsEinheitId()) + .name(group.getName()).build(); + var pvogChildOranigastionsEinheit = OrganisationsEinheitTestFactory.createBuilder() + .organisationsEinheitId(subGroup.getOrganisationsEinheitId()).name( + subGroup.getName()) + .build(); + when(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()).thenReturn(Stream.of(group)); + when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.ORGANISATIONS_EINHEIT_ID)).thenReturn( + List.of(pvogOranigastionsEinheit)); + when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.SUB_GROUP_ORGANISATIONS_EINHEIT_ID)).thenReturn( + List.of(pvogChildOranigastionsEinheit)); + + service.syncOrganisationsEinheitenFromKeycloak(syncTimestamp); + + var parentSynced = repository.findSyncedByKeycloakId(group.getId()).get(); + var childSynced = repository.findSyncedByKeycloakId(subGroup.getId()).get(); + + assertThat(parentSynced) + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + group.getId(), + group.getName(), + group.getOrganisationsEinheitId(), + SyncResult.OK, + pvogOranigastionsEinheit.getId(), + null, + Collections.emptyList(), + syncTimestamp); + assertThat(childSynced) + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + subGroup.getId(), + subGroup.getName(), + subGroup.getOrganisationsEinheitId(), + SyncResult.OK, + pvogChildOranigastionsEinheit.getId(), + parentSynced.getId(), + Collections.emptyList(), + syncTimestamp); + } + + @DisplayName("Group with sub group deleted in Keycloak") + @Test + void shouldSyncGroupAndSubGroupDeleted() { + var syncTimestamp = Instant.now().toEpochMilli(); + var group = GroupTestFactory.createBuilder().build(); + var subGroup = group.getSubGroups().getFirst(); + var pvogOranigastionsEinheit = OrganisationsEinheitTestFactory.createBuilder().organisationsEinheitId(group.getOrganisationsEinheitId()) + .name(group.getName()).build(); + var pvogChildOranigastionsEinheit = OrganisationsEinheitTestFactory.createBuilder() + .organisationsEinheitId(subGroup.getOrganisationsEinheitId()).name( + subGroup.getName()) + .build(); + when(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()).thenReturn(Stream.of(group)); + when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.ORGANISATIONS_EINHEIT_ID)).thenReturn( + List.of(pvogOranigastionsEinheit)); + when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.SUB_GROUP_ORGANISATIONS_EINHEIT_ID)).thenReturn( + List.of(pvogChildOranigastionsEinheit)); + + service.syncOrganisationsEinheitenFromKeycloak(syncTimestamp); + when(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()).thenReturn(Stream.of()); + service.syncOrganisationsEinheitenFromKeycloak(syncTimestamp + 1000); + + var parentSynced = repository.findSyncedByKeycloakId(group.getId()).get(); + var childSynced = repository.findSyncedByKeycloakId(subGroup.getId()).get(); + + assertThat(parentSynced) + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + group.getId(), + group.getName(), + group.getOrganisationsEinheitId(), + SyncResult.DELETED, + pvogOranigastionsEinheit.getId(), + null, + Collections.emptyList(), + syncTimestamp); + assertThat(childSynced) + .matches(organisationsEinheit -> Objects.nonNull(organisationsEinheit.getId())) + .matches(organisationsEinheit -> Objects.isNull(organisationsEinheit.getSettings().getSignatur())) + .extracting( + OrganisationsEinheit::getKeycloakId, + OrganisationsEinheit::getName, + OrganisationsEinheit::getOrganisationsEinheitId, + OrganisationsEinheit::getSyncResult, + OrganisationsEinheit::getZufiId, + OrganisationsEinheit::getParentId, + OrganisationsEinheit::getChildren, + OrganisationsEinheit::getLastSyncTimestamp + ).containsExactly( + subGroup.getId(), + subGroup.getName(), + subGroup.getOrganisationsEinheitId(), + SyncResult.DELETED, + pvogChildOranigastionsEinheit.getId(), + parentSynced.getId(), + Collections.emptyList(), + syncTimestamp); + } + +} \ 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..f9b3a0011fc4767b653a49ea3d1c3b85f65034e8 100644 --- a/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncServiceTest.java +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/SyncServiceTest.java @@ -20,6 +20,8 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; +import com.thedeanda.lorem.LoremIpsum; + import de.ozgcloud.admin.keycloak.AddGroupData; import de.ozgcloud.admin.keycloak.AddGroupDataTestFactory; import de.ozgcloud.admin.keycloak.Group; @@ -54,7 +56,7 @@ class SyncServiceTest { @BeforeEach void setUp() { when(keycloakRemoteService.getGroupsWithOrganisationsEinheitId()).thenReturn(Stream.of(group)); - doNothing().when(service).syncGroups(any(), any(), anyLong()); + doNothing().when(service).syncGroupsWithSubGroups(any(), any(), anyLong()); } @Test @@ -68,7 +70,7 @@ class SyncServiceTest { void shouldSyncGroupsFromKeycloak() { callService(); - verify(service).syncGroups(group, null, syncTimestamp); + verify(service).syncGroupsWithSubGroups(group, null, syncTimestamp); } @Test @@ -85,7 +87,7 @@ class SyncServiceTest { } @Nested - class TestSyncGroups { + class TestSyncGroupsWithSubGroups { private final long syncTimestamp = Instant.now().toEpochMilli(); private final Group group = GroupTestFactory.create(); @@ -96,28 +98,28 @@ class SyncServiceTest { @BeforeEach void setUp() { doReturn(syncedOrganisationsEinheitGroup).when(service).syncGroup(group, null, syncTimestamp); - doReturn(savedOrganisationsEinheitGroup).when(service).saveSyncedOrganisationsEinheit(any()); + doReturn(savedOrganisationsEinheitGroup).when(service).saveSyncedOrganisationsEinheit(syncedOrganisationsEinheitGroup); } @Test void shouldSyncGroup() { - service.syncGroups(group, null, syncTimestamp); + service.syncGroupsWithSubGroups(group, null, syncTimestamp); verify(service).syncGroup(group, null, syncTimestamp); } @Test - void shouldSaveSyncedGroup() { - service.syncGroups(group, null, syncTimestamp); + void shouldSaveSyncedOrganisationsEinheit() { + service.syncGroupsWithSubGroups(group, null, syncTimestamp); verify(service).saveSyncedOrganisationsEinheit(syncedOrganisationsEinheitGroup); } @Test void shouldSyncSubGroupsWithSyncedOrganisationsEinheitAsParent() { - service.syncGroups(group, null, syncTimestamp); + service.syncGroupsWithSubGroups(group, null, syncTimestamp); - verify(service).syncGroups(group.getSubGroups().getFirst(), savedOrganisationsEinheitGroup, syncTimestamp); + verify(service).syncGroupsWithSubGroups(group.getSubGroups().getFirst(), savedOrganisationsEinheitGroup, syncTimestamp); } } @@ -129,14 +131,18 @@ class SyncServiceTest { private final OrganisationsEinheit parent = OrganisationsEinheitTestFactory.createBuilder().id(UUID.randomUUID().toString()).build(); private final OrganisationsEinheit pvogOrganisationsEinheit = OrganisationsEinheitTestFactory.createBuilder().zufiId(null).settings(null) .parentId(null).syncResult(null).build(); + private final String syncedName = LoremIpsum.getInstance().getWords(3); + private final SyncResult syncedSyncResult = SyncResult.OK; + private final String syncedZufiId = UUID.randomUUID().toString(); @Nested class ParentGroup { @BeforeEach void setUp() { - doReturn(OrganisationsEinheitTestFactory.NAME).when(service).syncName(anyList(), any()); - doReturn(SyncResult.OK).when(service).evaluateSyncResult(anyList(), any()); + doReturn(syncedName).when(service).syncName(List.of(pvogOrganisationsEinheit), group); + doReturn(syncedSyncResult).when(service).evaluateSyncResult(List.of(pvogOrganisationsEinheit), group); + doReturn(syncedZufiId).when(service).syncZufiId(List.of(pvogOrganisationsEinheit)); when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.ORGANISATIONS_EINHEIT_ID)).thenReturn( List.of(pvogOrganisationsEinheit)); } @@ -150,16 +156,18 @@ class SyncServiceTest { @Test void shouldSyncName() { - service.syncGroup(group, null, syncTimestamp); + var synced = service.syncGroup(group, null, syncTimestamp); verify(service).syncName(List.of(pvogOrganisationsEinheit), group); + assertThat(synced.getName()).isEqualTo(syncedName); } @Test void shouldEvaluateSyncResult() { - service.syncGroup(group, null, syncTimestamp); + var synced = service.syncGroup(group, null, syncTimestamp); verify(service).evaluateSyncResult(List.of(pvogOrganisationsEinheit), group); + assertThat(synced.getSyncResult()).isEqualTo(syncedSyncResult); } @Test @@ -169,11 +177,19 @@ class SyncServiceTest { verify(organisationsEinheitRemoteService).getByOrganisationsEinheitId(GroupTestFactory.ORGANISATIONS_EINHEIT_ID); } + @Test + void shouldSetOrganisationsEinheitId() { + var synced = service.syncGroup(group, null, syncTimestamp); + + assertThat(synced.getOrganisationsEinheitId()).isEqualTo(group.getOrganisationsEinheitId()); + } + @Test void shouldSyncZufiId() { - service.syncGroup(group, null, syncTimestamp); + var synced = service.syncGroup(group, null, syncTimestamp); verify(service).syncZufiId(List.of(pvogOrganisationsEinheit)); + assertThat(synced.getZufiId()).isEqualTo(syncedZufiId); } @Test @@ -189,8 +205,9 @@ class SyncServiceTest { @BeforeEach void setUp() { - doReturn(OrganisationsEinheitTestFactory.NAME).when(service).syncName(anyList(), any()); - doReturn(SyncResult.OK).when(service).evaluateSyncResult(anyList(), any()); + doReturn(syncedName).when(service).syncName(List.of(pvogOrganisationsEinheit), group.getSubGroups().getFirst()); + doReturn(syncedSyncResult).when(service).evaluateSyncResult(List.of(pvogOrganisationsEinheit), group.getSubGroups().getFirst()); + doReturn(syncedZufiId).when(service).syncZufiId(List.of(pvogOrganisationsEinheit)); when(organisationsEinheitRemoteService.getByOrganisationsEinheitId(GroupTestFactory.SUB_GROUP_ORGANISATIONS_EINHEIT_ID)).thenReturn( List.of(pvogOrganisationsEinheit)); } @@ -204,23 +221,26 @@ class SyncServiceTest { @Test void shouldSyncName() { - service.syncGroup(group.getSubGroups().getFirst(), parent, syncTimestamp); + var synced = service.syncGroup(group.getSubGroups().getFirst(), parent, syncTimestamp); verify(service).syncName(List.of(pvogOrganisationsEinheit), group.getSubGroups().getFirst()); + assertThat(synced.getName()).isEqualTo(syncedName); } @Test void shouldEvaluateSyncResult() { - service.syncGroup(group.getSubGroups().getFirst(), parent, syncTimestamp); + var synced = service.syncGroup(group.getSubGroups().getFirst(), parent, syncTimestamp); verify(service).evaluateSyncResult(List.of(pvogOrganisationsEinheit), group.getSubGroups().getFirst()); + assertThat(synced.getSyncResult()).isEqualTo(syncedSyncResult); } @Test void shouldGetOrganisationsEinheit() { - service.syncGroup(group.getSubGroups().getFirst(), parent, syncTimestamp); + var synced = service.syncGroup(group.getSubGroups().getFirst(), parent, syncTimestamp); verify(organisationsEinheitRemoteService).getByOrganisationsEinheitId(GroupTestFactory.SUB_GROUP_ORGANISATIONS_EINHEIT_ID); + assertThat(synced.getOrganisationsEinheitId()).isEqualTo(group.getSubGroups().getFirst().getOrganisationsEinheitId()); } @Test @@ -232,9 +252,10 @@ class SyncServiceTest { @Test void shouldSyncZufiId() { - service.syncGroup(group.getSubGroups().getFirst(), parent, syncTimestamp); + var synced = service.syncGroup(group.getSubGroups().getFirst(), parent, syncTimestamp); verify(service).syncZufiId(List.of(pvogOrganisationsEinheit)); + assertThat(synced.getZufiId()).isEqualTo(syncedZufiId); } @Test @@ -377,7 +398,7 @@ class SyncServiceTest { @Nested class SyncedOrganisationsEinheitExists { - private final OrganisationsEinheit existingOrganisationsEinheit = OrganisationsEinheitTestFactory.create(); + private final OrganisationsEinheit existingOrganisationsEinheit = OrganisationsEinheitTestFactory.createNewBuilder().build(); @Captor private ArgumentCaptor<OrganisationsEinheit> savedOrganisationsEinheitArgumentCaptor; @@ -416,16 +437,20 @@ 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[] organisationsEinheiten = new OrganisationsEinheit[] { withoutSyncResult2, withoutSyncResult1 }; + + @Captor + private ArgumentCaptor<Stream<OrganisationsEinheit>> streamArgumentCaptor; @BeforeEach void setUp() { - when(repository.findAllWithoutSyncResult()).thenReturn(Stream.of(withoutSyncResult1, withoutSyncResult2)); - doNothing().when(service).syncAddedOrganisationsEinheit(anyLong(), any()); + when(repository.findAllWithoutSyncResult()).thenReturn(Stream.of(organisationsEinheiten)); + doNothing().when(service).syncAddedOrganisationsEinheit(any(), anyLong()); } @Test @@ -439,14 +464,14 @@ class SyncServiceTest { void shouldSynchronizeFirstOrganisationsEinheit() { callService(); - verify(service).syncAddedOrganisationsEinheit(syncTimestamp, withoutSyncResult1); + verify(service).syncAddedOrganisationsEinheit(withoutSyncResult1, syncTimestamp); } @Test void shouldSynchronizeSecondOrganisationsEinheit() { callService(); - verify(service).syncAddedOrganisationsEinheit(syncTimestamp, withoutSyncResult2); + verify(service).syncAddedOrganisationsEinheit(withoutSyncResult2, syncTimestamp); } private void callService() { @@ -458,75 +483,128 @@ class SyncServiceTest { class TestSyncAddedOrganisationsEinheit { private final long syncTimestamp = Instant.now().toEpochMilli(); + private final String addedGroupId = UUID.randomUUID().toString(); private final OrganisationsEinheit organisationsEinheit = OrganisationsEinheitTestFactory.create(); - private final AddGroupData addGroupData = AddGroupDataTestFactory.create(); - private final String keycloakId = GroupTestFactory.ID; - - @Captor - private ArgumentCaptor<OrganisationsEinheit> savedOrganisationsEinheitArgumentCaptor; - - @BeforeEach - void setUp() { - when(organisationsEinheitMapper.toAddGroupData(organisationsEinheit)).thenReturn(addGroupData); - } @Test - void shouldCreateAddGroupData() { - givenAddGroupInKeycloakSuccessful(); + void shouldAddAsGroupInKeycloak() { + doReturn(Optional.empty()).when(service).addAsGroupInKeycloak(organisationsEinheit); callService(); - verify(organisationsEinheitMapper).toAddGroupData(organisationsEinheit); + verify(service).addAsGroupInKeycloak(organisationsEinheit); } @Test - void shouldAddGroupInKeycloak() { - givenAddGroupInKeycloakSuccessful(); + void shouldUpdateAfterSuccessfulGroupCreation() { + doReturn(Optional.of(addedGroupId)).when(service).addAsGroupInKeycloak(organisationsEinheit); + doNothing().when(service).updateAfterSuccessfulGroupCreation(organisationsEinheit, addedGroupId, syncTimestamp); callService(); - verify(service).addGroupInKeycloak(addGroupData); + verify(service).updateAfterSuccessfulGroupCreation(organisationsEinheit, addedGroupId, syncTimestamp); } @Test - void shouldSaveIfAddGroupSuccessful() { - givenAddGroupInKeycloakSuccessful(); + void shouldNotUpdate() { + doReturn(Optional.empty()).when(service).addAsGroupInKeycloak(organisationsEinheit); callService(); - verify(repository).save(savedOrganisationsEinheitArgumentCaptor.capture()); - assertThat(savedOrganisationsEinheitArgumentCaptor.getValue()).extracting( - OrganisationsEinheit::getKeycloakId, - OrganisationsEinheit::getSyncResult, - OrganisationsEinheit::getLastSyncTimestamp - ).containsExactly(keycloakId, SyncResult.OK, syncTimestamp); + verify(service, never()).updateAfterSuccessfulGroupCreation(any(), any(), anyLong()); } - @Test - void shouldNotSaveIfAddGroupFailed() { - givenAddGroupInKeycloakFailed(); + private void callService() { + service.syncAddedOrganisationsEinheit(organisationsEinheit, syncTimestamp); + } + } - callService(); + @Nested + class TestAddAsGroupInKeycloak { - verifyNoInteractions(repository); - } + @Nested + class OnParentIdIsNotSet { + + private final OrganisationsEinheit organisationsEinheit = OrganisationsEinheitTestFactory.createBuilder().parentId(null).build(); + private final AddGroupData addGroupData = AddGroupDataTestFactory.create(); + private final String addedGroupId = UUID.randomUUID().toString(); - private void givenAddGroupInKeycloakSuccessful() { - doReturn(Optional.of(keycloakId)).when(service).addGroupInKeycloak(addGroupData); + @BeforeEach + void init() { + when(organisationsEinheitMapper.toAddGroupData(organisationsEinheit)).thenReturn(addGroupData); + doReturn(Optional.of(addedGroupId)).when(service).addGroupInKeycloak(addGroupData); + } + + @Test + void shouldCreateAddGroupData() { + callService(); + + verify(organisationsEinheitMapper).toAddGroupData(organisationsEinheit); + } + + @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); + } } - private void givenAddGroupInKeycloakFailed() { - doReturn(Optional.empty()).when(service).addGroupInKeycloak(addGroupData); + @Nested + class OnParentIdIsSet { + + @Test + void shouldThrowException() { + var withParentId = OrganisationsEinheitTestFactory.create(); + assertThatExceptionOfType(OrganisationsEinheitSynchronizationException.class) + .isThrownBy(() -> service.addAsGroupInKeycloak(withParentId)) + .withMessage("Organisationseinheit " + withParentId.getOrganisationsEinheitId() + " has parent"); + } } + } - private void callService() { - service.syncAddedOrganisationsEinheit(syncTimestamp, organisationsEinheit); + @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, keycloakId, syncTimestamp); + + verify(repository).save(organisationsEinheitArgumentCaptor.capture()); + assertThat(organisationsEinheitArgumentCaptor.getValue()) + .extracting(OrganisationsEinheit::getKeycloakId, OrganisationsEinheit::getSyncResult, OrganisationsEinheit::getLastSyncTimestamp) + .containsExactly(keycloakId, SyncResult.OK, syncTimestamp); } } @Nested class TestAddGroupInKeycloak { + private static final String FAILURE_MESSAGE = LoremIpsum.getInstance().getWords(5); + private final Throwable resourceCreationException = new ResourceCreationException(FAILURE_MESSAGE); + private final String keycloakId = GroupTestFactory.ID; private final AddGroupData addGroupData = AddGroupDataTestFactory.create(); @@ -549,12 +627,11 @@ class SyncServiceTest { } @Test - void shouldReturnEmptyInCaseOfException() { + void shouldThrowExceptionInCaseOfResourceCreationException() { givenAddGroupFailed(); - var keycloakId = callService(); - - assertThat(keycloakId).isEmpty(); + assertThatExceptionOfType(OrganisationsEinheitSynchronizationException.class).isThrownBy(this::callService) + .withMessage("Error creating group %s in Keycloak", addGroupData.toString()).withCause(resourceCreationException); } private void givenAddGroupSuccessful() { @@ -562,7 +639,7 @@ class SyncServiceTest { } private void givenAddGroupFailed() { - when(keycloakRemoteService.addGroup(addGroupData)).thenThrow(new ResourceCreationException("Failure")); + when(keycloakRemoteService.addGroup(addGroupData)).thenThrow(resourceCreationException); } private Optional<String> callService() {