Skip to content

Commit

Permalink
server part for deleting a user
Browse files Browse the repository at this point in the history
  • Loading branch information
clean-coder authored and peggimann committed Nov 6, 2024
1 parent cc9a232 commit 756dd72
Show file tree
Hide file tree
Showing 12 changed files with 229 additions and 16 deletions.
38 changes: 31 additions & 7 deletions backend/src/main/java/ch/puzzle/okr/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import ch.puzzle.okr.dto.NewUserDto;
import ch.puzzle.okr.dto.UserDto;
import ch.puzzle.okr.dto.userOkrData.UserOkrDataDto;
import ch.puzzle.okr.mapper.UserMapper;
import ch.puzzle.okr.service.authorization.AuthorizationService;
import ch.puzzle.okr.service.authorization.UserAuthorizationService;
Expand All @@ -11,13 +12,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import java.util.List;

Expand Down Expand Up @@ -85,4 +80,33 @@ public List<UserDto> createUsers(
return userMapper.toDtos(createdUsers);
}

@Operation(summary = "Check if User is member of Teams", description = "Check if User is member of any Team.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "true if user is member of a Team", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = Boolean.class)) }), })

@GetMapping(path = "/{id}/ismemberofteams")
public Boolean isUserMemberOfTeams(
@Parameter(description = "The ID of the user.", required = true) @PathVariable long id) {

return this.userAuthorizationService.isUserMemberOfTeams(id);
}

@Operation(summary = "Get User OKR Data", description = "Get User OKR Data")
@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Returned User OKR Data.", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = UserOkrDataDto.class)) }), })
@GetMapping(path = "/{id}/userokrdata")
public UserOkrDataDto getUserOkrData(@PathVariable long id) {
return this.userAuthorizationService.getUserOkrData(id);
}

@Operation(summary = "Delete User by Id", description = "Delete User by Id")
@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Deleted User by Id"),
@ApiResponse(responseCode = "401", description = "Not authorized to delete a User", content = @Content),
@ApiResponse(responseCode = "404", description = "Did not find the User with requested id") })
@DeleteMapping(path = "/{id}")
public void deleteUserById(@PathVariable long id) {
this.userAuthorizationService.deleteEntityById(id);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ch.puzzle.okr.dto.userOkrData;

public record UserKeyResultDataDto(Long keyResultId, String keyResultName, Long objectiveId, String objectiveName) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ch.puzzle.okr.dto.userOkrData;

import java.util.List;

public record UserOkrDataDto(List<UserKeyResultDataDto> keyResults) {
}
25 changes: 25 additions & 0 deletions backend/src/main/java/ch/puzzle/okr/mapper/UserOkrDataMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ch.puzzle.okr.mapper;

import ch.puzzle.okr.dto.userOkrData.UserKeyResultDataDto;
import ch.puzzle.okr.dto.userOkrData.UserOkrDataDto;
import ch.puzzle.okr.models.keyresult.KeyResult;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class UserOkrDataMapper {

public UserOkrDataDto toDto(List<KeyResult> keyResults) {
return new UserOkrDataDto(toUserKeyResultDataDtos(keyResults));
}

private List<UserKeyResultDataDto> toUserKeyResultDataDtos(List<KeyResult> keyResults) {
return keyResults.stream() //
.map(keyResult -> new UserKeyResultDataDto( //
keyResult.getId(), keyResult.getTitle(), //
keyResult.getObjective().getId(), keyResult.getObjective().getTitle() //
)) //
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package ch.puzzle.okr.service.authorization;

import ch.puzzle.okr.ErrorKey;
import ch.puzzle.okr.dto.userOkrData.UserOkrDataDto;
import ch.puzzle.okr.exception.OkrResponseStatusException;
import ch.puzzle.okr.mapper.UserOkrDataMapper;
import ch.puzzle.okr.models.User;
import ch.puzzle.okr.models.UserTeam;
import ch.puzzle.okr.models.keyresult.KeyResult;
import ch.puzzle.okr.service.business.KeyResultBusinessService;
import ch.puzzle.okr.service.business.UserBusinessService;
import org.springframework.stereotype.Service;

Expand All @@ -17,12 +21,14 @@ public class UserAuthorizationService {
private final AuthorizationService authorizationService;

private final TeamAuthorizationService teamAuthorizationService;
private final KeyResultBusinessService keyResultBusinessService;

public UserAuthorizationService(UserBusinessService userBusinessService, AuthorizationService authorizationService,
TeamAuthorizationService teamAuthorizationService) {
TeamAuthorizationService teamAuthorizationService, KeyResultBusinessService keyResultBusinessService) {
this.userBusinessService = userBusinessService;
this.authorizationService = authorizationService;
this.teamAuthorizationService = teamAuthorizationService;
this.keyResultBusinessService = keyResultBusinessService;
}

public List<User> getAllUsers() {
Expand Down Expand Up @@ -58,4 +64,21 @@ public List<User> createUsers(List<User> userList) {
OkrResponseStatusException.of(ErrorKey.NOT_AUTHORIZED_TO_WRITE, USER));
return userBusinessService.createUsers(userList);
}

public boolean isUserMemberOfTeams(long id) {
List<UserTeam> userTeamList = userBusinessService.getUserById(id).getUserTeamList();
return userTeamList != null && !userTeamList.isEmpty();
}

public void deleteEntityById(long id) {
AuthorizationService.checkRoleWriteAndReadAll(authorizationService.updateOrAddAuthorizationUser(),
OkrResponseStatusException.of(ErrorKey.NOT_AUTHORIZED_TO_DELETE, USER));

userBusinessService.deleteEntityById(id);
}

public UserOkrDataDto getUserOkrData(long id) {
List<KeyResult> keyResultsOwnedByUser = keyResultBusinessService.getKeyResultsOwnedByUser(id);
return new UserOkrDataMapper().toDto(keyResultsOwnedByUser);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,8 @@ public boolean isImUsed(Long id, KeyResult keyResult) {
private boolean isKeyResultTypeChangeable(Long id) {
return !hasKeyResultAnyCheckIns(id);
}

public List<KeyResult> getKeyResultsOwnedByUser(long id) {
return keyResultPersistenceService.getKeyResultsOwnedByUser(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,10 @@ public List<User> createUsers(List<User> userList) {
var userIter = userPersistenceService.saveAll(userList);
return StreamSupport.stream(userIter.spliterator(), false).toList();
}

@Transactional
public void deleteEntityById(long id) {
validationService.validateOnDelete(id);
userPersistenceService.deleteById(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,18 @@ public KeyResult recreateEntity(Long id, KeyResult keyResult) {
public KeyResult updateEntity(KeyResult keyResult) {
return save(keyResult);
}

public boolean isUserOwnerOfKeyResults(long id) {
List<KeyResult> allKeyResults = findAll();
long numberOfKeyResultsOfUser = allKeyResults.stream()
.filter(keyResult -> keyResult.getOwner().getId().equals(id)).count();
return numberOfKeyResultsOfUser > 0;
}

public List<KeyResult> getKeyResultsOwnedByUser(long userId) {
return findAll().stream() //
.filter(keyResult -> keyResult.getOwner().getId().equals(userId)) //
.toList();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@
import org.hamcrest.Matchers;
import org.hamcrest.core.Is;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.BDDMockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.web.server.ResponseStatusException;

import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -29,6 +32,7 @@

import static ch.puzzle.okr.controller.ActionControllerIT.SUCCESSFUL_UPDATE_BODY;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

Expand Down Expand Up @@ -163,4 +167,19 @@ void shouldCreateUsers() throws Exception {
.andExpect(jsonPath("$[0].isOkrChampion", Is.is(false)));
}

@Test
void shouldDeleteUser() throws Exception {
mvc.perform(delete("/api/v1/users/10").with(SecurityMockMvcRequestPostProcessors.csrf()))
.andExpect(MockMvcResultMatchers.status().isOk());
}

@DisplayName("should throw exception when user with id cant be found while deleting")
@Test
void throwExceptionWhenUserWithIdCantBeFoundWhileDeleting() throws Exception {
doThrow(new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found")).when(userAuthorizationService)
.deleteEntityById(1000);

mvc.perform(delete("/api/v1/users/1000").with(SecurityMockMvcRequestPostProcessors.csrf()))
.andExpect(MockMvcResultMatchers.status().isNotFound());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import ch.puzzle.okr.models.User;
import ch.puzzle.okr.models.authorization.AuthorizationUser;
import ch.puzzle.okr.service.business.UserBusinessService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
Expand Down Expand Up @@ -106,4 +107,35 @@ void createUsers_shouldThrowErrorIfLoggedInUserIsNotOkrChampion() {
assertThrows(OkrResponseStatusException.class,
() -> userAuthorizationService.createUsers(List.of(user, user2)));
}

@DisplayName("isUserMemberOfTeams() should return false if user is not member of teams")
@Test
void isUserMemberOfTeamsShouldReturnFalseIfUserIsNotMemberOfTeams() {
// arrange
Long userId = 1L;
User userWithoutTeams = defaultUser(userId);
when(userBusinessService.getUserById(userId)).thenReturn(userWithoutTeams);

// act
boolean isUserMemberOfTeams = userAuthorizationService.isUserMemberOfTeams(1L);

// assert
assertFalse(isUserMemberOfTeams);
}

@DisplayName("isUserMemberOfTeams() should return true if user is member of teams")
@Test
void isUserMemberOfTeamsShouldReturnTrueIfUserIsMemberOfTeams() {
// arrange
User userWithTeams = user2;
Long userId = user2.getId();
when(userBusinessService.getUserById(userId)).thenReturn(userWithTeams);

// act
boolean isUserMemberOfTeams = userAuthorizationService.isUserMemberOfTeams(userId);

// assert
assertTrue(isUserMemberOfTeams);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,19 @@ class UserBusinessServiceTest {

@BeforeEach
void setUp() {
User userAlice = User.Builder.builder().withId(2L).withFirstname("Alice").withLastname("Wunderland")
.withEmail("[email protected]").build();

User userBob = User.Builder.builder().withId(9L).withFirstname("Bob").withLastname("Baumeister")
.withEmail("[email protected]").build();
User userAlice = User.Builder.builder() //
.withId(2L) //
.withFirstname("Alice") //
.withLastname("Wunderland") //
.withEmail("[email protected]") //
.build();

User userBob = User.Builder.builder() //
.withId(9L) //
.withFirstname("Bob") //
.withLastname("Baumeister") //
.withEmail("[email protected]") //
.build();

userList = Arrays.asList(userAlice, userBob);
}
Expand Down Expand Up @@ -120,9 +128,8 @@ void getOrCreateUserShouldThrowResponseStatusExceptionWhenInvalidUser() {
Mockito.doThrow(new ResponseStatusException(HttpStatus.BAD_REQUEST, "Not allowed to give an id"))
.when(validationService).validateOnGetOrCreate(newUser);

ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> {
userBusinessService.getOrCreateUser(newUser);
});
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
() -> userBusinessService.getOrCreateUser(newUser));

assertEquals(HttpStatus.BAD_REQUEST, exception.getStatusCode());
assertEquals("Not allowed to give an id", exception.getReason());
Expand Down Expand Up @@ -162,4 +169,11 @@ void setOkrChampion_shouldNotThrowExceptionIfSecondLastOkrChampIsRemoved() {
verify(userPersistenceService, times(1)).save(user);
assertFalse(user.isOkrChampion());
}

@Test
void shouldDeleteUser() {
userBusinessService.deleteEntityById(23L);

verify(userPersistenceService, times(1)).deleteById(23L);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ch.puzzle.okr.service.persistence;

import ch.puzzle.okr.exception.OkrResponseStatusException;
import ch.puzzle.okr.models.User;
import ch.puzzle.okr.multitenancy.TenantContext;
import ch.puzzle.okr.test.SpringIntegrationTest;
Expand All @@ -8,6 +9,9 @@
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -178,4 +182,42 @@ private void assertUser(String firstName, String lastName, String email, User cu
assertEquals(lastName, currentUser.getLastname());
assertEquals(email, currentUser.getEmail());
}

@DisplayName("deleteById() should delete user when user found")
@Test
void deleteByIdShouldDeleteUserWhenUserFound() {
// arrange
User user = createUser();

// act
userPersistenceService.deleteById(user.getId());

// assert
OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, //
() -> userPersistenceService.findById(createdUser.getId()));

assertEquals(HttpStatus.NOT_FOUND, exception.getStatusCode());
}

private User createUser() {
User newUser = User.Builder.builder() //
.withId(null) //
.withFirstname("firstname") //
.withLastname("lastname") //
.withEmail("[email protected]") //
.build();
createdUser = userPersistenceService.getOrCreateUser(newUser);
assertNotNull(createdUser.getId());
return createdUser;
}

@DisplayName("deleteById() should throw exception when Id is null")
@Test
void deleteByIdShouldThrowExceptionWhenIdIsNull() {
InvalidDataAccessApiUsageException exception = assertThrows(InvalidDataAccessApiUsageException.class, //
() -> userPersistenceService.deleteById(null));

assertEquals("The given id must not be null", exception.getMessage());
}

}

0 comments on commit 756dd72

Please sign in to comment.