diff --git a/src/main/java/hng_java_boilerplate/organisation/controller/OrganisationController.java b/src/main/java/hng_java_boilerplate/organisation/controller/OrganisationController.java index 0010de962..a29c307ef 100644 --- a/src/main/java/hng_java_boilerplate/organisation/controller/OrganisationController.java +++ b/src/main/java/hng_java_boilerplate/organisation/controller/OrganisationController.java @@ -1,7 +1,8 @@ package hng_java_boilerplate.organisation.controller; -import hng_java_boilerplate.organisation.dto.CreateOrganisationRequestDto; -import hng_java_boilerplate.organisation.dto.CreateOrganisationResponseDto; +import hng_java_boilerplate.organisation.dto.*; +import hng_java_boilerplate.organisation.interfaces.AddUserResponse; +import hng_java_boilerplate.organisation.service.AddUsersToOrganisationService; import hng_java_boilerplate.organisation.entity.Organisation; import hng_java_boilerplate.organisation.service.OrganisationService; import io.swagger.v3.oas.annotations.Operation; @@ -19,6 +20,7 @@ @Tag(name="Organisation", description = "Controller for Organisation") public class OrganisationController { private final OrganisationService organisationService; + private final AddUsersToOrganisationService addUsersToOrganisationService; @PostMapping @Operation(summary = "create organisation") @@ -31,6 +33,18 @@ public ResponseEntity createOrganisation( ); } + @PostMapping("/{organisationId}/users") + public ResponseEntity addUserToOrganisation( + @PathVariable("organisationId") String organisationId, + @RequestBody @Valid AddUserRequestDTO orgRequest, + Authentication authenticatedUser + ) { + AddUserResponse response = addUsersToOrganisationService.addUserToOrganisation(organisationId, orgRequest, + authenticatedUser); + + return ResponseEntity.status(HttpStatus.OK).body(response); + } + @GetMapping("/{organisationId}") public ResponseEntity getOrganisationById(@PathVariable String organisationId) { Organisation organisation = organisationService.getOrganisationById(organisationId); diff --git a/src/main/java/hng_java_boilerplate/organisation/dto/AddUserRequestDTO.java b/src/main/java/hng_java_boilerplate/organisation/dto/AddUserRequestDTO.java new file mode 100644 index 000000000..422665369 --- /dev/null +++ b/src/main/java/hng_java_boilerplate/organisation/dto/AddUserRequestDTO.java @@ -0,0 +1,15 @@ +package hng_java_boilerplate.organisation.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Size; +import lombok.Builder; + +import java.util.List; + +@Builder +public record AddUserRequestDTO( + @NotEmpty(message = "User ID cannot be empty") + @Size(min = 1, message = "Provide a valid user ID") + List user_ids +) { +} \ No newline at end of file diff --git a/src/main/java/hng_java_boilerplate/organisation/dto/AddUserResponseDTO.java b/src/main/java/hng_java_boilerplate/organisation/dto/AddUserResponseDTO.java new file mode 100644 index 000000000..f4f85b662 --- /dev/null +++ b/src/main/java/hng_java_boilerplate/organisation/dto/AddUserResponseDTO.java @@ -0,0 +1,16 @@ +package hng_java_boilerplate.organisation.dto; + +import hng_java_boilerplate.organisation.interfaces.AddUserResponse; +import lombok.Builder; + +import java.util.List; + +@Builder +public record AddUserResponseDTO( + String status, + String message, + String organization_id, + List users_added_to_organisation, + Integer status_code +) implements AddUserResponse { +} diff --git a/src/main/java/hng_java_boilerplate/organisation/interfaces/AddUserResponse.java b/src/main/java/hng_java_boilerplate/organisation/interfaces/AddUserResponse.java new file mode 100644 index 000000000..5070a2c98 --- /dev/null +++ b/src/main/java/hng_java_boilerplate/organisation/interfaces/AddUserResponse.java @@ -0,0 +1,4 @@ +package hng_java_boilerplate.organisation.interfaces; + +public interface AddUserResponse { +} diff --git a/src/main/java/hng_java_boilerplate/organisation/service/AddUsersToOrganisationService.java b/src/main/java/hng_java_boilerplate/organisation/service/AddUsersToOrganisationService.java new file mode 100644 index 000000000..dea84f895 --- /dev/null +++ b/src/main/java/hng_java_boilerplate/organisation/service/AddUsersToOrganisationService.java @@ -0,0 +1,70 @@ +package hng_java_boilerplate.organisation.service; + +import hng_java_boilerplate.exception.ConflictException; +import hng_java_boilerplate.exception.NotFoundException; +import hng_java_boilerplate.exception.UnAuthorizedException; +import hng_java_boilerplate.organisation.dto.AddUserRequestDTO; +import hng_java_boilerplate.organisation.dto.AddUserResponseDTO; +import hng_java_boilerplate.organisation.entity.Organisation; +import hng_java_boilerplate.organisation.interfaces.AddUserResponse; +import hng_java_boilerplate.organisation.repository.OrganisationRepository; +import hng_java_boilerplate.user.entity.User; +import hng_java_boilerplate.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class AddUsersToOrganisationService { + private final OrganisationRepository organisationRepository; + private final UserRepository userRepository; + + @Transactional + public AddUserResponse addUserToOrganisation( + String organisationId, + AddUserRequestDTO orgRequest, + Authentication authenticatedUser + ) { + Organisation organisation = organisationRepository.findById(organisationId). + orElseThrow(() -> new NotFoundException("Organisation with id " + organisationId + " does not exist")); + + User user = (User) authenticatedUser.getPrincipal(); + + if (!organisation.getOwner().equals(user.getId())) { + throw new UnAuthorizedException("User is not owner of the organisation"); + } + + List addedUserIds = new ArrayList<>(); + + for (String userId : orgRequest.user_ids()) { + if (organisation.getUsers().stream().anyMatch(user1 -> user1.getId().equals(userId))) { + throw new ConflictException("User with id " + userId + " is already in the organisation"); + } + + var userExists = userRepository.findById(userId); + if (userExists.isEmpty()) { + throw new NotFoundException("User with id " + userId + " does not exist"); + } + + organisation.getUsers().add(userExists.get()); + userExists.get().getOrganisations().add(organisation); + addedUserIds.add(userId); + } + + organisationRepository.save(organisation); + + return AddUserResponseDTO.builder() + .status("success") + .message("Users added to organisation") + .organization_id(organisation.getId()) + .users_added_to_organisation(addedUserIds) + .status_code(HttpStatus.OK.value()) + .build(); + } +} diff --git a/src/test/java/hng_java_boilerplate/organisation/service/AddUsersToOrganisationServiceTest.java b/src/test/java/hng_java_boilerplate/organisation/service/AddUsersToOrganisationServiceTest.java new file mode 100644 index 000000000..804db9bc8 --- /dev/null +++ b/src/test/java/hng_java_boilerplate/organisation/service/AddUsersToOrganisationServiceTest.java @@ -0,0 +1,185 @@ +package hng_java_boilerplate.organisation.service; + +import hng_java_boilerplate.exception.ConflictException; +import hng_java_boilerplate.exception.NotFoundException; +import hng_java_boilerplate.exception.UnAuthorizedException; +import hng_java_boilerplate.organisation.dto.AddUserRequestDTO; +import hng_java_boilerplate.organisation.dto.AddUserResponseDTO; +import hng_java_boilerplate.organisation.entity.Organisation; +import hng_java_boilerplate.organisation.repository.OrganisationRepository; +import hng_java_boilerplate.user.entity.User; +import hng_java_boilerplate.user.repository.UserRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.Authentication; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class AddUsersToOrganisationServiceTest { + + @Mock + private OrganisationRepository organisationRepository; + + @Mock + private UserRepository userRepository; + + @Mock + private Authentication authenticatedUser; + + @InjectMocks + private AddUsersToOrganisationService addUsersToOrganisationService; + + private AddUserRequestDTO orgRequest; + private User user; + + @Test + void testAddUserToOrganisation_OrganisationNotFound() { + String organisationId = "b1e009c5-a197-42f9-b9a3-98fc357b5f08"; + AddUserRequestDTO orgRequest = new AddUserRequestDTO(List.of("user1", "user2", "user3")); + + when(organisationRepository.findById(organisationId)).thenReturn(Optional.empty()); + + NotFoundException exception = assertThrows(NotFoundException.class, () -> + addUsersToOrganisationService.addUserToOrganisation(organisationId, orgRequest, authenticatedUser)); + assertEquals("Organisation with id " + organisationId + " does not exist", exception.getMessage()); + + verify(organisationRepository).findById(organisationId); + verifyNoMoreInteractions(organisationRepository); + verifyNoInteractions(userRepository); + verifyNoInteractions(authenticatedUser); + } + + @Test + void testAddUserToOrganisation_UserNotOwner() { + String organisationId = "b1e009c5-a197-42f9-b9a3-98fc357b5f08"; + AddUserRequestDTO orgRequest = new AddUserRequestDTO(List.of("user1", "user2", "user3")); + User activeUser = new User(); + activeUser.setId("user-123"); + + Organisation organisation = new Organisation(); + organisation.setOwner("user-456"); + + when(organisationRepository.findById(organisationId)).thenReturn(Optional.of(organisation)); + when(authenticatedUser.getPrincipal()).thenReturn(activeUser); + + UnAuthorizedException exception = assertThrows(UnAuthorizedException.class, () -> + addUsersToOrganisationService.addUserToOrganisation(organisationId, orgRequest, authenticatedUser)); + assertEquals("User is not owner of the organisation", exception.getMessage()); + + verify(organisationRepository).findById(organisationId); + verify(authenticatedUser).getPrincipal(); + verifyNoMoreInteractions(organisationRepository); + verifyNoMoreInteractions(authenticatedUser); + verifyNoInteractions(userRepository); + } + + @Test + void testAddUserToOrganisation_UserAlreadyInOrganisation() { + String organisationId = "b1e009c5-a197-42f9-b9a3-98fc357b5f08"; + AddUserRequestDTO orgRequest = new AddUserRequestDTO(List.of("user1", "user2", "user3")); + User activeUser = new User(); + activeUser.setId("user-123"); + + User existingUser = new User(); + existingUser.setId("user1"); + + Organisation organisation = new Organisation(); + organisation.setId(organisationId); + organisation.setOwner("user-123"); + organisation.setUsers(List.of(existingUser)); + + when(organisationRepository.findById(organisationId)).thenReturn(Optional.of(organisation)); + when(authenticatedUser.getPrincipal()).thenReturn(activeUser); + + ConflictException exception = assertThrows(ConflictException.class, () -> + addUsersToOrganisationService.addUserToOrganisation(organisationId, orgRequest, authenticatedUser)); + assertEquals("User with id user1 is already in the organisation", exception.getMessage()); + + verify(organisationRepository).findById(organisationId); + verify(authenticatedUser).getPrincipal(); + verifyNoMoreInteractions(organisationRepository); + verifyNoMoreInteractions(authenticatedUser); + verifyNoInteractions(userRepository); + } + + @Test + void testAddUserToOrganisation_UserDoesNotExist() { + String organisationId = "b1e009c5-a197-42f9-b9a3-98fc357b5f08"; + AddUserRequestDTO orgRequest = new AddUserRequestDTO(List.of("user2")); + User activeUser = new User(); + activeUser.setId("user-123"); + + Organisation organisation = new Organisation(); + organisation.setId(organisationId); + organisation.setOwner("user-123"); + organisation.setUsers(new ArrayList<>()); + + when(organisationRepository.findById(organisationId)).thenReturn(Optional.of(organisation)); + when(authenticatedUser.getPrincipal()).thenReturn(activeUser); + when(userRepository.findById("user2")).thenReturn(Optional.empty()); + + NotFoundException exception = assertThrows(NotFoundException.class, () -> + addUsersToOrganisationService.addUserToOrganisation(organisationId, orgRequest, authenticatedUser)); + assertEquals("User with id user2 does not exist", exception.getMessage()); + + verify(organisationRepository).findById(organisationId); + verify(authenticatedUser).getPrincipal(); + verify(userRepository).findById("user2"); + verifyNoMoreInteractions(organisationRepository); + verifyNoMoreInteractions(authenticatedUser); + verifyNoMoreInteractions(userRepository); + } + + @Test + void testAddUserToOrganisation_Successful() { + String organisationId = "b1e009c5-a197-42f9-b9a3-98fc357b5f08"; + AddUserRequestDTO orgRequest = new AddUserRequestDTO(List.of("user1", "user2")); + User activeUser = new User(); + activeUser.setId("user-123"); + + Organisation organisation = new Organisation(); + organisation.setId(organisationId); + organisation.setOwner("user-123"); + organisation.setUsers(new ArrayList<>()); + + User user1 = new User(); + user1.setId("user1"); + user1.setOrganisations(new ArrayList<>()); + + User user2 = new User(); + user2.setId("user2"); + user2.setOrganisations(new ArrayList<>()); + + when(organisationRepository.findById(organisationId)).thenReturn(Optional.of(organisation)); + when(authenticatedUser.getPrincipal()).thenReturn(activeUser); + when(userRepository.findById("user1")).thenReturn(Optional.of(user1)); + when(userRepository.findById("user2")).thenReturn(Optional.of(user2)); + + var response = (AddUserResponseDTO) addUsersToOrganisationService.addUserToOrganisation(organisationId, + orgRequest, authenticatedUser); + + assertEquals("success", response.status()); + assertEquals("Users added to organisation", response.message()); + assertEquals(organisationId, response.organization_id()); + assertEquals(List.of("user1", "user2"), response.users_added_to_organisation()); + assertEquals(200, response.status_code()); + + verify(organisationRepository).findById(organisationId); + verify(authenticatedUser).getPrincipal(); + verify(userRepository).findById("user1"); + verify(userRepository).findById("user2"); + verify(organisationRepository).save(organisation); + verifyNoMoreInteractions(organisationRepository); + verifyNoMoreInteractions(authenticatedUser); + verifyNoMoreInteractions(userRepository); + } +} \ No newline at end of file