diff --git a/api/userservice.yaml b/api/userservice.yaml index a39acc5e3..a6c3a9813 100644 --- a/api/userservice.yaml +++ b/api/userservice.yaml @@ -2589,6 +2589,10 @@ components: description: "Datetime stamp when the user accepted data privacy" emailNotifications: $ref: "#/components/schemas/EmailNotificationsDTO" + sessions: + type: array + items: + $ref: "#/components/schemas/SessionDTO" PatchUserDTO: type: object diff --git a/src/main/java/de/caritas/cob/userservice/api/adapters/web/dto/UserDataResponseDTO.java b/src/main/java/de/caritas/cob/userservice/api/adapters/web/dto/UserDataResponseDTO.java index 891ff9cf0..a6c9b80d2 100644 --- a/src/main/java/de/caritas/cob/userservice/api/adapters/web/dto/UserDataResponseDTO.java +++ b/src/main/java/de/caritas/cob/userservice/api/adapters/web/dto/UserDataResponseDTO.java @@ -89,4 +89,6 @@ public class UserDataResponseDTO { private Boolean available; private EmailNotificationsDTO emailNotifications; + + private Set sessions; } diff --git a/src/main/java/de/caritas/cob/userservice/api/facade/userdata/AskerDataProvider.java b/src/main/java/de/caritas/cob/userservice/api/facade/userdata/AskerDataProvider.java index 62edfddd4..a4a9fd20c 100644 --- a/src/main/java/de/caritas/cob/userservice/api/facade/userdata/AskerDataProvider.java +++ b/src/main/java/de/caritas/cob/userservice/api/facade/userdata/AskerDataProvider.java @@ -3,6 +3,7 @@ import de.caritas.cob.userservice.api.adapters.web.dto.AgencyDTO; import de.caritas.cob.userservice.api.adapters.web.dto.LanguageCode; import de.caritas.cob.userservice.api.adapters.web.dto.UserDataResponseDTO; +import de.caritas.cob.userservice.api.adapters.web.dto.UserDataResponseDTO.UserDataResponseDTOBuilder; import de.caritas.cob.userservice.api.helper.AuthenticatedUser; import de.caritas.cob.userservice.api.helper.SessionDataProvider; import de.caritas.cob.userservice.api.manager.consultingtype.ConsultingTypeManager; @@ -11,6 +12,8 @@ import de.caritas.cob.userservice.api.model.UserAgency; import de.caritas.cob.userservice.api.port.out.IdentityClientConfig; import de.caritas.cob.userservice.api.service.agency.AgencyService; +import de.caritas.cob.userservice.api.service.session.SessionMapper; +import de.caritas.cob.userservice.api.service.session.SessionService; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; @@ -37,6 +40,8 @@ public class AskerDataProvider { private final @NonNull ConsultingTypeManager consultingTypeManager; private final @NonNull IdentityClientConfig identityClientConfig; + private final @NonNull SessionService sessionService; + private final @NonNull EmailNotificationMapper emailNotificationMapper; /** @@ -46,24 +51,39 @@ public class AskerDataProvider { * @return the user data */ public UserDataResponseDTO retrieveData(User user) { - return UserDataResponseDTO.builder() - .userId(user.getUserId()) - .userName(user.getUsername()) - .email(observeUserEmailAddress(user)) - .isAbsent(false) - .encourage2fa(user.getEncourage2fa()) - .isFormalLanguage(user.isLanguageFormal()) - .preferredLanguage(LanguageCode.fromValue(user.getLanguageCode().toString())) - .isInTeamAgency(false) - .userRoles(authenticatedUser.getRoles()) - .grantedAuthorities(authenticatedUser.getGrantedAuthorities()) - .consultingTypes(getConsultingTypes(user)) - .hasAnonymousConversations(false) - .hasArchive(false) - .dataPrivacyConfirmation(user.getDataPrivacyConfirmation()) - .termsAndConditionsConfirmation(user.getTermsAndConditionsConfirmation()) - .emailNotifications(emailNotificationMapper.toEmailNotificationsDTO(user)) - .build(); + var userDataResponseDTOBuilder = + UserDataResponseDTO.builder() + .userId(user.getUserId()) + .userName(user.getUsername()) + .email(observeUserEmailAddress(user)) + .isAbsent(false) + .encourage2fa(user.getEncourage2fa()) + .isFormalLanguage(user.isLanguageFormal()) + .preferredLanguage(LanguageCode.fromValue(user.getLanguageCode().toString())) + .isInTeamAgency(false) + .userRoles(authenticatedUser.getRoles()) + .grantedAuthorities(authenticatedUser.getGrantedAuthorities()) + .consultingTypes(getConsultingTypes(user)) + .hasAnonymousConversations(false) + .hasArchive(false) + .dataPrivacyConfirmation(user.getDataPrivacyConfirmation()) + .termsAndConditionsConfirmation(user.getTermsAndConditionsConfirmation()) + .emailNotifications(emailNotificationMapper.toEmailNotificationsDTO(user)); + + enrichWithUserSessions(user, userDataResponseDTOBuilder); + return userDataResponseDTOBuilder.build(); + } + + private void enrichWithUserSessions( + User user, UserDataResponseDTOBuilder userDataResponseDTOBuilder) { + List sessionsByUser = sessionService.findSessionsByUser(user); + if (CollectionUtils.isNotEmpty(sessionsByUser)) { + SessionMapper sessionMapper = new SessionMapper(); + userDataResponseDTOBuilder.sessions( + sessionsByUser.stream() + .map(sessionMapper::convertToSessionDTO) + .collect(Collectors.toSet())); + } } private String observeUserEmailAddress(User user) { diff --git a/src/main/java/de/caritas/cob/userservice/api/service/session/SessionService.java b/src/main/java/de/caritas/cob/userservice/api/service/session/SessionService.java index 177233fe9..a5a588f74 100644 --- a/src/main/java/de/caritas/cob/userservice/api/service/session/SessionService.java +++ b/src/main/java/de/caritas/cob/userservice/api/service/session/SessionService.java @@ -747,6 +747,13 @@ public Optional findSessionByConsultantAndUserAndConsultingType( return Optional.empty(); } + public List findSessionsByUser(User user) { + if (nonNull(user)) { + return sessionRepository.findByUser(user); + } + return emptyList(); + } + public String findGroupIdByConsultantAndUser(String consultantId, String askerId) { Optional consultant = consultantService.getConsultant(consultantId); diff --git a/src/main/java/de/caritas/cob/userservice/api/workflow/delete/service/DeleteInactiveSessionsAndUserService.java b/src/main/java/de/caritas/cob/userservice/api/workflow/delete/service/DeleteInactiveSessionsAndUserService.java index 3b92ef44a..83d49f609 100644 --- a/src/main/java/de/caritas/cob/userservice/api/workflow/delete/service/DeleteInactiveSessionsAndUserService.java +++ b/src/main/java/de/caritas/cob/userservice/api/workflow/delete/service/DeleteInactiveSessionsAndUserService.java @@ -31,9 +31,14 @@ public class DeleteInactiveSessionsAndUserService { private final @NonNull SessionRepository sessionRepository; private final @NonNull DeleteUserAccountService deleteUserAccountService; private final @NonNull WorkflowErrorMailService workflowErrorMailService; + private final @NonNull WorkflowErrorLogService workflowErrorLogService; private final @NonNull DeleteSessionService deleteSessionService; private final @NonNull InactivePrivateGroupsProvider inactivePrivateGroupsProvider; + private static final String USER_NOT_FOUND_REASON = "User could not be found."; + private static final String RC_SESSION_GROUP_NOT_FOUND_REASON = + "Session with rc group id could not be found."; + /** * Deletes all inactive sessions and even the asker accounts, if there are no more active * sessions. @@ -49,15 +54,28 @@ public void deleteInactiveSessionsAndUsers() { .flatMap(Collection::stream) .collect(Collectors.toList()); - sendWorkflowErrorsMail(workflowErrors); + findWorkflowErrorByReason(workflowErrors); } - private void sendWorkflowErrorsMail(List workflowErrors) { + private void findWorkflowErrorByReason(List workflowErrors) { if (isNotEmpty(workflowErrors)) { - this.workflowErrorMailService.buildAndSendErrorMail(workflowErrors); + List rcSessionGroupNotFoundWorkflowErrors = + getSameReasonWorkflowErrors(workflowErrors, RC_SESSION_GROUP_NOT_FOUND_REASON); + List workflowErrorsExceptSessionGroupNotFound = + new ArrayList<>(workflowErrors); + workflowErrorsExceptSessionGroupNotFound.removeAll(rcSessionGroupNotFoundWorkflowErrors); + this.workflowErrorLogService.logWorkflowErrors(rcSessionGroupNotFoundWorkflowErrors); + this.workflowErrorMailService.buildAndSendErrorMail(workflowErrorsExceptSessionGroupNotFound); } } + private static List getSameReasonWorkflowErrors( + List workflowErrors, String reason) { + return workflowErrors.stream() + .filter(error -> reason.equals(error.getReason())) + .collect(Collectors.toList()); + } + private List performDeletionWorkflow( Entry> userInactiveGroupEntry) { @@ -73,7 +91,7 @@ private List performDeletionWorkflow( .deletionSourceType(ASKER) .deletionTargetType(ALL) .identifier(userInactiveGroupEntry.getKey()) - .reason("User could not be found.") + .reason(USER_NOT_FOUND_REASON) .timestamp(nowInUtc()) .build())); @@ -115,7 +133,7 @@ private List performSessionDeletion( .deletionSourceType(ASKER) .deletionTargetType(ALL) .identifier(rcGroupId) - .reason("Session with rc group id could not be found.") + .reason(RC_SESSION_GROUP_NOT_FOUND_REASON) .timestamp(nowInUtc()) .build())); diff --git a/src/main/java/de/caritas/cob/userservice/api/workflow/delete/service/WorkflowErrorLogService.java b/src/main/java/de/caritas/cob/userservice/api/workflow/delete/service/WorkflowErrorLogService.java new file mode 100644 index 000000000..3960b4525 --- /dev/null +++ b/src/main/java/de/caritas/cob/userservice/api/workflow/delete/service/WorkflowErrorLogService.java @@ -0,0 +1,35 @@ +package de.caritas.cob.userservice.api.workflow.delete.service; + +import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; + +import de.caritas.cob.userservice.api.workflow.delete.model.DeletionWorkflowError; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** Service class to log all deletion workflow errors. */ +@Slf4j +@Service +@RequiredArgsConstructor +public class WorkflowErrorLogService { + + public void logWorkflowErrors(List workflowErrors) { + if (isNotEmpty(workflowErrors)) { + workflowErrors.forEach( + workflowError -> + log.warn( + "Errors during deletion workflow:" + + " SourceType = {}; " + + "TargetType = {}; " + + "Identifier = {}; " + + "Reason = {}; " + + "Timestamp = {}.", + workflowError.getDeletionSourceType(), + workflowError.getDeletionTargetType(), + workflowError.getIdentifier(), + workflowError.getReason(), + workflowError.getTimestamp())); + } + } +} diff --git a/src/test/java/de/caritas/cob/userservice/api/facade/userdata/AskerDataProviderTest.java b/src/test/java/de/caritas/cob/userservice/api/facade/userdata/AskerDataProviderTest.java index 912322dc7..d80aaafb3 100644 --- a/src/test/java/de/caritas/cob/userservice/api/facade/userdata/AskerDataProviderTest.java +++ b/src/test/java/de/caritas/cob/userservice/api/facade/userdata/AskerDataProviderTest.java @@ -18,8 +18,11 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.google.api.client.util.Sets; +import com.google.common.collect.Lists; import com.neovisionaries.i18n.LanguageCode; import de.caritas.cob.userservice.api.adapters.web.dto.AgencyDTO; +import de.caritas.cob.userservice.api.adapters.web.dto.SessionDTO; import de.caritas.cob.userservice.api.adapters.web.dto.UserDataResponseDTO; import de.caritas.cob.userservice.api.config.auth.UserRole; import de.caritas.cob.userservice.api.exception.httpresponses.InternalServerErrorException; @@ -27,9 +30,14 @@ import de.caritas.cob.userservice.api.helper.SessionDataProvider; import de.caritas.cob.userservice.api.manager.consultingtype.ConsultingTypeManager; import de.caritas.cob.userservice.api.model.Session; +import de.caritas.cob.userservice.api.model.Session.RegistrationType; +import de.caritas.cob.userservice.api.model.Session.SessionStatus; import de.caritas.cob.userservice.api.model.User; import de.caritas.cob.userservice.api.port.out.IdentityClientConfig; import de.caritas.cob.userservice.api.service.agency.AgencyService; +import de.caritas.cob.userservice.api.service.session.SessionService; +import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -61,6 +69,8 @@ public class AskerDataProviderTest { @Mock EmailNotificationMapper emailNotificationMapper; + @Mock SessionService sessionService; + @Test public void retrieveData_Should_ReturnUserDataWithAgency_When_ProvidedWithUserWithAgencyInSession() { @@ -104,6 +114,39 @@ public void retrieveData_Should_ReturnUserDataWithAgency_When_ProvidedWithUserWi assertEquals(AGENCY_DTO_KREUZBUND, agency); } + @Test + public void retrieveData_Should_ReturnUserDataWithSessions_When_ProvidedWithUserWithSessions() { + givenAnEmailDummySuffixConfig(); + when(authenticatedUser.getRoles()).thenReturn(asSet(UserRole.USER.getValue())); + ArrayList inputSessions = + Lists.newArrayList( + Session.builder() + .id(1L) + .registrationType(RegistrationType.ANONYMOUS) + .languageCode(LanguageCode.de) + .postcode("12111") + .status(SessionStatus.NEW) + .createDate(LocalDateTime.now()) + .build(), + Session.builder() + .id(2L) + .registrationType(RegistrationType.REGISTERED) + .languageCode(LanguageCode.de) + .postcode("11111") + .createDate(LocalDateTime.now()) + .status(SessionStatus.NEW) + .build()); + when(sessionService.findSessionsByUser(Mockito.any(User.class))).thenReturn(inputSessions); + Set sessions = askerDataProvider.retrieveData(USER).getSessions(); + + assertEquals(sessions.size(), 2); + var expectedSessionsIds = Sets.newHashSet(); + expectedSessionsIds.add(1L); + expectedSessionsIds.add(2L); + var sessionIds = sessions.stream().map(SessionDTO::getId).collect(Collectors.toSet()); + assertEquals(sessionIds, expectedSessionsIds); + } + @Test(expected = InternalServerErrorException.class) public void retrieveData_GetConsultingTypes_Should_ThrowInternalServerErrorException_When_AgencyServiceHelperFails() { diff --git a/src/test/java/de/caritas/cob/userservice/api/workflow/delete/service/DeleteInactiveSessionsAndUserServiceTest.java b/src/test/java/de/caritas/cob/userservice/api/workflow/delete/service/DeleteInactiveSessionsAndUserServiceTest.java index 625cad2c1..92b1a151e 100644 --- a/src/test/java/de/caritas/cob/userservice/api/workflow/delete/service/DeleteInactiveSessionsAndUserServiceTest.java +++ b/src/test/java/de/caritas/cob/userservice/api/workflow/delete/service/DeleteInactiveSessionsAndUserServiceTest.java @@ -1,7 +1,10 @@ package de.caritas.cob.userservice.api.workflow.delete.service; -import static org.mockito.ArgumentMatchers.any; +import static de.caritas.cob.userservice.api.helper.CustomLocalDateTime.nowInUtc; +import static de.caritas.cob.userservice.api.workflow.delete.model.DeletionSourceType.ASKER; +import static de.caritas.cob.userservice.api.workflow.delete.model.DeletionTargetType.ALL; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -31,6 +34,7 @@ public class DeleteInactiveSessionsAndUserServiceTest { @InjectMocks private DeleteInactiveSessionsAndUserService deleteInactiveSessionsAndUserService; @Mock private WorkflowErrorMailService workflowErrorMailService; + @Mock private WorkflowErrorLogService workflowErrorLogService; @Mock private UserRepository userRepository; @Mock private SessionRepository sessionRepository; @Mock private DeleteUserAccountService deleteUserAccountService; @@ -38,7 +42,8 @@ public class DeleteInactiveSessionsAndUserServiceTest { @Mock private InactivePrivateGroupsProvider inactivePrivateGroupsProvider; @Test - public void deleteInactiveSessionsAndUsers_Should_SendWorkflowErrorsMail() { + public void + deleteInactiveSessionsAndUsers_Should_SendWorkflowErrorsMail_When_userNotFoundReason() { EasyRandom easyRandom = new EasyRandom(); User user = easyRandom.nextObject(User.class); @@ -60,7 +65,9 @@ public void deleteInactiveSessionsAndUsers_Should_SendWorkflowErrorsMail() { deleteInactiveSessionsAndUserService.deleteInactiveSessionsAndUsers(); - verify(workflowErrorMailService, Mockito.times(1)).buildAndSendErrorMail(any()); + verify(workflowErrorLogService, Mockito.times(1)).logWorkflowErrors(Collections.emptyList()); + verify(workflowErrorMailService, Mockito.times(1)) + .buildAndSendErrorMail(argThat(list -> !list.isEmpty())); } @Test @@ -115,7 +122,7 @@ public void deleteInactiveSessionsAndUsers_Should_SendWorkflowErrorsMail() { @Test public void - deleteInactiveSessionsAndUsers_Should_SendWorkflowErrorMail_WhenUserHasActiveAndInactiveSessionsAndHasErrors() { + deleteInactiveSessionsAndUsers_Should_logWorkflowErrorMail_WhenUserHasActiveAndInactiveSessionsAndHasErrors() { EasyRandom easyRandom = new EasyRandom(); User user = easyRandom.nextObject(User.class); @@ -132,18 +139,28 @@ public void deleteInactiveSessionsAndUsers_Should_SendWorkflowErrorsMail() { when(userRepository.findByRcUserIdAndDeleteDateIsNull(anyString())) .thenReturn(Optional.of(user)); when(sessionRepository.findByUser(user)).thenReturn(Arrays.asList(session1, session2)); - DeletionWorkflowError deletionWorkflowError = Mockito.mock(DeletionWorkflowError.class); + DeletionWorkflowError deletionWorkflowError = + DeletionWorkflowError.builder() + .deletionSourceType(ASKER) + .deletionTargetType(ALL) + .identifier(null) + .reason("Session with rc group id could not be found.") + .timestamp(nowInUtc()) + .build(); when(deleteSessionService.performSessionDeletion(session1)) .thenReturn(Collections.singletonList(deletionWorkflowError)); deleteInactiveSessionsAndUserService.deleteInactiveSessionsAndUsers(); - verify(workflowErrorMailService, Mockito.times(1)).buildAndSendErrorMail(any()); + verify(workflowErrorLogService, Mockito.times(1)) + .logWorkflowErrors(argThat(list -> !list.isEmpty())); + verify(workflowErrorMailService, Mockito.times(1)) + .buildAndSendErrorMail(Collections.emptyList()); } @Test public void - deleteInactiveSessionsAndUsers_Should_SendWorkflowErrorMail_WhenSessionCouldNotBeFound() { + deleteInactiveSessionsAndUsers_Should_logWorkflowErrorMail_WhenSessionCouldNotBeFound() { EasyRandom easyRandom = new EasyRandom(); User user = easyRandom.nextObject(User.class); @@ -164,7 +181,10 @@ public void deleteInactiveSessionsAndUsers_Should_SendWorkflowErrorsMail() { deleteInactiveSessionsAndUserService.deleteInactiveSessionsAndUsers(); - verify(workflowErrorMailService, Mockito.times(1)).buildAndSendErrorMail(any()); + verify(workflowErrorLogService, Mockito.times(1)) + .logWorkflowErrors(argThat(list -> !list.isEmpty())); + verify(workflowErrorMailService, Mockito.times(1)) + .buildAndSendErrorMail(Collections.emptyList()); } @Test @@ -187,6 +207,8 @@ public void deleteInactiveSessionsAndUsers_Should_SendWorkflowErrorsMail() { deleteInactiveSessionsAndUserService.deleteInactiveSessionsAndUsers(); - verify(workflowErrorMailService, Mockito.times(1)).buildAndSendErrorMail(any()); + verify(workflowErrorLogService, Mockito.times(1)).logWorkflowErrors(Collections.emptyList()); + verify(workflowErrorMailService, Mockito.times(1)) + .buildAndSendErrorMail(argThat(list -> !list.isEmpty())); } } diff --git a/src/test/java/de/caritas/cob/userservice/api/workflow/delete/service/WorkflowErrorLogServiceTest.java b/src/test/java/de/caritas/cob/userservice/api/workflow/delete/service/WorkflowErrorLogServiceTest.java new file mode 100644 index 000000000..43274c0e5 --- /dev/null +++ b/src/test/java/de/caritas/cob/userservice/api/workflow/delete/service/WorkflowErrorLogServiceTest.java @@ -0,0 +1,76 @@ +package de.caritas.cob.userservice.api.workflow.delete.service; + +import static de.caritas.cob.userservice.api.helper.CustomLocalDateTime.nowInUtc; +import static de.caritas.cob.userservice.api.workflow.delete.model.DeletionSourceType.ASKER; +import static de.caritas.cob.userservice.api.workflow.delete.model.DeletionTargetType.ROCKET_CHAT; +import static java.util.Collections.emptyList; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import de.caritas.cob.userservice.api.workflow.delete.model.DeletionWorkflowError; +import java.util.ArrayList; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.junit.MockitoJUnitRunner; +import org.slf4j.LoggerFactory; + +@RunWith(MockitoJUnitRunner.class) +public class WorkflowErrorLogServiceTest { + + @InjectMocks private WorkflowErrorLogService workflowErrorLogService; + + private ListAppender listAppender; + + @Before + public void setUp() { + Logger logger = (Logger) LoggerFactory.getLogger(WorkflowErrorLogService.class); + listAppender = new ListAppender<>(); + listAppender.start(); + logger.addAppender(listAppender); + } + + @Test + public void logWorkflowErrors_Should_logNoErrors_When_workflowErrorsAreNull() { + + // when + this.workflowErrorLogService.logWorkflowErrors(null); + + // then + Assertions.assertThat(listAppender.list).isEmpty(); + } + + @Test + public void logWorkflowErrors_Should_logNoErrors_When_workflowErrorsAreEmpty() { + + // when + this.workflowErrorLogService.logWorkflowErrors(emptyList()); + + // then + Assertions.assertThat(listAppender.list).isEmpty(); + } + + @Test + public void logWorkflowErrors_Should_logErrors_When_workflowErrorsExists() { + // given + List workflowErrors = new ArrayList<>(); + workflowErrors.add( + DeletionWorkflowError.builder() + .deletionSourceType(ASKER) + .deletionTargetType(ROCKET_CHAT) + .timestamp(nowInUtc()) + .reason("reason") + .identifier("id") + .build()); + + // when + this.workflowErrorLogService.logWorkflowErrors(workflowErrors); + + // then + Assertions.assertThat(listAppender.list).hasSize(1); + } +}